This commit is contained in:
Fred Hallock 2025-04-17 01:33:22 +02:00 committed by GitHub
commit be4fb1c400
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 1222 additions and 191 deletions

BIN
data/light_gun_mask.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

290
data/light_gun_mask.svg Normal file
View File

@ -0,0 +1,290 @@
<svg width="1024" height="1024" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<!-- Created with SVG-edit - https://github.com/SVG-Edit/svgedit-->
<g class="layer">
<title>Layer 1</title>
<rect fill="#000000" height="128" id="svg_2" stroke="#ff0000" stroke-width="0" width="243" x="380" y="271"/>
<path d="m600.67,449l-13.33,-6l-16,40.33l18.67,10.33l10.67,-44.67l-0.01,0.01z" fill="#000000" id="svg_191" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<rect fill="#000000" height="105.14" id="svg_64" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)" width="61.35" x="618.16" y="176.54"/>
<rect fill="#000000" height="40.27" id="svg_77" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)" width="177.3" x="656.54" y="401.41"/>
<path d="m916.05,438.78c3.75,4 4.19,18.32 4.22,25.94c0.03,7.62 -1.56,14.53 -2.91,18.59c-1.35,4.05 -3.24,4.86 -14.32,4.32c-11.08,-0.54 -1.08,-48.38 -1.14,-48.43c-0.05,-0.05 4.87,-4.94 12.49,-1.84l1.66,1.43l0,-0.01z" fill="#000000" id="svg_164" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m930.59,246.27c1.89,0 8.92,2.16 10.27,-10.27c1.35,-12.43 4.32,-33.78 4.05,-48.38c-0.27,-14.59 -16.22,-10.54 -16.27,-10.59c-0.05,-0.05 0.05,69.24 1.95,69.24z" fill="#000000" id="svg_65" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m388.86,370.14c4.43,7.14 36.71,26.29 47,26.86c10.29,0.57 0.43,20.43 0.29,20.29c-0.14,-0.14 -7.86,-0.43 -14.71,-1.57c-6.86,-1.14 -20.71,-11.43 -29.86,-13.57c-9.14,-2.14 -17.57,-2.71 -17.43,-2.86c0.14,-0.14 14.71,-29.14 14.71,-29.14l0,-0.01z" fill="#000000" id="svg_62" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m479.46,264.85l-11.77,43.23c0,0 -6.69,5.23 -10.15,9.85c-3.46,4.62 -7.31,14.23 -6.92,25c0.38,10.77 5,22.69 8.85,25.77c3.85,3.08 8.85,7.69 13.85,9.62c5,1.92 10.38,3.85 10.15,3.62c-0.23,-0.23 -0.15,3.31 -0.38,3.08c-0.23,-0.23 -15.92,-0.15 -23.62,-3.62c-7.69,-3.46 -16.92,-10 -22.69,-20.38c-5.77,-10.38 -8.08,-23.08 -8.46,-29.62c-0.38,-6.54 -0.38,-15 -0.62,-15.23c-0.23,-0.23 -14.77,-6.69 -19.38,-9.38c-4.62,-2.69 -16.15,-13.08 -18.46,-16.54c-2.31,-3.46 5,-25 4.77,-25.23c-0.23,-0.23 84.85,-0.15 84.85,-0.15l-0.02,-0.02z" fill="#0000ff" id="svg_30" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m113,39l18,7l5,19l-34,7l11,-33z" fill="#000000" id="svg_15" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m71,59l-1,-13l12,-20l35,8l1,28l-47,-3z" fill="#000000" id="svg_13" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<rect fill="#000000" height="73" id="svg_11" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" width="194" x="392" y="57"/>
<path d="m869,60l14,-22l27,3l13,8l2,14l-56,-3z" fill="#000000" id="svg_16" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m73.84,677.62c0,0 -1.08,2.16 6.22,4.32c7.3,2.16 88.11,13.24 88.05,13.19c-0.05,-0.05 37.89,3.03 46.54,3.57c8.65,0.54 57.57,2.7 68.65,-3.78c11.08,-6.49 -1.89,-27.03 -1.95,-27.08c-0.05,-0.05 -196.97,-28.32 -197.03,-28.38c-0.05,-0.05 -10.49,38.16 -10.49,38.16l0.01,0z" fill="#000000" id="svg_67" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m858,522l6,7.67l13,0.33l2.33,-11c0,0 -20.67,0.67 -20.67,1c0,0.33 -0.67,2 -0.67,2l0.01,0z" fill="#000000" id="svg_192" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="820" cy="493" fill="#000000" fill-opacity="null" id="svg_194" rx="49" ry="49" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="820" cy="493" fill="#000000" fill-opacity="null" id="svg_193" rx="37.5" ry="37.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m723.3,403.03c-0.27,0 -0.27,23.51 -0.27,23.78c0,0.27 -115.41,-1.62 -115.46,-1.68c-0.05,-0.05 -31.03,87.62 -30.76,87.62c0.27,0 28.92,27.84 28.86,27.78c-0.05,-0.05 149.55,-0.49 149.73,-0.54c0.18,-0.05 4.1,-8.6 4.05,-8.65c-0.05,-0.05 21.68,-0.49 21.62,-0.54c-0.05,-0.05 3.3,-5.35 3.24,-5.41c-0.05,-0.05 87.08,-0.22 87.03,-0.27c-0.05,-0.05 5.19,4.92 5.14,4.86c-0.05,-0.05 18.43,0.32 18.38,0.27c-0.05,-0.05 27.62,-105.62 27.35,-105.62c-0.27,0 -165.95,2.16 -166,2.11c-0.05,-0.05 0.32,-24.27 0.32,-24.54c0,-0.27 -32.97,0.81 -33.24,0.81l0.01,0.02z" fill="#000000" id="svg_165" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m428.33,397.05l-6.79,15.65c0.71,-0.36 4.85,6.38 17.54,7.92c12.69,1.54 186.54,1.15 186.31,0.92c-0.23,-0.23 7.54,1.38 13.69,-3.23c6.15,-4.62 4.67,-8.79 4.77,-9.03c0.1,-0.23 11.77,-0.21 11.54,-0.44c-0.23,-0.23 193.69,0.62 193.46,0.38c-0.23,-0.23 3.31,-13.62 3.31,-14c0,-0.38 64.23,-0.38 64,-0.62c-0.23,-0.23 6,-5.54 8.69,-18.62c2.69,-13.08 1.07,-65.32 1.41,-66.12c0.34,-0.8 3.98,-5.42 3.75,-5.65c-0.23,-0.23 0.62,-58.23 1,-58.23c0.38,0 9.62,-42.69 9.38,-42.92c-0.23,-0.23 -2.08,-4 -2.31,-4.23c-0.23,-0.23 3.31,-8.23 3.08,-8.46c-0.23,-0.23 4.08,-3.62 3.85,-3.85c-0.23,-0.23 -287.46,1.38 -287.69,1.15c-0.23,-0.23 -16.31,71.77 -16.54,71.54c-0.23,-0.23 11,0.62 10.77,0.38c-0.23,-0.23 -3.62,12.54 -3.85,12.31c-0.23,-0.23 -158.76,-0.07 -164.42,-0.3c-5.66,-0.23 -53.71,1.45 -61.88,3.58c-8.16,2.13 -34.24,10.41 -38.47,41.56c-4.23,31.15 -0.38,36.92 1.54,41.54c1.92,4.62 8.46,22.31 8.23,22.08c-0.23,-0.23 -7.08,13.31 -7.31,13.08c-0.23,-0.23 0.23,7.15 0.23,7.54c0,0.38 -12.31,-0.77 -20.77,5.77c-8.46,6.54 -23.08,17.31 -24.23,22.31c-1.15,5 -5.77,33.46 -8.08,40.38c-2.31,6.92 -9.23,13.46 -12.31,18.08c-3.08,4.62 -5,10 -8.85,18.46c-3.85,8.46 -3.8,32.25 -9.56,41.87c-5.77,9.62 -16.2,15.83 -21.59,44.29c-5.38,28.46 -1.92,45.38 0,51.92c1.92,6.54 9.23,22.69 7.31,26.54c-1.92,3.85 -17.69,5.38 -31.92,4.62c-14.23,-0.77 -164.23,-23.08 -164.85,-23.31c-0.62,-0.23 -14,17.54 -14,17.15c0,-0.38 -1.92,8.85 -1.54,8.46c0.38,-0.38 -5.77,0.38 -9.23,-3.85c-3.46,-4.23 -27.31,-51.54 -21.54,-118.46c5.77,-66.92 46.54,-125 46.31,-125.23c-0.23,-0.23 62.96,-88.26 43.89,-132.07c-19.07,-43.8 -53.88,-38.32 -62.81,-38.78c-8.93,-0.46 -18.75,5.71 -23.85,4.71c-5.1,-1 -15.08,-25.56 -15.08,-31.33c0,-5.77 -0.45,-42.31 0.98,-42.54c1.44,-0.23 427.09,-1.69 434.17,-1.54c7.08,0.15 8.84,85.42 8.66,85.73c-0.18,0.31 0.08,1.57 -0.2,1.2c-0.27,-0.37 105.62,1 116.38,2.15c10.77,1.15 33.08,14.23 26.92,40c-6.15,25.77 -14.23,53.08 -16.54,61.15c-2.31,8.08 -17.69,19.62 -25,19.62c-7.31,0 -21.92,-0.38 -31.15,-0.38c-9.23,0 -28.46,-12.31 -33.46,-12.31c-5,0 -25.38,13.85 -55.77,12.69c-30.38,-1.15 -29.23,-2.69 -29.23,-2.69l-0.35,1.46z" fill="#000000" id="svg_53" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<rect fill="#000000" height="80.66" id="svg_54" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="0" width="205.56" x="429.46" y="189.46"/>
<rect fill="#000000" height="68" id="svg_17" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)" width="49" x="477" y="186"/>
<path d="m413.27,67.98l-11.27,41.02c0,0 -0.38,9.38 16,10c16.38,0.62 125.85,-0.23 140.46,0c14.62,0.23 15.92,-12.77 15.54,-13c-0.38,-0.23 -12,-24.05 -12,-39.9c0,-15.85 0,-13.1 0,-13.1c0,0 380,1 380,1c0,0 4.97,52.46 5.24,61.92c0.27,9.46 -1.24,72.08 -2.24,71.08c-1,-1 -911.56,4 -911.83,4c-0.28,0 0.56,-77.83 1.39,-82.28c0.83,-4.44 6.44,-37.72 16.44,-46.72c10,-9 20.33,-8.11 26.17,-8.11c5.83,0 307.37,-1.4 318.83,-0.91c11.46,0.49 19.88,6.68 17.27,15z" fill="#000000" id="svg_10" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<line fill="none" fill-opacity="0.5" id="svg_12" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" x1="414" x2="561" y1="69" y2="69"/>
<line fill="none" fill-opacity="0.5" id="svg_14" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" x1="78" x2="116" y1="33" y2="34"/>
<path d="m33.83,134.17c1,-27 26.17,-25.17 26.17,-25.17c0,0 342,0 342,0" fill="none" fill-opacity="0.5" id="svg_20" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<line fill="none" fill-opacity="0.5" id="svg_21" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" x1="99" x2="79" y1="110" y2="190"/>
<line fill="none" fill-opacity="0.5" id="svg_25" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" x1="109" x2="89" y1="110" y2="190"/>
<line fill="none" fill-opacity="0.5" id="svg_26" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" x1="119" x2="99" y1="110" y2="190"/>
<line fill="none" fill-opacity="0.5" id="svg_28" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" x1="129" x2="109" y1="110" y2="190"/>
<line fill="none" fill-opacity="0.5" id="svg_29" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" x1="139" x2="119" y1="110" y2="190"/>
<line fill="none" fill-opacity="0.5" id="svg_31" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" x1="149" x2="129" y1="110" y2="190"/>
<line fill="none" fill-opacity="0.5" id="svg_32" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" x1="159" x2="139" y1="110" y2="190"/>
<line fill="none" fill-opacity="0.5" id="svg_34" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" x1="169" x2="149" y1="110" y2="190"/>
<line fill="none" fill-opacity="0.5" id="svg_35" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" x1="179" x2="159" y1="110" y2="190"/>
<line fill="none" fill-opacity="0.5" id="svg_36" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" x1="189" x2="169" y1="110" y2="190"/>
<line fill="none" fill-opacity="0.5" id="svg_37" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" x1="199" x2="179" y1="110" y2="190"/>
<line fill="none" fill-opacity="0.5" id="svg_38" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" x1="209" x2="189" y1="110" y2="190"/>
<line fill="none" fill-opacity="0.5" id="svg_39" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" x1="219" x2="199" y1="110" y2="190"/>
<line fill="none" fill-opacity="0.5" id="svg_40" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" x1="229" x2="209" y1="110" y2="190"/>
<line fill="none" fill-opacity="0.5" id="svg_41" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" x1="239" x2="219" y1="110" y2="190"/>
<line fill="none" fill-opacity="0.5" id="svg_42" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" x1="249" x2="229" y1="110" y2="190"/>
<line fill="none" fill-opacity="0.5" id="svg_43" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" x1="259" x2="239" y1="110" y2="190"/>
<line fill="none" fill-opacity="0.5" id="svg_44" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" x1="269" x2="249" y1="110" y2="190"/>
<path d="m639.1,188.54c3.65,7.86 3.83,9.38 0.36,23.23c-3.46,13.85 -10,42.69 -10.23,42.46c-0.23,-0.23 -370.92,1.38 -371.15,1.15c-0.23,-0.23 -60.54,-2.46 -70.54,17.15c-10,19.62 2.31,55.77 18.85,65.77c16.54,10 21.92,5.77 28.08,9.23c6.15,3.46 39.23,31.92 95.77,35.77" fill="#000000" fill-opacity="0.01" id="svg_24" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<line fill="none" fill-opacity="0.5" id="svg_45" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" x1="279" x2="259" y1="110" y2="190"/>
<line fill="none" fill-opacity="0.5" id="svg_46" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" x1="289" x2="269" y1="110" y2="190"/>
<line fill="none" fill-opacity="0.5" id="svg_47" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" x1="299" x2="279" y1="110" y2="190"/>
<line fill="none" fill-opacity="0.5" id="svg_48" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" x1="309" x2="289" y1="110" y2="190"/>
<line fill="none" fill-opacity="0.5" id="svg_52" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" x1="574.75" x2="708.5" y1="107.25" y2="107.25"/>
<line fill="none" fill-opacity="0.5" id="svg_3" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)" x1="478" x2="525" y1="215.77" y2="215.77"/>
<line fill="none" fill-opacity="0.5" id="svg_4" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" x1="478" x2="525" y1="223.54" y2="223.54"/>
<line fill="none" fill-opacity="0.5" id="svg_6" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" x1="478" x2="525" y1="231.35" y2="231.35"/>
<line fill="none" fill-opacity="0.5" id="svg_5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)" x1="478" x2="525" y1="239.12" y2="239.12"/>
<path d="m411.64,60.4l-328.56,1.14c-18.61,0.28 -34.69,-1.86 -40.15,14.46" fill="#000000" fill-opacity="0.5" id="svg_8" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<line fill="none" fill-opacity="0.5" id="svg_9" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)" x1="562" x2="718" y1="60" y2="60"/>
<path d="m725.96,53.35l-7.5,5.88l-11.15,47.69l3.85,10.38l235.71,0.31" fill="none" fill-opacity="0.5" id="svg_18" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<line fill="none" fill-opacity="0.5" id="svg_23" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" x1="478" x2="525" y1="246.42" y2="246.42"/>
<path d="m373.31,343.31l-31,0.15c0,0 -7.85,13.31 -9.38,16c-1.54,2.69 -4.23,15.77 -3.08,19.62c1.15,3.85 4.23,26.15 8.85,27.69c4.62,1.54 11.92,-0.77 20,-7.69c8.08,-6.92 8.46,-5 10,-5.38c1.54,-0.38 3.85,0 3.62,-0.23c-0.23,-0.23 6.38,-22.08 6.15,-22.31c-0.23,-0.23 -3.23,-8.23 -4.38,-12.46c-1.15,-4.23 -0.77,-15.38 -0.77,-15.38l-0.01,-0.01z" fill="#000000" id="svg_27" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m938.31,198.69c0,0 -256.92,-0.38 -263.08,0c-6.15,0.38 -8.08,5.77 -8.31,5.54c-0.23,-0.23 -55.15,202.92 -55.38,202.69c-0.23,-0.23 37.15,0.23 36.38,0.23c-0.77,0 12.69,-36.15 13.62,-38.69c0.92,-2.54 2.92,-5.15 6.38,-5.15c3.46,0 95.38,0.77 99.23,0.77c3.85,0 6.15,1.15 8.46,4.23c2.31,3.08 15.38,27.31 15.15,27.08c-0.23,-0.23 67.54,-0.54 67.31,-0.77c-0.23,-0.23 6.38,-27.08 16,-47.85c9.62,-20.77 22.69,-36.15 22.46,-36.38c-0.23,-0.23 27.74,-0.44 28.93,-0.38" fill="#000000" fill-opacity="0" id="svg_55" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m928.87,305.23c-5.53,-0.29 -33.25,0.38 -33.48,0.15c-0.23,-0.23 -40.54,75.23 -40.54,74.85c0,-0.38 -56.54,0.38 -56.77,0.15c-0.23,-0.23 -17.08,-27.46 -17.31,-27.69c-0.23,-0.23 -123.62,-0.92 -123.85,-1.15c-0.23,-0.23 -18.62,44.85 -18.85,44.62c-0.23,-0.23 -24,-0.15 -24.23,-0.38" fill="#000000" fill-opacity="0.01" id="svg_56" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<line fill="none" fill-opacity="0.5" id="svg_57" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)" x1="637.92" x2="646.77" y1="394.85" y2="406.38"/>
<line fill="none" fill-opacity="0.01" id="svg_58" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" x1="657.15" x2="664.08" y1="351" y2="363.69"/>
<line fill="none" fill-opacity="0.01" id="svg_59" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" x1="780.23" x2="772.54" y1="353.31" y2="365.62"/>
<line fill="none" fill-opacity="0.01" id="svg_60" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" x1="797.92" x2="791.5" y1="379.46" y2="394.85"/>
<line fill="none" fill-opacity="0.01" id="svg_61" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)" x1="854.46" x2="857.5" y1="381.38" y2="393"/>
<path d="m476.27,274.65c-14.05,33.51 -28.65,41.62 -32.7,54.59c-4.05,12.97 -2.16,28.11 8.38,40c5.27,5.95 11.08,9.59 16.15,11.82c5.07,2.23 9.39,3.04 11.69,3.31" fill="#000000" fill-opacity="0" id="svg_63" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m77.62,669.51c28.38,5.41 113.24,20 129.46,20.27c16.22,0.27 20.54,-1.08 31.89,-0.27c11.35,0.81 20.54,3.24 34.59,1.89c14.05,-1.35 13.51,-5.68 13.78,-5.68" fill="#000000" fill-opacity="0.5" id="svg_68" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m692.76,230.32c10.27,0.27 206.76,-0.54 214.59,-0.27c7.84,0.27 14.05,2.43 11.35,11.62c-2.7,9.19 -5.14,19.19 -5.95,20.54c-0.81,1.35 0,4.86 -7.84,5.41c-7.84,0.54 -220.27,0.54 -225.95,1.08c-5.68,0.54 -9.73,-4.59 -7.03,-11.35c2.7,-6.76 5.14,-14.86 7.03,-19.73c1.89,-4.86 3.51,-7.57 13.78,-7.3l0.02,0z" fill="#000000" fill-opacity="0" id="svg_69" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<ellipse cx="826.54" cy="322.49" fill="#000000" fill-opacity="0.5" id="svg_70" rx="38.65" ry="39.19" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="826.54" cy="322.49" fill="#0000ff" id="svg_71" rx="23.51" ry="23.78" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m32.82,209.78c1.35,16.49 4.8,21.89 17.24,21.62c12.43,-0.27 59.31,-3.66 84.05,14.32c24.74,17.98 29.16,38.63 31.05,49.98" fill="#000000" fill-opacity="0" id="svg_72" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m75.73,671.41c-8.38,-1.35 -21.89,-29.73 -26.22,-68.38c-4.32,-38.65 3.78,-59.46 10.81,-68.65c7.03,-9.19 18.38,-4.86 21.35,-4.05c2.97,0.81 132.7,56.49 139.19,60.81c6.49,4.32 11.08,8.38 11.62,19.73c0.54,11.35 0.81,25.14 0,30.54c-0.81,5.41 -8.65,10 -21.89,8.65c-13.24,-1.35 -107.03,-15.41 -107.08,-15.46" fill="#000000" fill-opacity="0" id="svg_73" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m96,652.49c4.32,-5.68 7.3,-18.38 7.24,-18.43c-0.05,-0.05 -4.54,-7.51 -4.59,-7.57c-0.05,-0.05 3.3,-29.14 0.86,-46.7c-2.43,-17.57 -7.03,-45.95 -22.7,-50.54" fill="#000000" fill-opacity="0" id="svg_76" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<ellipse cx="676.5" cy="378.5" fill="#000000" id="svg_79" rx="10" ry="10" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<line fill="none" fill-opacity="0.5" id="svg_80" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" x1="676.54" x2="676.5" y1="373" y2="384"/>
<line fill="none" fill-opacity="0.5" id="svg_81" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" x1="671" x2="682" y1="378.5" y2="378.5"/>
<ellipse cx="897.35" cy="363.03" fill="#000000" id="svg_84" rx="10" ry="10" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<line fill="none" fill-opacity="0.5" id="svg_83" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" x1="897.39" x2="897.35" y1="357.53" y2="368.53"/>
<line fill="none" fill-opacity="0.5" id="svg_82" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" x1="891.85" x2="902.85" y1="363.03" y2="363.03"/>
<ellipse cx="232.49" cy="622.22" fill="#000000" id="svg_87" rx="10" ry="10" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<line fill="none" fill-opacity="0.5" id="svg_86" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" x1="232.53" x2="232.49" y1="616.72" y2="627.72"/>
<line fill="none" fill-opacity="0.5" id="svg_85" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" x1="226.99" x2="237.99" y1="622.22" y2="622.22"/>
<ellipse cx="75.46" cy="621.14" fill="#000000" id="svg_90" rx="10" ry="10" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<line fill="none" fill-opacity="0.5" id="svg_89" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)" x1="75.5" x2="75.46" y1="615.64" y2="626.64"/>
<line fill="none" fill-opacity="0.5" id="svg_88" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)" x1="69.96" x2="80.96" y1="621.14" y2="621.14"/>
<ellipse cx="618.97" cy="508.7" fill="#000000" id="svg_93" rx="10" ry="10" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<line fill="none" fill-opacity="0.5" id="svg_92" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" x1="619.01" x2="618.97" y1="503.2" y2="514.2"/>
<line fill="none" fill-opacity="0.5" id="svg_91" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" x1="613.47" x2="624.47" y1="508.7" y2="508.7"/>
<ellipse cx="887.89" cy="505.46" fill="#000000" id="svg_96" rx="10" ry="10" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<line fill="none" fill-opacity="0.5" id="svg_95" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)" x1="887.93" x2="887.89" y1="499.96" y2="510.96"/>
<line fill="none" fill-opacity="0.5" id="svg_94" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)" x1="882.39" x2="893.39" y1="505.46" y2="505.46"/>
<ellipse cx="352.22" cy="365.19" fill="#000000" id="svg_99" rx="10" ry="10" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<line fill="none" id="svg_98" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)" x1="352.26" x2="352.22" y1="359.69" y2="370.69"/>
<line fill="none" id="svg_97" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)" x1="346.72" x2="357.72" y1="365.19" y2="365.19"/>
<ellipse cx="75.73" cy="223.57" fill="#000000" id="svg_102" rx="10" ry="10" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<line fill="none" id="svg_101" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)" x1="75.77" x2="75.73" y1="218.07" y2="229.07"/>
<line fill="none" id="svg_100" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)" x1="70.23" x2="81.23" y1="223.57" y2="223.57"/>
<ellipse cx="163.57" cy="370.59" fill="#000000" id="svg_103" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="186.27" cy="379.78" fill="#000000" id="svg_104" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="212.49" cy="389.78" fill="#000000" id="svg_105" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="168.71" cy="389.51" fill="#000000" id="svg_106" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="195.19" cy="399.24" fill="#000000" id="svg_108" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="221.68" cy="408.97" fill="#000000" id="svg_109" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="153.84" cy="397.35" fill="#000000" id="svg_110" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="178.97" cy="406.27" fill="#000000" id="svg_111" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="203.57" cy="416.54" fill="#000000" id="svg_112" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="160.59" cy="416.27" fill="#000000" id="svg_113" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="187.08" cy="424.92" fill="#000000" id="svg_114" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="212.76" cy="432.21" fill="#000000" id="svg_115" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="146" cy="423.03" fill="#000000" id="svg_116" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="170.59" cy="431.67" fill="#000000" id="svg_117" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="195.19" cy="441.4" fill="#000000" id="svg_118" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="152.49" cy="442.21" fill="#000000" id="svg_119" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="137.62" cy="449.78" fill="#000000" id="svg_120" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="161.95" cy="458.16" fill="#000000" id="svg_121" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="178.7" cy="450.05" fill="#000000" id="svg_122" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="185.73" cy="467.35" fill="#000000" id="svg_123" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="203.3" cy="461.4" fill="#000000" id="svg_124" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="130.59" cy="475.73" fill="#000000" id="svg_125" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="146.81" cy="468.43" fill="#000000" id="svg_126" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="170.05" cy="477.35" fill="#000000" id="svg_127" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="154.65" cy="484.65" fill="#000000" id="svg_128" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="178.16" cy="493.84" fill="#000000" id="svg_129" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="194.11" cy="485.73" fill="#000000" id="svg_130" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="140.05" cy="493.03" fill="#000000" id="svg_131" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="162.76" cy="503.03" fill="#000000" id="svg_132" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="147.08" cy="510.05" fill="#000000" id="svg_133" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="124.11" cy="501.94" fill="#000000" id="svg_134" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="116.54" cy="528.16" fill="#000000" id="svg_135" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="132.49" cy="520.05" fill="#000000" id="svg_136" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="138.7" cy="537.08" fill="#000000" id="svg_137" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="153.84" cy="528.7" fill="#000000" id="svg_138" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="170.59" cy="519.51" fill="#000000" id="svg_139" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="160.59" cy="546" fill="#000000" id="svg_140" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m270.59,383.84c-7.03,-2.43 -21.08,-2.97 -29.19,9.46c-8.11,12.43 -58.11,126.49 -62.16,137.84c-4.05,11.35 -8.32,22.78 -0.22,30.35c8.11,7.57 18.59,9.65 20.22,9.65c1.62,0 9.19,-0.81 11.89,-5.68c2.7,-4.86 3.24,-10.81 5.68,-15.95c2.43,-5.14 11.08,-16.76 14.59,-23.24c3.51,-6.49 5.68,-32.97 10.54,-44.05c4.86,-11.08 18.65,-28.92 20.54,-33.78c1.89,-4.86 1.62,-21.89 5.14,-30.54c3.51,-8.65 7.57,-18.38 8.92,-21.35c1.35,-2.97 1.08,-10.27 -5.95,-12.7l0,-0.01z" fill="#000000" fill-opacity="0.5" id="svg_161" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<ellipse cx="202.76" cy="503.84" fill="#000000" id="svg_141" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="209.24" cy="520.32" fill="#000000" id="svg_142" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="192.76" cy="528.97" fill="#000000" id="svg_143" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="199.78" cy="545.46" fill="#000000" id="svg_144" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="184.65" cy="550.32" fill="#000000" id="svg_145" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="204.65" cy="560.86" fill="#000000" id="svg_146" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="215.46" cy="536.27" fill="#000000" id="svg_147" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="225.46" cy="512.75" fill="#000000" id="svg_148" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="218.97" cy="495.73" fill="#000000" id="svg_149" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="213.03" cy="478.43" fill="#000000" id="svg_150" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="235.46" cy="486" fill="#000000" id="svg_151" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="228.7" cy="470.59" fill="#000000" id="svg_152" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="221.41" cy="453.84" fill="#000000" id="svg_153" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="238.16" cy="445.19" fill="#000000" id="svg_154" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="230.32" cy="428.97" fill="#000000" id="svg_155" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="243.84" cy="461.94" fill="#000000" id="svg_156" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="255.19" cy="436.81" fill="#000000" id="svg_157" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="247.62" cy="418.7" fill="#000000" id="svg_158" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="241.95" cy="403.84" fill="#000000" id="svg_159" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="262.22" cy="410.32" fill="#000000" id="svg_160" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<text fill="#ff0000" font-family="Helvetica" font-size="24" font-weight="bold" id="svg_162" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="0" text-anchor="middle" transform="matrix(1, 0, 0, 1, 0, 0)" x="826.81" xml:space="preserve" y="331.95">B</text>
<text fill="#ff0000" font-family="Helvetica" font-size="24" font-weight="bold" id="svg_163" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="0" text-anchor="middle" transform="matrix(1, 0, 0, 1, 0, 0)" x="437.35" xml:space="preserve" y="301.41">A</text>
<rect fill="#000000" fill-opacity="0" height="75.14" id="svg_166" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)" width="23.78" x="729.24" y="431.95"/>
<path d="m888.97,445.46c0,0 11.89,0 14.32,0c2.43,0 3.51,1.62 2.97,5.14c-0.54,3.51 -7.03,27.84 -7.57,30.81c-0.54,2.97 -1.35,4.59 -4.59,4.59c-3.24,0 -8.65,0 -11.08,-0.27c-2.43,-0.27 -3.51,-3.24 -2.43,-6.76c1.08,-3.51 8.38,-33.51 8.38,-33.51z" fill="#000000" id="svg_167" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m850.43,446.01c0,0 14.83,0 17.26,0c2.43,0 5.85,0.6 5.3,4.11c-0.54,3.51 -7.3,29.46 -7.84,32.43c-0.54,2.97 -2.16,3.51 -5.41,3.51c-3.24,0 -17.57,0 -20,-0.27c-2.43,-0.27 -3.78,-3.24 -2.7,-6.76c1.08,-3.51 4.81,-24.22 6.71,-32.8l6.68,-0.23l0,0.01z" fill="#000000" id="svg_168" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m770.61,446.54c0.27,-0.27 51.35,-0.54 53.78,-0.54c2.43,0 5.14,1.08 4.59,4.59c-0.54,3.51 -7.3,29.46 -7.84,32.43c-0.54,2.97 -2.43,3.78 -5.68,3.78c-3.24,0 -37.03,0 -40,0c-2.97,0 -5.41,-1.08 -5.41,-4.32c0,-3.24 0.27,-35.68 0.54,-35.95l0.02,0.01z" fill="#000000" id="svg_169" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m625.46,437.08c4.86,0.27 85.41,0.27 90,0.27c4.59,0 7.57,0.54 7.84,6.76c0.27,6.22 -0.27,36.76 -0.27,41.89c0,5.14 -2.16,7.03 -7.3,7.3c-5.14,0.27 -102.43,0.54 -107.3,0.54c-4.86,0 -7.3,-4.32 -4.86,-11.89c2.43,-7.57 11.62,-34.32 13.24,-38.65c1.62,-4.32 3.78,-6.49 8.65,-6.22z" fill="#000000" fill-opacity="0" id="svg_170" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m316.87,422.09c-0.43,18.7 -2.61,29.57 -6.09,41.74c-3.48,12.17 -20.87,26.09 -25.65,38.26c-4.78,12.17 0,24.35 -3.04,35.65c-3.04,11.3 -20.43,23.04 -27.83,47.39c-7.39,24.35 -3.48,44.35 -0.87,53.48" fill="#000000" fill-opacity="0" id="svg_171" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m339.54,428.44c-4.34,-0.94 -40.51,-16.12 -47.45,-19.34c-6.94,-3.22 -11.2,5.82 -5.07,9.45c6.13,3.63 39.72,17.22 50.68,20.35" fill="#000000" id="svg_172" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m334.78,452.35c-6.98,-1.51 -45.41,-15.93 -51.98,-18.58c-6.56,-2.65 -10.44,6.57 -3.56,9.26c6.88,2.69 42.36,17.97 53.32,21.1" fill="#000000" id="svg_173" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m324.19,479.93c-2.26,-1.7 -45.23,-17.06 -51.22,-19.15c-6,-2.08 -11.01,7.33 -3.75,10.39c7.26,3.07 37.27,14.58 48.22,17.71" fill="#000000" id="svg_174" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m310.92,502.97c-4.34,-1.7 -43.9,-16.69 -50.84,-19.34c-6.94,-2.65 -8.93,6.95 -2.43,9.64c6.51,2.69 39.16,17.22 50.11,20.35" fill="#000000" id="svg_175" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m305.85,525.84c-3.02,-0.38 -42.58,-15.55 -50.84,-18.77c-8.26,-3.22 -12.52,6.76 -4.88,10.77c7.64,4.01 43.12,15.9 54.07,19.03" fill="#000000" id="svg_176" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m295.79,553.97c-3.02,-2.45 -42.77,-15.18 -50.47,-17.64c-7.69,-2.46 -11.58,7.33 -3.18,10.21c8.39,2.88 44.06,15.71 46.72,17.52" fill="#000000" id="svg_177" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m282.56,578.25c-6.79,-1.7 -43.34,-9.89 -52.35,-12.54c-9.02,-2.65 -10.82,8.08 -2.05,10.77c8.77,2.69 40.29,9.86 51.43,12.05" fill="#000000" id="svg_178" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m277.13,603.4c-6.79,-1.13 -21.45,-3.29 -26.69,-3.86c-5.24,-0.57 -6.29,9.02 -0.35,10.21c5.94,1.18 16.51,2.5 26.15,3.37" fill="#000000" id="svg_179" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m276.53,629.49c-6.52,0.43 -17.4,0.69 -23.4,0.3c-6,-0.39 -7.61,9.78 1.72,10.21c9.34,0.43 14.59,0.36 22.99,-0.78" fill="#000000" id="svg_180" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<line fill="none" id="svg_181" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" x1="877" x2="922" y1="49" y2="49"/>
<line fill="none" fill-opacity="null" id="svg_182" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" transform="matrix(1, 0, 0, 1, 0, 0)" x1="472" x2="472" y1="254" y2="195"/>
<line fill="none" fill-opacity="null" id="svg_183" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="531" x2="531" y1="254" y2="195"/>
<line fill="none" fill-opacity="null" id="svg_184" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)" x1="628.33" x2="640.33" y1="254.33" y2="259"/>
<path d="m595,271.67c24,1.33 39.67,21 34,43c-5.67,22 -11,45.67 -15.67,63c-4.67,17.33 -14.33,26 -42.67,25.67c-28.33,-0.33 -82,-1.67 -104.33,-1.33c-22.33,0.33 -20.67,0 -26.33,-5" fill="#000000" fill-opacity="0" id="svg_185" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<line fill="none" fill-opacity="0" id="svg_186" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)" x1="442.67" x2="643" y1="409.33" y2="410.33"/>
<path d="m446,402c0,0 -0.67,6 -3.33,8.33c-2.67,2.33 -8.33,5 -20,3.33" fill="#000000" fill-opacity="0" id="svg_188" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m576.67,441l13.33,-4.33l3.67,1.33c0,0 -0.67,8.33 -8.67,29.33c-8,21 -11.67,26.33 -11.67,26.33c0,0 -4.33,-2 -4.33,-2c0,0 -7.33,-11.33 -7.33,-11.33c0,0 15,-39.33 15,-39.33z" fill="#000000" id="svg_189" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m589,438c-0.67,3 -2.67,10.67 -8.67,27c-6,16.33 -9.33,22.67 -12,26.67" fill="#000000" fill-opacity="0" id="svg_190" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<line fill="none" fill-opacity="null" id="svg_195" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" x1="730" x2="753" y1="438" y2="438"/>
<line fill="none" fill-opacity="null" id="svg_196" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" x1="730" x2="753" y1="445" y2="445"/>
<line fill="none" fill-opacity="null" id="svg_197" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" x1="730" x2="753" y1="452" y2="452"/>
<line fill="none" fill-opacity="null" id="svg_198" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" x1="730" x2="753" y1="459" y2="459"/>
<line fill="none" fill-opacity="null" id="svg_199" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" x1="730" x2="753" y1="466" y2="466"/>
<line fill="none" fill-opacity="null" id="svg_200" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)" x1="730" x2="753" y1="473" y2="473"/>
<line fill="none" fill-opacity="null" id="svg_201" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" x1="730" x2="753" y1="480" y2="480"/>
<line fill="none" fill-opacity="null" id="svg_202" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" x1="730" x2="753" y1="487" y2="487"/>
<line fill="none" fill-opacity="null" id="svg_203" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" x1="730" x2="753" y1="494" y2="494"/>
<line fill="none" fill-opacity="null" id="svg_204" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)" x1="730" x2="753" y1="501" y2="501"/>
<line fill="none" fill-opacity="null" id="svg_205" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)" x1="895.71" x2="896.57" y1="305.43" y2="310.29"/>
<path d="m629.45,441.46c4.86,0.27 77.98,0.58 82.57,0.58c4.59,0 6.63,-0.4 6.9,5.82c0.27,6.22 -0.27,31.76 -0.27,36.89c0,5.14 -3.41,3.9 -8.55,4.17c-5.14,0.27 -92.8,0.06 -97.67,0.06c-4.86,0 -5.28,-1.74 -2.84,-9.31c2.43,-7.57 9.67,-28.24 11.3,-32.57c1.62,-4.32 3.46,-5.93 8.32,-5.66l0.24,0.01l0,0.01z" fill="#000000" fill-opacity="0" id="svg_206" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m694.63,234.39c10.27,0.27 202.69,-0.54 210.53,-0.27c7.84,0.27 11.55,0.24 8.85,9.43c-2.7,9.19 -4.51,15.44 -5.32,16.79c-0.81,1.35 0.94,2.36 -6.9,2.91c-7.84,0.54 -212.77,0.54 -218.45,1.08c-5.68,0.54 -10.04,0.41 -7.34,-6.35c2.7,-6.76 5.14,-14.55 7.03,-19.42c1.89,-4.86 1.33,-4.44 11.6,-4.17z" fill="#000000" fill-opacity="0" id="svg_207" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m413.59,65.48l-0.26,41.14c0.24,1.33 0,11 11.43,12.43" fill="#000000" fill-opacity="0" id="svg_211" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m561.96,69.74l-0.2,38.46c0,1.82 -1.45,9.42 -11.84,10.85" fill="#000000" fill-opacity="0" id="svg_212" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m377.71,372.57c8.29,4 11.14,14 10.57,15.14c-0.57,1.14 -3.14,7.14 -3.14,7.14c0,0 -12,-1.14 -13.43,-1.43" fill="#000000" fill-opacity="0" id="svg_215" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m359.43,343.14c-0.86,-20 -0.57,-36.29 13.14,-56.29c13.71,-20 36.29,-31.14 61.71,-32" fill="#000000" fill-opacity="0" id="svg_217" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m659.95,294.95l-2.05,5.84c-1.37,4.16 -3.47,9.68 -6.63,18.89c-3.16,9.21 2.11,11.05 7.89,11.05c5.79,0 72.89,-0.53 81.32,-0.53c8.42,0 10.26,-3.95 11.84,-8.42c1.58,-4.47 6.58,-21.05 7.95,-26.79" fill="#000000" fill-opacity="0" id="svg_1" stroke="#ff0000" stroke-width="3"/>
<path d="m664.68,296c-3.16,9.21 -5.26,13.95 -8.42,23.16c-3.16,9.21 2.11,6.84 7.89,6.84c5.79,0 65.79,0 74.21,0c8.42,0 8.95,-4.21 10.53,-8.68c1.58,-4.47 5,-15.79 6.37,-21.53" fill="#000000" fill-opacity="0" id="svg_7" stroke="#ff0000" stroke-width="3"/>
<path d="m643.11,294.95l146.63,-0.47c0.79,0.79 6.79,-7.16 12.58,-11.11c5.79,-3.95 14.47,-6.58 24.47,-6.58c10,0 19.21,2.89 25.53,7.37c6.32,4.47 10.79,10.53 10.58,10.32c-0.21,-0.21 66,0.47 65.79,0.26" fill="#000000" fill-opacity="0" id="svg_19" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<line fill="none" fill-opacity="0" id="svg_49" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="671.26" x2="661.26" y1="295.47" y2="325.21"/>
<line fill="none" fill-opacity="0" id="svg_51" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="677.26" x2="667.26" y1="295.47" y2="325.21"/>
<line fill="none" fill-opacity="0" id="svg_66" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="683.26" x2="673.26" y1="295.47" y2="325.21"/>
<line fill="none" fill-opacity="0" id="svg_74" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="689.26" x2="679.26" y1="295.47" y2="325.21"/>
<line fill="none" fill-opacity="0" id="svg_75" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="695.26" x2="685.26" y1="295.47" y2="325.21"/>
<line fill="none" fill-opacity="0" id="svg_78" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="701.26" x2="691.26" y1="295.47" y2="325.21"/>
<line fill="none" fill-opacity="0" id="svg_107" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="707.26" x2="697.26" y1="295.47" y2="325.21"/>
<line fill="none" fill-opacity="0" id="svg_187" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="713.26" x2="703.26" y1="295.47" y2="325.21"/>
<line fill="none" fill-opacity="0" id="svg_208" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="719.26" x2="709.26" y1="295.47" y2="325.21"/>
<line fill="none" fill-opacity="0" id="svg_209" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="725.26" x2="715.26" y1="295.47" y2="325.21"/>
<line fill="none" fill-opacity="0" id="svg_210" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="731.26" x2="721.26" y1="295.47" y2="325.21"/>
<line fill="none" fill-opacity="0" id="svg_213" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="737.26" x2="727.26" y1="295.47" y2="325.21"/>
<line fill="none" fill-opacity="0" id="svg_214" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="743.26" x2="733.26" y1="295.47" y2="325.21"/>
<line fill="none" fill-opacity="0" id="svg_216" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="749.26" x2="739.26" y1="295.47" y2="325.21"/>
<rect fill="#000000" height="18" id="svg_218" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" width="62.5" x="671" y="302"/>
<line fill="none" fill-opacity="0" id="svg_232" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" transform="matrix(1, 0, 0, 1, 0, 0)" x1="689.5" x2="679.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_231" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" transform="matrix(1, 0, 0, 1, 0, 0)" x1="695.5" x2="685.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_230" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" transform="matrix(1, 0, 0, 1, 0, 0)" x1="701.5" x2="691.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_229" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="707.5" x2="697.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_228" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" transform="matrix(1, 0, 0, 1, 0, 0)" x1="713.5" x2="703.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_227" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" transform="matrix(1, 0, 0, 1, 0, 0)" x1="719.5" x2="709.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_226" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" transform="matrix(1, 0, 0, 1, 0, 0)" x1="725.5" x2="715.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_225" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" transform="matrix(1, 0, 0, 1, 0, 0)" x1="731.5" x2="721.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_224" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" transform="matrix(1, 0, 0, 1, 0, 0)" x1="737.5" x2="727.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_223" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" transform="matrix(1, 0, 0, 1, 0, 0)" x1="743.5" x2="733.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_222" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" transform="matrix(1, 0, 0, 1, 0, 0)" x1="749.5" x2="739.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_221" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" transform="matrix(1, 0, 0, 1, 0, 0)" x1="755.5" x2="745.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_220" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" transform="matrix(1, 0, 0, 1, 0, 0)" x1="761.5" x2="751.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_219" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="767.5" x2="757.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_234" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="773.5" x2="763.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_235" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="779.5" x2="769.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_236" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="785.5" x2="775.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_237" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="791.5" x2="781.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_238" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="797.5" x2="787.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_239" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="803.5" x2="793.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_240" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="809.5" x2="799.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_241" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="815.5" x2="805.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_242" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="821.5" x2="811.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_243" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="827.5" x2="817.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_244" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="833.5" x2="823.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_245" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="839.5" x2="829.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_246" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="845.5" x2="835.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_247" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="851.5" x2="841.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_248" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="857.5" x2="847.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_249" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="863.5" x2="853.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_250" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="869.5" x2="859.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_251" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="875.5" x2="865.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_252" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="881.5" x2="871.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_253" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="887.5" x2="877.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_254" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="893.5" x2="883.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_255" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="899.5" x2="889.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_256" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="905.5" x2="895.5" y1="234.13" y2="263.86"/>
<line fill="none" fill-opacity="0" id="svg_257" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" transform="matrix(1, 0, 0, 1, 0, 0)" x1="911.5" x2="901.5" y1="234.13" y2="263.86"/>
<rect fill="#000000" height="18" id="svg_258" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" width="34" x="788" y="240.5"/>
<rect fill="#000000" height="18" id="svg_259" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" width="34" x="868" y="240.5"/>
<rect fill="#000000" height="13" id="svg_260" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" width="13" x="790.5" y="243"/>
<rect fill="#000000" height="13" id="svg_261" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" width="13" x="870.5" y="243"/>
<rect fill="#000000" height="13" id="svg_262" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" width="13" x="718" y="304.5"/>
<line fill="none" fill-opacity="0" id="svg_263" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="627.5" x2="612" y1="441" y2="488"/>
<line fill="none" fill-opacity="0" id="svg_264" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="632.5" x2="617" y1="441" y2="488"/>
<line fill="none" fill-opacity="0" id="svg_265" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="637.5" x2="622" y1="441" y2="488"/>
<line fill="none" fill-opacity="0" id="svg_266" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="642.5" x2="627" y1="441" y2="488"/>
<line fill="none" fill-opacity="0" id="svg_267" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="647.5" x2="632" y1="441" y2="488"/>
<line fill="none" fill-opacity="0" id="svg_268" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="652.5" x2="637" y1="441" y2="488"/>
<line fill="none" fill-opacity="0" id="svg_269" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="657.5" x2="642" y1="441" y2="488"/>
<line fill="none" fill-opacity="0" id="svg_270" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="662.5" x2="647" y1="441" y2="488"/>
<line fill="none" fill-opacity="0" id="svg_271" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="667.5" x2="652" y1="441" y2="488"/>
<line fill="none" fill-opacity="0" id="svg_272" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="672.5" x2="657" y1="441" y2="488"/>
<line fill="none" fill-opacity="0" id="svg_273" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="677.5" x2="662" y1="441" y2="488"/>
<line fill="none" fill-opacity="0" id="svg_274" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="682.5" x2="667" y1="441" y2="488"/>
<line fill="none" fill-opacity="0" id="svg_275" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="687.5" x2="672" y1="441" y2="488"/>
<line fill="none" fill-opacity="0" id="svg_276" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="692.5" x2="677" y1="441" y2="488"/>
<line fill="none" fill-opacity="0" id="svg_277" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="697.5" x2="682" y1="441" y2="488"/>
<line fill="none" fill-opacity="0" id="svg_278" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="702.5" x2="687" y1="441" y2="488"/>
<line fill="none" fill-opacity="0" id="svg_279" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="707.5" x2="692" y1="441" y2="488"/>
<line fill="none" fill-opacity="0" id="svg_280" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="712.5" x2="697" y1="441" y2="488"/>
<line fill="none" fill-opacity="0" id="svg_281" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="717.5" x2="702" y1="441" y2="488"/>
<line fill="none" fill-opacity="0" id="svg_282" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="719.5" x2="707" y1="451" y2="488"/>
<line fill="none" fill-opacity="0" id="svg_283" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" x1="719.5" x2="712" y1="466" y2="488"/>
<ellipse cx="618.5" cy="534.5" fill="#000000" id="svg_285" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="656.5" cy="534.5" fill="#000000" id="svg_286" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="693.5" cy="534.5" fill="#000000" id="svg_287" rx="2.5" ry="2.5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<line fill="none" fill-opacity="null" id="svg_288" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)" x1="711" x2="763" y1="531.2" y2="531.21"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 74 KiB

View File

@ -1,6 +1,7 @@
pfiles = [
'controller_mask.png',
'controller_mask_s.png',
'light_gun_mask.png',
'xmu_mask.png',
'logo_sdf.png',
'xemu_64x64.png',

View File

@ -15,6 +15,8 @@ specific_ss.add(files(
'xbox.c',
'xbox_pci.c',
'xid.c',
'xid-gamepad.c',
'xid-light-gun.c',
'xblc.c',
'xid-gamepad.c',
))

434
hw/xbox/xid-light-gun.c Normal file
View File

@ -0,0 +1,434 @@
/*
* QEMU USB XID Devices
*
* Copyright (c) 2013 espes
* Copyright (c) 2017 Jannik Vogel
* Copyright (c) 2018-2021 Matt Borgerson
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include "xid.h"
#define USB_VENDOR_MICROSOFT 0x045e
#define LIGHT_GUN_IN_ENDPOINT_ID 0x02
#define LIGHT_GUN_OUT_ENDPOINT_ID 0x02
#define USB_XID(obj) \
OBJECT_CHECK(USBXIDLightGunState, (obj), TYPE_USB_XID_LIGHT_GUN)
typedef struct XIDLightGunReport {
uint8_t bReportId;
uint8_t bLength;
uint8_t wButtons;
uint8_t wState;
uint8_t bAnalogButtons[8]; // The last 2 are unused
int16_t sThumbLX;
int16_t sThumbLY;
} QEMU_PACKED XIDLightGunReport;
typedef struct XIDLightGunCalibrationReport {
uint8_t bReportId;
uint8_t bLength;
int16_t sCenterCalibrationX;
int16_t sCenterCalibrationY;
int16_t sTopLeftCalibrationX;
int16_t sTopLeftCalibrationY;
} QEMU_PACKED XIDLightGunCalibrationReport;
typedef struct USBXIDLightGunState {
USBDevice dev;
USBEndpoint *intr;
const XIDDesc *xid_desc;
XIDLightGunReport in_state;
XIDLightGunReport in_state_capabilities;
XIDLightGunCalibrationReport out_state;
XIDLightGunCalibrationReport out_state_capabilities;
uint8_t device_index;
} USBXIDLightGunState;
static const USBDescIface desc_iface_xbox_light_gun = {
.bInterfaceNumber = 0,
.bNumEndpoints = 2,
.bInterfaceClass = USB_CLASS_XID,
.bInterfaceSubClass = 0x42,
.bInterfaceProtocol = 0x00,
.eps =
(USBDescEndpoint[]){
{
.bEndpointAddress = USB_DIR_IN | LIGHT_GUN_IN_ENDPOINT_ID,
.bmAttributes = USB_ENDPOINT_XFER_INT,
.wMaxPacketSize = 0x20,
.bInterval = 4,
},
{
.bEndpointAddress = USB_DIR_OUT | LIGHT_GUN_OUT_ENDPOINT_ID,
.bmAttributes = USB_ENDPOINT_XFER_INT,
.wMaxPacketSize = 0x20,
.bInterval = 4,
},
},
};
static const USBDescDevice desc_device_xbox_light_gun = {
.bcdUSB = 0x0110,
.bMaxPacketSize0 = 0x40,
.bNumConfigurations = 1,
.confs =
(USBDescConfig[]){
{
.bNumInterfaces = 1,
.bConfigurationValue = 1,
.bmAttributes = USB_CFG_ATT_ONE,
.bMaxPower = 50,
.nif = 1,
.ifs = &desc_iface_xbox_light_gun,
},
},
};
static const USBDesc desc_xbox_light_gun = {
.id = {
.idVendor = USB_VENDOR_MICROSOFT,
.idProduct = 0x0202,
.bcdDevice = 0x0100,
.iManufacturer = STR_MANUFACTURER,
.iProduct = STR_PRODUCT,
.iSerialNumber = STR_SERIALNUMBER,
},
.full = &desc_device_xbox_light_gun,
.str = desc_strings,
};
static const XIDDesc desc_xid_xbox_light_gun = {
.bLength = 0x10,
.bDescriptorType = USB_DT_XID,
.bcdXid = 0x100,
.bType = XID_DEVICETYPE_GAMEPAD,
.bSubType = XID_DEVICESUBTYPE_LIGHT_GUN,
.bMaxInputReportSize = 20,
.bMaxOutputReportSize = 6,
.wAlternateProductIds = { 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF },
};
static void update_lg_input(USBXIDLightGunState *s)
{
if (xemu_input_get_test_mode()) {
// Don't report changes if we are testing the controller while running
return;
}
ControllerState *state = xemu_input_get_bound(s->device_index);
// assert(state);
if (state == NULL)
return;
xemu_input_update_controller(state);
s->in_state.wButtons = 0;
if (state->lg.buttons & CONTROLLER_BUTTON_DPAD_UP)
s->in_state.wButtons |= 0x01;
if (state->lg.buttons & CONTROLLER_BUTTON_DPAD_DOWN)
s->in_state.wButtons |= 0x02;
if (state->lg.buttons & CONTROLLER_BUTTON_DPAD_LEFT)
s->in_state.wButtons |= 0x04;
if (state->lg.buttons & CONTROLLER_BUTTON_DPAD_RIGHT)
s->in_state.wButtons |= 0x08;
if (state->lg.buttons & CONTROLLER_BUTTON_START)
s->in_state.wButtons |= 0x10;
if (state->lg.buttons & CONTROLLER_BUTTON_BACK)
s->in_state.wButtons |= 0x20;
s->in_state.wState = state->lg.status;
s->in_state.bAnalogButtons[0] =
(state->lg.buttons & CONTROLLER_BUTTON_A) ? 0xFF : 0x00;
s->in_state.bAnalogButtons[1] =
(state->lg.buttons & CONTROLLER_BUTTON_B) ? 0xFF : 0x00;
s->in_state.bAnalogButtons[2] =
(state->lg.buttons & CONTROLLER_BUTTON_X) ? 0xFF : 0x00;
s->in_state.bAnalogButtons[3] =
(state->lg.buttons & CONTROLLER_BUTTON_Y) ? 0xFF : 0x00;
s->in_state.bAnalogButtons[4] =
(state->lg.buttons & CONTROLLER_BUTTON_BLACK) ? 0xFF : 0x00;
s->in_state.bAnalogButtons[5] =
(state->lg.buttons & CONTROLLER_BUTTON_WHITE) ? 0xFF : 0x00;
s->in_state.sThumbLX = state->lg.axis[0];
s->in_state.sThumbLY = state->lg.axis[1];
}
static void update_lg_output(USBXIDLightGunState *s)
{
if (s->out_state.bLength == 6) {
// Rumble Data, do nothing (for now)
} else if (s->out_state.bLength == 10) {
ControllerState *state = xemu_input_get_bound(s->device_index);
assert(state);
xemu_input_update_controller(state);
// Calibration Data
s->out_state.sTopLeftCalibrationX =
-25000 - s->out_state.sTopLeftCalibrationX;
s->out_state.sTopLeftCalibrationY =
25000 - s->out_state.sTopLeftCalibrationY;
state->lg.offsetX = s->out_state.sCenterCalibrationX;
state->lg.offsetY = s->out_state.sCenterCalibrationY;
state->lg.scaleX = 25000.0f / (s->out_state.sCenterCalibrationX -
s->out_state.sTopLeftCalibrationX);
state->lg.scaleY = 25000.0f / (s->out_state.sTopLeftCalibrationY -
s->out_state.sCenterCalibrationY);
}
}
static void usb_xid_light_gun_handle_control(USBDevice *dev, USBPacket *p,
int request, int value, int index,
int length, uint8_t *data)
{
USBXIDLightGunState *s = DO_UPCAST(USBXIDLightGunState, dev, dev);
DPRINTF("xid light_gun handle_control 0x%x 0x%x\n", request, value);
int ret =
usb_desc_handle_control(dev, p, request, value, index, length, data);
if (ret >= 0) {
DPRINTF("xid handled by usb_desc_handle_control: %d\n", ret);
return;
}
switch (request) {
/* HID requests */
case ClassInterfaceRequest | HID_GET_REPORT:
DPRINTF("xid GET_REPORT 0x%x\n", value);
update_lg_input(s);
if (value == 0x0100) { /* input */
if (length <= s->in_state.bLength) {
memcpy(data, &s->in_state, s->in_state.bLength);
p->actual_length = length;
} else {
p->status = USB_RET_STALL;
}
} else {
p->status = USB_RET_STALL;
assert(false);
}
break;
case ClassInterfaceOutRequest | HID_SET_REPORT:
DPRINTF("xid SET_REPORT 0x%x\n", value);
if (value == 0x0200) { /* output */
/* Read length, then the entire packet */
if (length == sizeof(XIDGamepadOutputReport)) {
memcpy(&s->out_state, data, sizeof(XIDGamepadOutputReport));
/* FIXME: This should also be a STALL */
assert(s->out_state.bLength == sizeof(XIDGamepadOutputReport));
p->actual_length = length;
} else {
p->status = USB_RET_STALL;
}
update_lg_output(s);
} else if (value == 0x0201) { /* light gun calibration */
if (length == sizeof(XIDLightGunCalibrationReport)) {
memcpy(&s->out_state, data,
sizeof(XIDLightGunCalibrationReport));
DPRINTF("xid Light Gun Calibration Data: %d, %d, %d, %d\n",
s->out_state.sCenterCalibrationX,
s->out_state.sCenterCalibrationY,
s->out_state.sTopLeftCalibrationX,
s->out_state.sTopLeftCalibrationY);
/* FIXME: This should also be a STALL */
assert(s->out_state.bLength ==
sizeof(XIDLightGunCalibrationReport));
p->actual_length = length;
} else {
p->status = USB_RET_STALL;
}
update_lg_output(s);
} else {
p->status = USB_RET_STALL;
assert(false);
}
break;
/* XID requests */
case VendorInterfaceRequest | USB_REQ_GET_DESCRIPTOR:
DPRINTF("xid GET_DESCRIPTOR 0x%x\n", value);
if (value == 0x4200) {
assert(s->xid_desc->bLength <= length);
memcpy(data, s->xid_desc, s->xid_desc->bLength);
p->actual_length = s->xid_desc->bLength;
} else {
p->status = USB_RET_STALL;
assert(false);
}
break;
case VendorInterfaceRequest | XID_GET_CAPABILITIES:
DPRINTF("xid XID_GET_CAPABILITIES 0x%x\n", value);
if (value == 0x0100) {
if (length > s->in_state_capabilities.bLength) {
length = s->in_state_capabilities.bLength;
}
memcpy(data, &s->in_state_capabilities, length);
p->actual_length = length;
} else if (value == 0x0200) {
if (length > s->out_state_capabilities.bLength) {
length = s->out_state_capabilities.bLength;
}
memcpy(data, &s->out_state_capabilities, length);
p->actual_length = length;
} else {
p->status = USB_RET_STALL;
assert(false);
}
break;
case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_DEVICE) << 8) |
USB_REQ_GET_DESCRIPTOR:
/* FIXME: ! */
DPRINTF("xid unknown xpad request 0x%x: value = 0x%x\n", request,
value);
memset(data, 0x00, length);
// FIXME: Intended for the hub: usbd_get_hub_descriptor, UT_READ_CLASS?!
p->status = USB_RET_STALL;
// assert(false);
break;
case ((USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_ENDPOINT) << 8) |
USB_REQ_CLEAR_FEATURE:
/* FIXME: ! */
DPRINTF("xid unknown xpad request 0x%x: value = 0x%x\n", request,
value);
memset(data, 0x00, length);
p->status = USB_RET_STALL;
break;
default:
DPRINTF("xid USB stalled on request 0x%x value 0x%x\n", request, value);
p->status = USB_RET_STALL;
assert(false);
break;
}
}
static void usb_xid_light_gun_handle_data(USBDevice *dev, USBPacket *p)
{
USBXIDLightGunState *s = DO_UPCAST(USBXIDLightGunState, dev, dev);
DPRINTF("xid light_gun handle_gamepad_data 0x%x %d 0x%zx\n", p->pid,
p->ep->nr, p->iov.size);
switch (p->pid) {
case USB_TOKEN_IN:
if (p->ep->nr == LIGHT_GUN_IN_ENDPOINT_ID) {
update_lg_input(s);
usb_packet_copy(p, &s->in_state, s->in_state.bLength);
} else {
assert(false);
}
break;
case USB_TOKEN_OUT:
if (p->ep->nr == LIGHT_GUN_OUT_ENDPOINT_ID) {
usb_packet_copy(p, &s->out_state, s->out_state.bLength);
update_lg_output(s);
} else {
assert(false);
}
break;
default:
p->status = USB_RET_STALL;
assert(false);
break;
}
}
static void usb_xid_light_gun_class_initfn(ObjectClass *klass, void *data)
{
USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
uc->handle_reset = usb_xid_handle_reset;
uc->handle_control = usb_xid_light_gun_handle_control;
uc->handle_data = usb_xid_light_gun_handle_data;
// uc->handle_destroy = usb_xid_handle_destroy;
uc->handle_attach = usb_desc_attach;
}
static void usb_xbox_light_gun_realize(USBDevice *dev, Error **errp)
{
USBXIDLightGunState *s = USB_XID(dev);
usb_desc_create_serial(dev);
usb_desc_init(dev);
s->intr = usb_ep_get(dev, USB_TOKEN_IN, 2);
s->in_state.bLength = sizeof(s->in_state);
s->in_state.bReportId = 0;
s->out_state.bLength = sizeof(s->out_state);
s->out_state.bReportId = 0;
s->xid_desc = &desc_xid_xbox_light_gun;
memset(&s->in_state_capabilities, 0xFF, sizeof(s->in_state_capabilities));
s->in_state_capabilities.bLength = sizeof(s->in_state_capabilities);
s->in_state_capabilities.bReportId = 0;
memset(&s->out_state_capabilities, 0xFF, sizeof(s->out_state_capabilities));
s->out_state_capabilities.bLength = sizeof(s->out_state_capabilities);
s->out_state_capabilities.bReportId = 0;
}
static Property xid_properties[] = {
DEFINE_PROP_UINT8("index", USBXIDLightGunState, device_index, 0),
DEFINE_PROP_END_OF_LIST(),
};
static const VMStateDescription vmstate_usb_xbox = {
.name = TYPE_USB_XID_LIGHT_GUN,
.version_id = 1,
.minimum_version_id = 1,
.fields = (VMStateField[]){ VMSTATE_USB_DEVICE(dev, USBXIDLightGunState),
// FIXME
VMSTATE_END_OF_LIST() },
};
static void usb_xbox_light_gun_class_initfn(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
uc->product_desc = "Microsoft Xbox Light Gun";
uc->usb_desc = &desc_xbox_light_gun;
uc->realize = usb_xbox_light_gun_realize;
uc->unrealize = usb_xbox_gamepad_unrealize;
usb_xid_light_gun_class_initfn(klass, data);
set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
dc->vmsd = &vmstate_usb_xbox;
device_class_set_props(dc, xid_properties);
dc->desc = "Microsoft Xbox Light Gun";
}
static const TypeInfo usb_xbox_light_gun_info = {
.name = TYPE_USB_XID_LIGHT_GUN,
.parent = TYPE_USB_DEVICE,
.instance_size = sizeof(USBXIDLightGunState),
.class_init = usb_xbox_light_gun_class_initfn,
};
static void usb_xid_register_types(void)
{
type_register_static(&usb_xbox_light_gun_info);
}
type_init(usb_xid_register_types)

View File

@ -47,8 +47,8 @@ void update_output(USBXIDGamepadState *s)
ControllerState *state = xemu_input_get_bound(s->device_index);
assert(state);
state->rumble_l = s->out_state.left_actuator_strength;
state->rumble_r = s->out_state.right_actuator_strength;
state->gp.rumble_l = s->out_state.left_actuator_strength;
state->gp.rumble_r = s->out_state.right_actuator_strength;
xemu_input_update_rumble(state);
}
@ -84,23 +84,23 @@ void update_input(USBXIDGamepadState *s)
};
for (int i = 0; i < 6; i++) {
int pressed = state->buttons & button_map_analog[i][1];
int pressed = state->gp.buttons & button_map_analog[i][1];
s->in_state.bAnalogButtons[button_map_analog[i][0]] = pressed ? 0xff : 0;
}
s->in_state.wButtons = 0;
for (int i = 0; i < 8; i++) {
if (state->buttons & button_map_binary[i][1]) {
if (state->gp.buttons & button_map_binary[i][1]) {
s->in_state.wButtons |= BUTTON_MASK(button_map_binary[i][0]);
}
}
s->in_state.bAnalogButtons[GAMEPAD_LEFT_TRIGGER] = state->axis[CONTROLLER_AXIS_LTRIG] >> 7;
s->in_state.bAnalogButtons[GAMEPAD_RIGHT_TRIGGER] = state->axis[CONTROLLER_AXIS_RTRIG] >> 7;
s->in_state.sThumbLX = state->axis[CONTROLLER_AXIS_LSTICK_X];
s->in_state.sThumbLY = state->axis[CONTROLLER_AXIS_LSTICK_Y];
s->in_state.sThumbRX = state->axis[CONTROLLER_AXIS_RSTICK_X];
s->in_state.sThumbRY = state->axis[CONTROLLER_AXIS_RSTICK_Y];
s->in_state.bAnalogButtons[GAMEPAD_LEFT_TRIGGER] = state->gp.axis[CONTROLLER_AXIS_LTRIG] >> 7;
s->in_state.bAnalogButtons[GAMEPAD_RIGHT_TRIGGER] = state->gp.axis[CONTROLLER_AXIS_RTRIG] >> 7;
s->in_state.sThumbLX = state->gp.axis[CONTROLLER_AXIS_LSTICK_X];
s->in_state.sThumbLY = state->gp.axis[CONTROLLER_AXIS_LSTICK_Y];
s->in_state.sThumbRX = state->gp.axis[CONTROLLER_AXIS_RSTICK_X];
s->in_state.sThumbRY = state->gp.axis[CONTROLLER_AXIS_RSTICK_Y];
}
void usb_xid_handle_reset(USBDevice *dev)
@ -108,8 +108,8 @@ void usb_xid_handle_reset(USBDevice *dev)
DPRINTF("xid reset\n");
}
void usb_xid_handle_control(USBDevice *dev, USBPacket *p,
int request, int value, int index, int length, uint8_t *data)
void usb_xid_handle_control(USBDevice *dev, USBPacket *p, int request,
int value, int index, int length, uint8_t *data)
{
USBXIDGamepadState *s = (USBXIDGamepadState *)dev;
@ -216,7 +216,7 @@ void usb_xid_handle_control(USBDevice *dev, USBPacket *p,
}
#if 0
static void usb_xid_handle_destroy(USBDevice *dev)
void usb_xid_handle_destroy(USBDevice *dev)
{
USBXIDState *s = DO_UPCAST(USBXIDState, dev, dev);
DPRINTF("xid handle_destroy\n");

View File

@ -51,9 +51,11 @@
#define XID_DEVICESUBTYPE_GAMEPAD 0x01
#define XID_DEVICESUBTYPE_GAMEPAD_S 0x02
#define XID_DEVICESUBTYPE_LIGHT_GUN 0x50
#define TYPE_USB_XID_GAMEPAD "usb-xbox-gamepad"
#define TYPE_USB_XID_GAMEPAD_S "usb-xbox-gamepad-s"
#define TYPE_USB_XID_LIGHT_GUN "usb-xbox-light-gun"
#define GAMEPAD_A 0
#define GAMEPAD_B 1

View File

@ -34,6 +34,9 @@
#include "sysemu/blockdev.h"
extern SDL_Window *m_window;
extern int viewport_coords[4];
// #define DEBUG_INPUT
#ifdef DEBUG_INPUT
@ -90,6 +93,9 @@ const char *bound_drivers[4] = { DRIVER_DUKE, DRIVER_DUKE, DRIVER_DUKE,
DRIVER_DUKE };
int test_mode;
static int m_mouseX;
static int m_mouseY;
static const char **port_index_to_settings_key_map[] = {
&g_config.input.bindings.port1,
&g_config.input.bindings.port2,
@ -143,6 +149,8 @@ static const char *get_bound_driver(int port)
return DRIVER_DUKE;
if (strcmp(driver, DRIVER_S) == 0)
return DRIVER_S;
if (strcmp(driver, DRIVER_LIGHT_GUN) == 0)
return DRIVER_LIGHT_GUN;
return DRIVER_DUKE;
}
@ -156,7 +164,9 @@ void xemu_input_init(void)
}
if (SDL_Init(SDL_INIT_GAMECONTROLLER) < 0) {
fprintf(stderr, "Failed to initialize SDL gamecontroller subsystem: %s\n", SDL_GetError());
fprintf(stderr,
"Failed to initialize SDL gamecontroller subsystem: %s\n",
SDL_GetError());
exit(1);
}
@ -170,37 +180,63 @@ void xemu_input_init(void)
new_con->peripheral_types[1] = PERIPHERAL_NONE;
new_con->peripherals[0] = NULL;
new_con->peripherals[1] = NULL;
new_con->lg.scaleX = 1.0f;
new_con->lg.scaleY = 1.0f;
sdl_kbd_scancode_map[0] = g_config.input.keyboard_controller_scancode_map.a;
sdl_kbd_scancode_map[1] = g_config.input.keyboard_controller_scancode_map.b;
sdl_kbd_scancode_map[2] = g_config.input.keyboard_controller_scancode_map.x;
sdl_kbd_scancode_map[3] = g_config.input.keyboard_controller_scancode_map.y;
sdl_kbd_scancode_map[4] = g_config.input.keyboard_controller_scancode_map.dpad_left;
sdl_kbd_scancode_map[5] = g_config.input.keyboard_controller_scancode_map.dpad_up;
sdl_kbd_scancode_map[6] = g_config.input.keyboard_controller_scancode_map.dpad_right;
sdl_kbd_scancode_map[7] = g_config.input.keyboard_controller_scancode_map.dpad_down;
sdl_kbd_scancode_map[8] = g_config.input.keyboard_controller_scancode_map.back;
sdl_kbd_scancode_map[9] = g_config.input.keyboard_controller_scancode_map.start;
sdl_kbd_scancode_map[10] = g_config.input.keyboard_controller_scancode_map.white;
sdl_kbd_scancode_map[11] = g_config.input.keyboard_controller_scancode_map.black;
sdl_kbd_scancode_map[12] = g_config.input.keyboard_controller_scancode_map.lstick_btn;
sdl_kbd_scancode_map[13] = g_config.input.keyboard_controller_scancode_map.rstick_btn;
sdl_kbd_scancode_map[14] = g_config.input.keyboard_controller_scancode_map.guide;
sdl_kbd_scancode_map[15] = g_config.input.keyboard_controller_scancode_map.lstick_up;
sdl_kbd_scancode_map[16] = g_config.input.keyboard_controller_scancode_map.lstick_left;
sdl_kbd_scancode_map[17] = g_config.input.keyboard_controller_scancode_map.lstick_right;
sdl_kbd_scancode_map[18] = g_config.input.keyboard_controller_scancode_map.lstick_down;
sdl_kbd_scancode_map[19] = g_config.input.keyboard_controller_scancode_map.ltrigger;
sdl_kbd_scancode_map[20] = g_config.input.keyboard_controller_scancode_map.rstick_up;
sdl_kbd_scancode_map[21] = g_config.input.keyboard_controller_scancode_map.rstick_left;
sdl_kbd_scancode_map[22] = g_config.input.keyboard_controller_scancode_map.rstick_right;
sdl_kbd_scancode_map[23] = g_config.input.keyboard_controller_scancode_map.rstick_down;
sdl_kbd_scancode_map[24] = g_config.input.keyboard_controller_scancode_map.rtrigger;
sdl_kbd_scancode_map[4] =
g_config.input.keyboard_controller_scancode_map.dpad_left;
sdl_kbd_scancode_map[5] =
g_config.input.keyboard_controller_scancode_map.dpad_up;
sdl_kbd_scancode_map[6] =
g_config.input.keyboard_controller_scancode_map.dpad_right;
sdl_kbd_scancode_map[7] =
g_config.input.keyboard_controller_scancode_map.dpad_down;
sdl_kbd_scancode_map[8] =
g_config.input.keyboard_controller_scancode_map.back;
sdl_kbd_scancode_map[9] =
g_config.input.keyboard_controller_scancode_map.start;
sdl_kbd_scancode_map[10] =
g_config.input.keyboard_controller_scancode_map.white;
sdl_kbd_scancode_map[11] =
g_config.input.keyboard_controller_scancode_map.black;
sdl_kbd_scancode_map[12] =
g_config.input.keyboard_controller_scancode_map.lstick_btn;
sdl_kbd_scancode_map[13] =
g_config.input.keyboard_controller_scancode_map.rstick_btn;
sdl_kbd_scancode_map[14] =
g_config.input.keyboard_controller_scancode_map.guide;
sdl_kbd_scancode_map[15] =
g_config.input.keyboard_controller_scancode_map.lstick_up;
sdl_kbd_scancode_map[16] =
g_config.input.keyboard_controller_scancode_map.lstick_left;
sdl_kbd_scancode_map[17] =
g_config.input.keyboard_controller_scancode_map.lstick_right;
sdl_kbd_scancode_map[18] =
g_config.input.keyboard_controller_scancode_map.lstick_down;
sdl_kbd_scancode_map[19] =
g_config.input.keyboard_controller_scancode_map.ltrigger;
sdl_kbd_scancode_map[20] =
g_config.input.keyboard_controller_scancode_map.rstick_up;
sdl_kbd_scancode_map[21] =
g_config.input.keyboard_controller_scancode_map.rstick_left;
sdl_kbd_scancode_map[22] =
g_config.input.keyboard_controller_scancode_map.rstick_right;
sdl_kbd_scancode_map[23] =
g_config.input.keyboard_controller_scancode_map.rstick_down;
sdl_kbd_scancode_map[24] =
g_config.input.keyboard_controller_scancode_map.rtrigger;
for (int i = 0; i < 25; i++) {
if( (sdl_kbd_scancode_map[i] < SDL_SCANCODE_UNKNOWN) ||
(sdl_kbd_scancode_map[i] >= SDL_NUM_SCANCODES) ) {
fprintf(stderr, "WARNING: Keyboard controller map scancode out of range (%d) : Disabled\n", sdl_kbd_scancode_map[i]);
if ((sdl_kbd_scancode_map[i] < SDL_SCANCODE_UNKNOWN) ||
(sdl_kbd_scancode_map[i] >= SDL_NUM_SCANCODES)) {
fprintf(stderr,
"WARNING: Keyboard controller map scancode out of range "
"(%d) : Disabled\n",
sdl_kbd_scancode_map[i]);
sdl_kbd_scancode_map[i] = SDL_SCANCODE_UNKNOWN;
}
}
@ -215,7 +251,8 @@ void xemu_input_init(void)
if (port >= 0) {
xemu_input_bind(port, new_con, 0);
char buf[128];
snprintf(buf, sizeof(buf), "Connected '%s' to port %d", new_con->name, port+1);
snprintf(buf, sizeof(buf), "Connected '%s' to port %d", new_con->name,
port+1);
xemu_queue_notification(buf);
xemu_input_rebind_xmu(port);
}
@ -223,7 +260,8 @@ void xemu_input_init(void)
QTAILQ_INSERT_TAIL(&available_controllers, new_con, entry);
}
int xemu_input_get_controller_default_bind_port(ControllerState *state, int start)
int xemu_input_get_controller_default_bind_port(ControllerState *state,
int start)
{
char guid[35] = { 0 };
if (state->type == INPUT_DEVICE_SDL_GAMECONTROLLER) {
@ -268,28 +306,34 @@ void xemu_input_process_sdl_events(const SDL_Event *event)
SDL_GameController *sdl_con;
sdl_con = SDL_GameControllerOpen(event->cdevice.which);
if (sdl_con == NULL) {
DPRINTF("Could not open joystick %d as a game controller\n", event->cdevice.which);
DPRINTF("Could not open joystick %d as a game controller\n",
event->cdevice.which);
return;
}
// Success! Create a new node to track this controller and continue init
ControllerState *new_con = malloc(sizeof(ControllerState));
memset(new_con, 0, sizeof(ControllerState));
new_con->type = INPUT_DEVICE_SDL_GAMECONTROLLER;
new_con->name = SDL_GameControllerName(sdl_con);
new_con->rumble_enabled = true;
new_con->sdl_gamecontroller = sdl_con;
new_con->sdl_joystick = SDL_GameControllerGetJoystick(new_con->sdl_gamecontroller);
new_con->sdl_joystick_id = SDL_JoystickInstanceID(new_con->sdl_joystick);
new_con->sdl_joystick_guid = SDL_JoystickGetGUID(new_con->sdl_joystick);
new_con->bound = -1;
new_con->type = INPUT_DEVICE_SDL_GAMECONTROLLER;
new_con->name = SDL_GameControllerName(sdl_con);
new_con->gp.rumble_enabled = true;
new_con->sdl_gamecontroller = sdl_con;
new_con->sdl_joystick =
SDL_GameControllerGetJoystick(new_con->sdl_gamecontroller);
new_con->sdl_joystick_id =
SDL_JoystickInstanceID(new_con->sdl_joystick);
new_con->sdl_joystick_guid = SDL_JoystickGetGUID(new_con->sdl_joystick);
new_con->bound = -1;
new_con->peripheral_types[0] = PERIPHERAL_NONE;
new_con->peripheral_types[1] = PERIPHERAL_NONE;
new_con->peripherals[0] = NULL;
new_con->peripherals[1] = NULL;
new_con->lg.scaleX = 1.0f;
new_con->lg.scaleY = 1.0f;
char guid_buf[35] = { 0 };
SDL_JoystickGetGUIDString(new_con->sdl_joystick_guid, guid_buf, sizeof(guid_buf));
SDL_JoystickGetGUIDString(new_con->sdl_joystick_guid, guid_buf,
sizeof(guid_buf));
DPRINTF("Opened %s (%s)\n", new_con->name, guid_buf);
QTAILQ_INSERT_TAIL(&available_controllers, new_con, entry);
@ -338,7 +382,8 @@ void xemu_input_process_sdl_events(const SDL_Event *event)
if (did_bind) {
char buf[128];
snprintf(buf, sizeof(buf), "Connected '%s' to port %d", new_con->name, port+1);
snprintf(buf, sizeof(buf), "Connected '%s' to port %d",
new_con->name, port+1);
xemu_queue_notification(buf);
xemu_input_rebind_xmu(port);
}
@ -346,8 +391,9 @@ void xemu_input_process_sdl_events(const SDL_Event *event)
DPRINTF("Controller Removed: %d\n", event->cdevice.which);
int handled = 0;
ControllerState *iter, *next;
QTAILQ_FOREACH_SAFE(iter, &available_controllers, entry, next) {
if (iter->type != INPUT_DEVICE_SDL_GAMECONTROLLER) continue;
QTAILQ_FOREACH_SAFE (iter, &available_controllers, entry, next) {
if (iter->type != INPUT_DEVICE_SDL_GAMECONTROLLER)
continue;
if (iter->sdl_joystick_id == event->cdevice.which) {
DPRINTF("Device removed: %s\n", iter->name);
@ -358,7 +404,8 @@ void xemu_input_process_sdl_events(const SDL_Event *event)
// FIXME: Probably replace with a callback registration thing,
// but this works well enough for now.
char buf[128];
snprintf(buf, sizeof(buf), "Port %d disconnected", iter->bound+1);
snprintf(buf, sizeof(buf), "Port %d disconnected",
iter->bound+1);
xemu_queue_notification(buf);
// Unbind the controller, but don't save the unbinding in
@ -367,7 +414,7 @@ void xemu_input_process_sdl_events(const SDL_Event *event)
}
// Unlink
QTAILQ_REMOVE(&available_controllers, iter, entry);
QTAILQ_REMOVE (&available_controllers, iter, entry);
// Deallocate
if (iter->sdl_gamecontroller) {
@ -412,85 +459,234 @@ void xemu_input_update_controller(ControllerState *state)
void xemu_input_update_controllers(void)
{
ControllerState *iter;
QTAILQ_FOREACH(iter, &available_controllers, entry) {
QTAILQ_FOREACH (iter, &available_controllers, entry) {
xemu_input_update_controller(iter);
}
QTAILQ_FOREACH(iter, &available_controllers, entry) {
QTAILQ_FOREACH (iter, &available_controllers, entry) {
xemu_input_update_rumble(iter);
}
}
void xemu_input_update_sdl_kbd_controller_state(ControllerState *state)
{
state->buttons = 0;
memset(state->axis, 0, sizeof(state->axis));
state->gp.buttons = 0;
state->lg.buttons = 0;
memset(state->gp.axis, 0, sizeof(state->gp.axis));
memset(state->lg.axis, 0, sizeof(state->lg.axis));
const uint8_t *kbd = SDL_GetKeyboardState(NULL);
for (int i = 0; i < 15; i++) {
state->buttons |= kbd[sdl_kbd_scancode_map[i]] << i;
if (state->bound < 0)
return;
const char *bound_driver = get_bound_driver(state->bound);
if (strcmp(bound_driver, DRIVER_LIGHT_GUN) == 0) {
uint32_t mouseBtn = SDL_GetMouseState(&m_mouseX, &m_mouseY);
int32_t windowWidth, windowHeight;
SDL_GL_GetDrawableSize(
m_window, &windowWidth,
&windowHeight); // get the mouse location relative to the Viewport,
// not the Window
// Calculate the position of the mouse coordinates in [-32768,32768]
DPRINTF("[Lightgun] Window Coordinates: %d, %d\n", m_mouseX, m_mouseY);
// Check that the mouse position is within the window coordinates
if (m_mouseX >= 0 && m_mouseX <= windowWidth && m_mouseY >= 0 &&
m_mouseY <= windowHeight) {
if (viewport_coords[2] > 0 && viewport_coords[3] > 0) {
// Switch from Window coordinates to Viewport Coordinates
m_mouseX -= viewport_coords[0];
m_mouseY -= viewport_coords[1];
windowWidth = viewport_coords[2];
windowHeight = viewport_coords[3];
}
DPRINTF("[Lightgun] Viewport Coordinates: %d, %d\n", m_mouseX, m_mouseY);
int32_t x = (int32_t)((m_mouseX - (windowWidth / 2)) *
state->lg.scaleX * 65535 / windowWidth) +
state->lg.offsetX;
int32_t y = (int32_t)(((windowHeight / 2) - m_mouseY) *
state->lg.scaleY * 65535 / windowHeight) +
state->lg.offsetY;
state->lg.axis[0] = (int16_t)MIN(MAX(x, -32768), 32767);
state->lg.axis[1] = (int16_t)MIN(MAX(y, -32768), 32767);
state->lg.status = 0x20; // Light Visible
DPRINTF("[LightGun] X: %d, Y: %d", state->lg.axis[0], state->lg.axis[1]);
} else {
state->lg.status = 0x00;
}
// Left mouse button is the trigger (A), right mouse button is B
if (mouseBtn & SDL_BUTTON(SDL_BUTTON_LEFT)) {
state->lg.buttons |= CONTROLLER_BUTTON_A;
}
if (mouseBtn & SDL_BUTTON(SDL_BUTTON_RIGHT)) {
state->lg.buttons |= CONTROLLER_BUTTON_B;
}
if (kbd[g_config.input.keyboard_controller_scancode_map.a])
state->lg.buttons |= CONTROLLER_BUTTON_A;
if (kbd[g_config.input.keyboard_controller_scancode_map.b])
state->lg.buttons |= CONTROLLER_BUTTON_B;
if (kbd[g_config.input.keyboard_controller_scancode_map.x])
state->lg.buttons |= CONTROLLER_BUTTON_X;
if (kbd[g_config.input.keyboard_controller_scancode_map.y])
state->lg.buttons |= CONTROLLER_BUTTON_Y;
if (kbd[g_config.input.keyboard_controller_scancode_map.start])
state->lg.buttons |= CONTROLLER_BUTTON_START;
if (kbd[g_config.input.keyboard_controller_scancode_map.back])
state->lg.buttons |= CONTROLLER_BUTTON_BACK;
if (kbd[g_config.input.keyboard_controller_scancode_map.black])
state->lg.buttons |= CONTROLLER_BUTTON_BLACK;
if (kbd[g_config.input.keyboard_controller_scancode_map.white])
state->lg.buttons |= CONTROLLER_BUTTON_WHITE;
if (kbd[g_config.input.keyboard_controller_scancode_map.dpad_up])
state->lg.buttons |= CONTROLLER_BUTTON_DPAD_UP;
if (kbd[g_config.input.keyboard_controller_scancode_map.dpad_down])
state->lg.buttons |= CONTROLLER_BUTTON_DPAD_DOWN;
if (kbd[g_config.input.keyboard_controller_scancode_map.dpad_left])
state->lg.buttons |= CONTROLLER_BUTTON_DPAD_LEFT;
if (kbd[g_config.input.keyboard_controller_scancode_map.dpad_right])
state->lg.buttons |= CONTROLLER_BUTTON_DPAD_RIGHT;
} else {
// Update Gamepad Buttons
for (int i = 0; i < 15; i++) {
state->gp.buttons |= kbd[sdl_kbd_scancode_map[i]] << i;
}
// Update Gamepad Axes
if (kbd[sdl_kbd_scancode_map[15]])
state->gp.axis[CONTROLLER_AXIS_LSTICK_Y] = 32767;
if (kbd[sdl_kbd_scancode_map[16]])
state->gp.axis[CONTROLLER_AXIS_LSTICK_X] = -32768;
if (kbd[sdl_kbd_scancode_map[17]])
state->gp.axis[CONTROLLER_AXIS_LSTICK_X] = 32767;
if (kbd[sdl_kbd_scancode_map[18]])
state->gp.axis[CONTROLLER_AXIS_LSTICK_Y] = -32768;
if (kbd[sdl_kbd_scancode_map[19]])
state->gp.axis[CONTROLLER_AXIS_LTRIG] = 32767;
if (kbd[sdl_kbd_scancode_map[20]])
state->gp.axis[CONTROLLER_AXIS_RSTICK_Y] = 32767;
if (kbd[sdl_kbd_scancode_map[21]])
state->gp.axis[CONTROLLER_AXIS_RSTICK_X] = -32768;
if (kbd[sdl_kbd_scancode_map[22]])
state->gp.axis[CONTROLLER_AXIS_RSTICK_X] = 32767;
if (kbd[sdl_kbd_scancode_map[23]])
state->gp.axis[CONTROLLER_AXIS_RSTICK_Y] = -32768;
if (kbd[sdl_kbd_scancode_map[24]])
state->gp.axis[CONTROLLER_AXIS_RTRIG] = 32767;
}
if (kbd[sdl_kbd_scancode_map[15]]) state->axis[CONTROLLER_AXIS_LSTICK_Y] = 32767;
if (kbd[sdl_kbd_scancode_map[16]]) state->axis[CONTROLLER_AXIS_LSTICK_X] = -32768;
if (kbd[sdl_kbd_scancode_map[17]]) state->axis[CONTROLLER_AXIS_LSTICK_X] = 32767;
if (kbd[sdl_kbd_scancode_map[18]]) state->axis[CONTROLLER_AXIS_LSTICK_Y] = -32768;
if (kbd[sdl_kbd_scancode_map[19]]) state->axis[CONTROLLER_AXIS_LTRIG] = 32767;
if (kbd[sdl_kbd_scancode_map[20]]) state->axis[CONTROLLER_AXIS_RSTICK_Y] = 32767;
if (kbd[sdl_kbd_scancode_map[21]]) state->axis[CONTROLLER_AXIS_RSTICK_X] = -32768;
if (kbd[sdl_kbd_scancode_map[22]]) state->axis[CONTROLLER_AXIS_RSTICK_X] = 32767;
if (kbd[sdl_kbd_scancode_map[23]]) state->axis[CONTROLLER_AXIS_RSTICK_Y] = -32768;
if (kbd[sdl_kbd_scancode_map[24]]) state->axis[CONTROLLER_AXIS_RTRIG] = 32767;
}
void xemu_input_update_sdl_controller_state(ControllerState *state)
{
state->buttons = 0;
memset(state->axis, 0, sizeof(state->axis));
state->gp.buttons = 0;
memset(state->gp.axis, 0, sizeof(state->gp.axis));
const SDL_GameControllerButton sdl_button_map[15] = {
SDL_CONTROLLER_BUTTON_A,
SDL_CONTROLLER_BUTTON_B,
SDL_CONTROLLER_BUTTON_X,
SDL_CONTROLLER_BUTTON_Y,
SDL_CONTROLLER_BUTTON_DPAD_LEFT,
SDL_CONTROLLER_BUTTON_DPAD_UP,
SDL_CONTROLLER_BUTTON_DPAD_RIGHT,
SDL_CONTROLLER_BUTTON_DPAD_DOWN,
SDL_CONTROLLER_BUTTON_BACK,
SDL_CONTROLLER_BUTTON_START,
SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
SDL_CONTROLLER_BUTTON_LEFTSTICK,
SDL_CONTROLLER_BUTTON_RIGHTSTICK,
SDL_CONTROLLER_BUTTON_GUIDE
};
if (state->bound < 0)
return;
for (int i = 0; i < 15; i++) {
state->buttons |= SDL_GameControllerGetButton(state->sdl_gamecontroller, sdl_button_map[i]) << i;
const char *bound_driver = get_bound_driver(state->bound);
if (strcmp(bound_driver, DRIVER_LIGHT_GUN) == 0) {
if (SDL_GameControllerGetButton(state->sdl_gamecontroller,
SDL_CONTROLLER_BUTTON_B))
state->lg.buttons |= CONTROLLER_BUTTON_A;
if (SDL_GameControllerGetButton(state->sdl_gamecontroller,
SDL_CONTROLLER_BUTTON_A))
state->lg.buttons |= CONTROLLER_BUTTON_B;
if (SDL_GameControllerGetButton(state->sdl_gamecontroller,
SDL_CONTROLLER_BUTTON_X))
state->lg.buttons |= CONTROLLER_BUTTON_X;
if (SDL_GameControllerGetButton(state->sdl_gamecontroller,
SDL_CONTROLLER_BUTTON_Y))
state->lg.buttons |= CONTROLLER_BUTTON_Y;
if (SDL_GameControllerGetButton(state->sdl_gamecontroller,
SDL_CONTROLLER_BUTTON_START))
state->lg.buttons |= CONTROLLER_BUTTON_START;
if (SDL_GameControllerGetButton(state->sdl_gamecontroller,
SDL_CONTROLLER_BUTTON_BACK))
state->lg.buttons |= CONTROLLER_BUTTON_BACK;
if (SDL_GameControllerGetButton(state->sdl_gamecontroller,
SDL_CONTROLLER_BUTTON_LEFTSHOULDER))
state->lg.buttons |= CONTROLLER_BUTTON_BLACK;
if (SDL_GameControllerGetButton(state->sdl_gamecontroller,
SDL_CONTROLLER_BUTTON_RIGHTSHOULDER))
state->lg.buttons |= CONTROLLER_BUTTON_WHITE;
if (SDL_GameControllerGetButton(state->sdl_gamecontroller,
SDL_CONTROLLER_BUTTON_DPAD_UP))
state->lg.buttons |= CONTROLLER_BUTTON_DPAD_UP;
if (SDL_GameControllerGetButton(state->sdl_gamecontroller,
SDL_CONTROLLER_BUTTON_DPAD_DOWN))
state->lg.buttons |= CONTROLLER_BUTTON_DPAD_DOWN;
if (SDL_GameControllerGetButton(state->sdl_gamecontroller,
SDL_CONTROLLER_BUTTON_DPAD_LEFT))
state->lg.buttons |= CONTROLLER_BUTTON_DPAD_LEFT;
if (SDL_GameControllerGetButton(state->sdl_gamecontroller,
SDL_CONTROLLER_BUTTON_DPAD_RIGHT))
state->lg.buttons |= CONTROLLER_BUTTON_DPAD_RIGHT;
state->lg.axis[0] = SDL_GameControllerGetAxis(
state->sdl_gamecontroller, SDL_CONTROLLER_AXIS_LEFTX);
state->lg.axis[1] = SDL_GameControllerGetAxis(
state->sdl_gamecontroller, SDL_CONTROLLER_AXIS_LEFTY);
// xemu_input_print_controller_state(state);
} else {
const SDL_GameControllerButton sdl_button_map[15] = {
SDL_CONTROLLER_BUTTON_A,
SDL_CONTROLLER_BUTTON_B,
SDL_CONTROLLER_BUTTON_X,
SDL_CONTROLLER_BUTTON_Y,
SDL_CONTROLLER_BUTTON_DPAD_LEFT,
SDL_CONTROLLER_BUTTON_DPAD_UP,
SDL_CONTROLLER_BUTTON_DPAD_RIGHT,
SDL_CONTROLLER_BUTTON_DPAD_DOWN,
SDL_CONTROLLER_BUTTON_BACK,
SDL_CONTROLLER_BUTTON_START,
SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
SDL_CONTROLLER_BUTTON_LEFTSTICK,
SDL_CONTROLLER_BUTTON_RIGHTSTICK,
SDL_CONTROLLER_BUTTON_GUIDE
};
for (int i = 0; i < 15; i++) {
state->gp.buttons |=
SDL_GameControllerGetButton(state->sdl_gamecontroller,
sdl_button_map[i])
<< i;
}
const SDL_GameControllerAxis sdl_axis_map[6] = {
SDL_CONTROLLER_AXIS_TRIGGERLEFT, SDL_CONTROLLER_AXIS_TRIGGERRIGHT,
SDL_CONTROLLER_AXIS_LEFTX, SDL_CONTROLLER_AXIS_LEFTY,
SDL_CONTROLLER_AXIS_RIGHTX, SDL_CONTROLLER_AXIS_RIGHTY,
};
for (int i = 0; i < 6; i++) {
state->gp.axis[i] = SDL_GameControllerGetAxis(
state->sdl_gamecontroller, sdl_axis_map[i]);
}
// FIXME: Check range
state->gp.axis[CONTROLLER_AXIS_LSTICK_Y] =
-1 - state->gp.axis[CONTROLLER_AXIS_LSTICK_Y];
state->gp.axis[CONTROLLER_AXIS_RSTICK_Y] =
-1 - state->gp.axis[CONTROLLER_AXIS_RSTICK_Y];
// xemu_input_print_controller_state(state);
}
const SDL_GameControllerAxis sdl_axis_map[6] = {
SDL_CONTROLLER_AXIS_TRIGGERLEFT, SDL_CONTROLLER_AXIS_TRIGGERRIGHT,
SDL_CONTROLLER_AXIS_LEFTX, SDL_CONTROLLER_AXIS_LEFTY,
SDL_CONTROLLER_AXIS_RIGHTX, SDL_CONTROLLER_AXIS_RIGHTY,
};
for (int i = 0; i < 6; i++) {
state->axis[i] = SDL_GameControllerGetAxis(state->sdl_gamecontroller, sdl_axis_map[i]);
}
// FIXME: Check range
state->axis[CONTROLLER_AXIS_LSTICK_Y] = -1 - state->axis[CONTROLLER_AXIS_LSTICK_Y];
state->axis[CONTROLLER_AXIS_RSTICK_Y] = -1 - state->axis[CONTROLLER_AXIS_RSTICK_Y];
// xemu_input_print_controller_state(state);
}
void xemu_input_update_rumble(ControllerState *state)
{
if (!state->rumble_enabled) {
if (!state->gp.rumble_enabled) {
return;
}
@ -500,7 +696,8 @@ void xemu_input_update_rumble(ControllerState *state)
return;
}
SDL_GameControllerRumble(state->sdl_gamecontroller, state->rumble_l, state->rumble_r, 250);
SDL_GameControllerRumble(state->sdl_gamecontroller, state->gp.rumble_l,
state->gp.rumble_r, 250);
state->last_rumble_updated_ts = qemu_clock_get_us(QEMU_CLOCK_REALTIME);
}
@ -547,7 +744,8 @@ void xemu_input_bind(int index, ControllerState *state, int save)
char guid_buf[35] = { 0 };
if (state) {
if (state->type == INPUT_DEVICE_SDL_GAMECONTROLLER) {
SDL_JoystickGetGUIDString(state->sdl_joystick_guid, guid_buf, sizeof(guid_buf));
SDL_JoystickGetGUIDString(state->sdl_joystick_guid, guid_buf,
sizeof(guid_buf));
} else if (state->type == INPUT_DEVICE_SDL_KEYBOARD) {
snprintf(guid_buf, sizeof(guid_buf), "keyboard");
}
@ -568,18 +766,22 @@ void xemu_input_bind(int index, ControllerState *state, int save)
bound_controllers[index]->bound = index;
char *tmp;
QDict *usbhub_qdict = NULL;
DeviceState *usbhub_dev = NULL;
// Create controller's internal USB hub.
QDict *usbhub_qdict = qdict_new();
usbhub_qdict = qdict_new();
qdict_put_str(usbhub_qdict, "driver", "usb-hub");
tmp = g_strdup_printf("1.%d", port_map[index]);
qdict_put_str(usbhub_qdict, "port", tmp);
qdict_put_int(usbhub_qdict, "ports", 3);
QemuOpts *usbhub_opts = qemu_opts_from_qdict(qemu_find_opts("device"), usbhub_qdict, &error_abort);
DeviceState *usbhub_dev = qdev_device_add(usbhub_opts, &error_abort);
QemuOpts *usbhub_opts = qemu_opts_from_qdict(
qemu_find_opts("device"), usbhub_qdict, &error_abort);
usbhub_dev = qdev_device_add(usbhub_opts, &error_abort);
g_free(tmp);
// Create XID controller. This is connected to Port 1 of the controller's internal USB Hub
// Create XID controller. This is connected to Port 1 of the
// controller's internal USB Hub
QDict *qdict = qdict_new();
// Specify device driver
@ -598,7 +800,8 @@ void xemu_input_bind(int index, ControllerState *state, int save)
g_free(tmp);
// Create the device
QemuOpts *opts = qemu_opts_from_qdict(qemu_find_opts("device"), qdict, &error_abort);
QemuOpts *opts =
qemu_opts_from_qdict(qemu_find_opts("device"), qdict, &error_abort);
DeviceState *dev = qdev_device_add(opts, &error_abort);
assert(dev);

View File

@ -32,9 +32,11 @@
#define DRIVER_DUKE "usb-xbox-gamepad"
#define DRIVER_S "usb-xbox-gamepad-s"
#define DRIVER_LIGHT_GUN "usb-xbox-light-gun"
#define DRIVER_DUKE_DISPLAY_NAME "Xbox Controller"
#define DRIVER_S_DISPLAY_NAME "Xbox Controller S"
#define DRIVER_LIGHT_GUN_DISPLAY_NAME "Light Gun"
enum controller_state_buttons_mask {
CONTROLLER_BUTTON_A = (1 << 0),
@ -78,12 +80,7 @@ typedef struct XmuState {
void *dev;
} XmuState;
typedef struct ControllerState {
QTAILQ_ENTRY(ControllerState) entry;
int64_t last_input_updated_ts;
int64_t last_rumble_updated_ts;
typedef struct GamepadState {
// Input state
uint16_t buttons;
int16_t axis[CONTROLLER_AXIS__COUNT];
@ -95,6 +92,29 @@ typedef struct ControllerState {
// Rumble state
bool rumble_enabled;
uint16_t rumble_l, rumble_r;
} GamepadState;
typedef struct LightGunState {
// Input State
uint16_t buttons;
uint8_t status;
int16_t axis[2];
// Calibration
int16_t offsetX;
int16_t offsetY;
float scaleX;
float scaleY;
} LightGunState;
typedef struct ControllerState {
QTAILQ_ENTRY(ControllerState) entry;
int64_t last_input_updated_ts;
int64_t last_rumble_updated_ts;
GamepadState gp;
LightGunState lg;
enum controller_input_device_type type;
const char *name;
@ -124,6 +144,7 @@ void xemu_input_process_sdl_events(const SDL_Event *event); // SDL_CONTROLLERDEV
void xemu_input_update_controllers(void);
void xemu_input_update_controller(ControllerState *state);
void xemu_input_update_sdl_kbd_controller_state(ControllerState *state);
void xemu_input_update_sdl_mouse_controller_state(ControllerState *state);
void xemu_input_update_sdl_controller_state(ControllerState *state);
void xemu_input_update_rumble(ControllerState *state);
ControllerState *xemu_input_get_bound(int index);
@ -132,7 +153,8 @@ bool xemu_input_bind_xmu(int player_index, int peripheral_port_index,
const char *filename, bool is_rebind);
void xemu_input_rebind_xmu(int port);
void xemu_input_unbind_xmu(int player_index, int peripheral_port_index);
int xemu_input_get_controller_default_bind_port(ControllerState *state, int start);
int xemu_input_get_controller_default_bind_port(ControllerState *state,
int start);
void xemu_save_peripheral_settings(int player_index, int peripheral_index,
int peripheral_type,
const char *peripheral_parameter);

View File

@ -112,7 +112,8 @@ static int guest_cursor;
static int guest_x, guest_y;
static SDL_Cursor *guest_sprite;
static Notifier mouse_mode_notifier;
static SDL_Window *m_window;
SDL_Window *m_window;
int viewport_coords[4];
static SDL_GLContext m_context;
// struct decal_shader *blit;

View File

@ -21,6 +21,7 @@
#include "common.hh"
#include "data/controller_mask.png.h"
#include "data/controller_mask_s.png.h"
#include "data/light_gun_mask.png.h"
#include "data/logo_sdf.png.h"
#include "data/xemu_64x64.png.h"
#include "data/xmu_mask.png.h"
@ -33,8 +34,11 @@
#include "ui/shader/xemu-logo-frag.h"
extern int viewport_coords[4];
Fbo *controller_fbo, *xmu_fbo, *logo_fbo;
GLuint g_controller_duke_tex, g_controller_s_tex, g_logo_tex, g_icon_tex, g_xmu_tex;
GLuint g_controller_duke_tex, g_controller_s_tex, g_light_gun_tex,
g_logo_tex, g_icon_tex, g_xmu_tex;
enum class ShaderType {
Blit,
@ -425,6 +429,10 @@ static const struct rect tex_items[] = {
{ 0, 0, 512, 512 } // obj_xmu
};
static const struct rect light_gun_tex_items[] = {
{ 16, 159, 462, 336 } // obj_controller
};
enum tex_item_names {
obj_controller,
obj_lstick,
@ -444,6 +452,9 @@ void InitCustomRendering(void)
LoadTextureFromMemory(controller_mask_data, controller_mask_size);
g_controller_s_tex =
LoadTextureFromMemory(controller_mask_s_data, controller_mask_s_size);
g_light_gun_tex =
LoadTextureFromMemory(light_gun_mask_data, light_gun_mask_size);
g_decal_shader = NewDecalShader(ShaderType::Mask);
controller_fbo = new Fbo(512, 512);
@ -468,7 +479,7 @@ static void RenderMeter(DecalShader *s, float x, float y, float width,
}
static void RenderDukeController(float frame_x, float frame_y, uint32_t primary_color,
uint32_t secondary_color, ControllerState *state)
uint32_t secondary_color, ControllerState *state)
{
// Location within the controller texture of masked button locations,
// relative to the origin of the controller
@ -517,12 +528,12 @@ static void RenderDukeController(float frame_x, float frame_y, uint32_t primary_
// Check to see if the guide button is pressed
const uint32_t animate_guide_button_duration = 2000;
if (state->buttons & CONTROLLER_BUTTON_GUIDE) {
state->animate_guide_button_end = now + animate_guide_button_duration;
if (state->gp.buttons & CONTROLLER_BUTTON_GUIDE) {
state->gp.animate_guide_button_end = now + animate_guide_button_duration;
}
if (now < state->animate_guide_button_end) {
t = 1.0f - (float)(state->animate_guide_button_end-now)/(float)animate_guide_button_duration;
if (now < state->gp.animate_guide_button_end) {
t = 1.0f - (float)(state->gp.animate_guide_button_end-now)/(float)animate_guide_button_duration;
float sin_wav = (1-sin(M_PI * t / 2.0f));
// Animate guide button by highlighting logo jewel and fading out over time
@ -549,7 +560,7 @@ static void RenderDukeController(float frame_x, float frame_y, uint32_t primary_
// The controller has alpha cutouts where the buttons are. Draw a surface
// behind the buttons if they are activated
for (int i = 0; i < 12; i++) {
if (state->buttons & (1 << i)) {
if (state->gp.buttons & (1 << i)) {
RenderDecal(g_decal_shader, frame_x + buttons[i].x,
frame_y + buttons[i].y, buttons[i].w, buttons[i].h, 0,
0, 1, 1, 0, 0, primary_color + 0xff);
@ -563,14 +574,14 @@ static void RenderDukeController(float frame_x, float frame_y, uint32_t primary_
float h = tex_items[obj_lstick].h;
float c_x = frame_x+lstick_ctr.x;
float c_y = frame_y+lstick_ctr.y;
float lstick_x = (float)state->axis[CONTROLLER_AXIS_LSTICK_X]/32768.0;
float lstick_y = (float)state->axis[CONTROLLER_AXIS_LSTICK_Y]/32768.0;
float lstick_x = (float)state->gp.axis[CONTROLLER_AXIS_LSTICK_X]/32768.0;
float lstick_y = (float)state->gp.axis[CONTROLLER_AXIS_LSTICK_Y]/32768.0;
RenderDecal(g_decal_shader, (int)(c_x - w / 2.0f + 10.0f * lstick_x),
(int)(c_y - h / 2.0f + 10.0f * lstick_y), w, h,
tex_items[obj_lstick].x, tex_items[obj_lstick].y, w, h,
(state->buttons & CONTROLLER_BUTTON_LSTICK) ? secondary_color :
(state->gp.buttons & CONTROLLER_BUTTON_LSTICK) ? secondary_color :
primary_color,
(state->buttons & CONTROLLER_BUTTON_LSTICK) ? primary_color :
(state->gp.buttons & CONTROLLER_BUTTON_LSTICK) ? primary_color :
secondary_color,
0);
@ -579,33 +590,33 @@ static void RenderDukeController(float frame_x, float frame_y, uint32_t primary_
h = tex_items[obj_rstick].h;
c_x = frame_x+rstick_ctr.x;
c_y = frame_y+rstick_ctr.y;
float rstick_x = (float)state->axis[CONTROLLER_AXIS_RSTICK_X]/32768.0;
float rstick_y = (float)state->axis[CONTROLLER_AXIS_RSTICK_Y]/32768.0;
float rstick_x = (float)state->gp.axis[CONTROLLER_AXIS_RSTICK_X]/32768.0;
float rstick_y = (float)state->gp.axis[CONTROLLER_AXIS_RSTICK_Y]/32768.0;
RenderDecal(g_decal_shader, (int)(c_x - w / 2.0f + 10.0f * rstick_x),
(int)(c_y - h / 2.0f + 10.0f * rstick_y), w, h,
tex_items[obj_rstick].x, tex_items[obj_rstick].y, w, h,
(state->buttons & CONTROLLER_BUTTON_RSTICK) ? secondary_color :
(state->gp.buttons & CONTROLLER_BUTTON_RSTICK) ? secondary_color :
primary_color,
(state->buttons & CONTROLLER_BUTTON_RSTICK) ? primary_color :
(state->gp.buttons & CONTROLLER_BUTTON_RSTICK) ? primary_color :
secondary_color,
0);
glBlendFunc(GL_ONE, GL_ZERO); // Don't blend, just overwrite values in buffer
// Render trigger bars
float ltrig = state->axis[CONTROLLER_AXIS_LTRIG] / 32767.0;
float rtrig = state->axis[CONTROLLER_AXIS_RTRIG] / 32767.0;
float ltrig = state->gp.axis[CONTROLLER_AXIS_LTRIG] / 32767.0;
float rtrig = state->gp.axis[CONTROLLER_AXIS_RTRIG] / 32767.0;
const uint32_t animate_trigger_duration = 1000;
if ((ltrig > 0) || (rtrig > 0)) {
state->animate_trigger_end = now + animate_trigger_duration;
state->gp.animate_trigger_end = now + animate_trigger_duration;
rumble_l = fmax(rumble_l, ltrig);
rumble_r = fmax(rumble_r, rtrig);
}
// Animate trigger alpha down after a period of inactivity
alpha = 0x80;
if (state->animate_trigger_end > now) {
t = 1.0f - (float)(state->animate_trigger_end-now)/(float)animate_trigger_duration;
if (state->gp.animate_trigger_end > now) {
t = 1.0f - (float)(state->gp.animate_trigger_end-now)/(float)animate_trigger_duration;
float sin_wav = (1-sin(M_PI * t / 2.0f));
alpha += fmin(sin_wav * 0x40, 0x80);
}
@ -619,8 +630,8 @@ static void RenderDukeController(float frame_x, float frame_y, uint32_t primary_
rtrig, primary_color + alpha, primary_color + 0xff);
// Apply rumble updates
state->rumble_l = (int)(rumble_l * (float)0xffff);
state->rumble_r = (int)(rumble_r * (float)0xffff);
state->gp.rumble_l = (int)(rumble_l * (float)0xffff);
state->gp.rumble_r = (int)(rumble_r * (float)0xffff);
glBindVertexArray(0);
glUseProgram(0);
@ -676,13 +687,13 @@ static void RenderControllerS(float frame_x, float frame_y, uint32_t primary_col
// Check to see if the guide button is pressed
const uint32_t animate_guide_button_duration = 2000;
if (state->buttons & CONTROLLER_BUTTON_GUIDE) {
state->animate_guide_button_end =
if (state->gp.buttons & CONTROLLER_BUTTON_GUIDE) {
state->gp.animate_guide_button_end =
now + animate_guide_button_duration;
}
if (now < state->animate_guide_button_end) {
t = 1.0f - (float)(state->animate_guide_button_end - now) /
if (now < state->gp.animate_guide_button_end) {
t = 1.0f - (float)(state->gp.animate_guide_button_end - now) /
(float)animate_guide_button_duration;
float sin_wav = (1 - sin(M_PI * t / 2.0f));
@ -712,7 +723,7 @@ static void RenderControllerS(float frame_x, float frame_y, uint32_t primary_col
// The controller has alpha cutouts where the buttons are. Draw a surface
// behind the buttons if they are activated
for (int i = 0; i < 12; i++) {
if (state->buttons & (1 << i)) {
if (state->gp.buttons & (1 << i)) {
RenderDecal(g_decal_shader, frame_x + buttons[i].x,
frame_y + buttons[i].y, buttons[i].w, buttons[i].h, 0,
0, 1, 1, 0, 0, primary_color + 0xff);
@ -726,15 +737,15 @@ static void RenderControllerS(float frame_x, float frame_y, uint32_t primary_col
float h = tex_items[obj_lstick].h;
float c_x = frame_x + lstick_ctr.x;
float c_y = frame_y + lstick_ctr.y;
float lstick_x = (float)state->axis[CONTROLLER_AXIS_LSTICK_X] / 32768.0;
float lstick_y = (float)state->axis[CONTROLLER_AXIS_LSTICK_Y] / 32768.0;
float lstick_x = (float)state->gp.axis[CONTROLLER_AXIS_LSTICK_X] / 32768.0;
float lstick_y = (float)state->gp.axis[CONTROLLER_AXIS_LSTICK_Y] / 32768.0;
RenderDecal(
g_decal_shader, (int)(c_x - w / 2.0f + 10.0f * lstick_x),
(int)(c_y - h / 2.0f + 10.0f * lstick_y), w, h, tex_items[obj_lstick].x,
tex_items[obj_lstick].y, w, h,
(state->buttons & CONTROLLER_BUTTON_LSTICK) ? secondary_color :
(state->gp.buttons & CONTROLLER_BUTTON_LSTICK) ? secondary_color :
primary_color,
(state->buttons & CONTROLLER_BUTTON_LSTICK) ? primary_color :
(state->gp.buttons & CONTROLLER_BUTTON_LSTICK) ? primary_color :
secondary_color,
0);
@ -743,15 +754,15 @@ static void RenderControllerS(float frame_x, float frame_y, uint32_t primary_col
h = tex_items[obj_rstick].h;
c_x = frame_x + rstick_ctr.x;
c_y = frame_y + rstick_ctr.y;
float rstick_x = (float)state->axis[CONTROLLER_AXIS_RSTICK_X] / 32768.0;
float rstick_y = (float)state->axis[CONTROLLER_AXIS_RSTICK_Y] / 32768.0;
float rstick_x = (float)state->gp.axis[CONTROLLER_AXIS_RSTICK_X] / 32768.0;
float rstick_y = (float)state->gp.axis[CONTROLLER_AXIS_RSTICK_Y] / 32768.0;
RenderDecal(
g_decal_shader, (int)(c_x - w / 2.0f + 10.0f * rstick_x),
(int)(c_y - h / 2.0f + 10.0f * rstick_y), w, h, tex_items[obj_rstick].x,
tex_items[obj_rstick].y, w, h,
(state->buttons & CONTROLLER_BUTTON_RSTICK) ? secondary_color :
(state->gp.buttons & CONTROLLER_BUTTON_RSTICK) ? secondary_color :
primary_color,
(state->buttons & CONTROLLER_BUTTON_RSTICK) ? primary_color :
(state->gp.buttons & CONTROLLER_BUTTON_RSTICK) ? primary_color :
secondary_color,
0);
@ -759,19 +770,19 @@ static void RenderControllerS(float frame_x, float frame_y, uint32_t primary_col
GL_ZERO); // Don't blend, just overwrite values in buffer
// Render trigger bars
float ltrig = state->axis[CONTROLLER_AXIS_LTRIG] / 32767.0;
float rtrig = state->axis[CONTROLLER_AXIS_RTRIG] / 32767.0;
float ltrig = state->gp.axis[CONTROLLER_AXIS_LTRIG] / 32767.0;
float rtrig = state->gp.axis[CONTROLLER_AXIS_RTRIG] / 32767.0;
const uint32_t animate_trigger_duration = 1000;
if ((ltrig > 0) || (rtrig > 0)) {
state->animate_trigger_end = now + animate_trigger_duration;
state->gp.animate_trigger_end = now + animate_trigger_duration;
rumble_l = fmax(rumble_l, ltrig);
rumble_r = fmax(rumble_r, rtrig);
}
// Animate trigger alpha down after a period of inactivity
alpha = 0x80;
if (state->animate_trigger_end > now) {
t = 1.0f - (float)(state->animate_trigger_end - now) /
if (state->gp.animate_trigger_end > now) {
t = 1.0f - (float)(state->gp.animate_trigger_end - now) /
(float)animate_trigger_duration;
float sin_wav = (1 - sin(M_PI * t / 2.0f));
alpha += fmin(sin_wav * 0x40, 0x80);
@ -786,8 +797,53 @@ static void RenderControllerS(float frame_x, float frame_y, uint32_t primary_col
rtrig, primary_color + alpha, primary_color + 0xff);
// Apply rumble updates
state->rumble_l = (int)(rumble_l * (float)0xffff);
state->rumble_r = (int)(rumble_r * (float)0xffff);
state->gp.rumble_l = (int)(rumble_l * (float)0xffff);
state->gp.rumble_r = (int)(rumble_r * (float)0xffff);
glBindVertexArray(0);
glUseProgram(0);
}
static void RenderLightGun(float frame_x, float frame_y, uint32_t primary_color,
uint32_t secondary_color, ControllerState *state)
{
// Location within the controller texture of masked button locations,
// relative to the origin of the controller
const struct rect buttons[] = {
{ 174, 156, 55, 66 }, // A
{ 385, 177, 28, 28 } // B
};
glUseProgram(g_decal_shader->prog);
glBindVertexArray(g_decal_shader->vao);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, g_light_gun_tex);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE, GL_ZERO);
// Render controller texture
RenderDecal(g_decal_shader, frame_x, frame_y,
light_gun_tex_items[obj_controller].w,
light_gun_tex_items[obj_controller].h,
light_gun_tex_items[obj_controller].x,
light_gun_tex_items[obj_controller].y,
light_gun_tex_items[obj_controller].w,
light_gun_tex_items[obj_controller].h, primary_color,
secondary_color, 0);
glBlendFunc(GL_ONE_MINUS_DST_ALPHA,
GL_ONE); // Blend with controller cutouts
// The controller has alpha cutouts where the buttons are. Draw a surface
// behind the buttons if they are activated
for (int i = 0; i < 2; i++) {
if (state->lg.buttons & (1 << i)) {
RenderDecal(g_decal_shader, frame_x + buttons[i].x,
frame_y + buttons[i].y, buttons[i].w, buttons[i].h, 0,
0, 1, 1, 0, 0, primary_color + 0xff);
}
}
glBindVertexArray(0);
glUseProgram(0);
@ -799,6 +855,8 @@ void RenderController(float frame_x, float frame_y, uint32_t primary_color,
if (strcmp(bound_drivers[state->bound], DRIVER_S) == 0)
RenderControllerS(frame_x, frame_y, primary_color, secondary_color,
state);
else if (strcmp(bound_drivers[state->bound], DRIVER_LIGHT_GUN) == 0)
RenderLightGun(frame_x, frame_y, primary_color, secondary_color, state);
else if (strcmp(bound_drivers[state->bound], DRIVER_DUKE) == 0)
RenderDukeController(frame_x, frame_y, primary_color, secondary_color,
state);
@ -933,6 +991,7 @@ void RenderFramebuffer(GLint tex, int width, int height, bool flip)
{
int tw, th;
float scale[2];
int viewport_width, viewport_height;
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex);
@ -961,6 +1020,14 @@ void RenderFramebuffer(GLint tex, int width, int height, bool flip)
}
}
viewport_width = (int)(width * scale[0]);
viewport_height = (int)(height * scale[1]);
viewport_coords[0] = (width - viewport_width) / 2;
viewport_coords[1] = (height - viewport_height) / 2;
viewport_coords[2] = viewport_width;
viewport_coords[3] = viewport_height;
RenderFramebuffer(tex, width, height, flip, scale);
}

View File

@ -12,20 +12,20 @@ InputManager::InputManager()
void InputManager::Update()
{
ImGuiIO& io = ImGui::GetIO();
ImGuiIO &io = ImGui::GetIO();
// Combine all controller states to allow any controller to navigate
m_buttons = 0;
int16_t axis[CONTROLLER_AXIS__COUNT] = {0};
int16_t axis[CONTROLLER_AXIS__COUNT] = { 0 };
ControllerState *iter;
QTAILQ_FOREACH(iter, &available_controllers, entry) {
if (iter->type != INPUT_DEVICE_SDL_GAMECONTROLLER) continue;
m_buttons |= iter->buttons;
m_buttons |= iter->gp.buttons;
// We simply take any axis that is >10 % activation
for (int i = 0; i < CONTROLLER_AXIS__COUNT; i++) {
if ((iter->axis[i] > 3276) || (iter->axis[i] < -3276)) {
axis[i] = iter->axis[i];
if ((iter->gp.axis[i] > 3276) || (iter->gp.axis[i] < -3276)) {
axis[i] = iter->gp.axis[i];
}
}
}

View File

@ -172,15 +172,19 @@ void MainMenuInputView::Draw()
driver = DRIVER_DUKE_DISPLAY_NAME;
else if (strcmp(driver, DRIVER_S) == 0)
driver = DRIVER_S_DISPLAY_NAME;
else if (strcmp(driver, DRIVER_LIGHT_GUN) == 0)
driver = DRIVER_LIGHT_GUN_DISPLAY_NAME;
ImGui::SetNextItemWidth(-FLT_MIN);
if (ImGui::BeginCombo("###InputDrivers", driver,
ImGuiComboFlags_NoArrowButton)) {
const char *available_drivers[] = { DRIVER_DUKE, DRIVER_S };
const char *available_drivers[] = { DRIVER_DUKE, DRIVER_S,
DRIVER_LIGHT_GUN };
const char *driver_display_names[] = {
DRIVER_DUKE_DISPLAY_NAME,
DRIVER_S_DISPLAY_NAME
};
DRIVER_S_DISPLAY_NAME,
DRIVER_LIGHT_GUN_DISPLAY_NAME
};
bool is_selected = false;
int num_drivers = sizeof(driver_display_names) / sizeof(driver_display_names[0]);
for (int i = 0; i < num_drivers; i++) {
@ -305,16 +309,17 @@ void MainMenuInputView::Draw()
ImGui::GetCursorPosX() +
(int)((ImGui::GetColumnWidth() - controller_display_size.x) / 2.0));
ImGui::Image(id,
controller_display_size,
ImVec2(0, controller_height/t_h),
ImVec2(controller_width/t_w, 0));
cur = ImGui::GetCursorPos();
ImGui::Image(id, controller_display_size,
ImVec2(0, controller_height / t_h),
ImVec2(controller_width / t_w, 0));
ImVec2 pos = ImGui::GetCursorPos();
if (!device_selected) {
const char *msg = "Please select an available input device";
ImVec2 dim = ImGui::CalcTextSize(msg);
ImGui::SetCursorPosX(cur.x + (controller_display_size.x-dim.x)/2);
ImGui::SetCursorPosY(cur.y + (controller_display_size.y-dim.y)/2);
ImGui::SetCursorPosX(cur.x + (controller_display_size.x - dim.x) / 2);
ImGui::SetCursorPosY(cur.y + (controller_display_size.y - dim.y) / 2);
ImGui::Text("%s", msg);
}
@ -333,20 +338,22 @@ void MainMenuInputView::Draw()
xmu_fbo->Target();
id = (ImTextureID)(intptr_t)xmu_fbo->Texture();
const char *img_file_filters = ".img Files\0*.img\0All Files\0*.*\0";
const char *img_file_filters =
".img Files\0*.img\0All Files\0*.*\0";
const char *comboLabels[2] = { "###ExpansionSlotA",
"###ExpansionSlotB" };
"###ExpansionSlotB" };
for (int i = 0; i < 2; i++) {
// Display a combo box to allow the user to choose the type of
// peripheral they want to use
enum peripheral_type selected_type =
bound_state->peripheral_types[i];
const char *peripheral_type_names[2] = { "None", "Memory Unit" };
const char *peripheral_type_names[2] = { "None",
"Memory Unit" };
const char *selected_peripheral_type =
peripheral_type_names[selected_type];
ImGui::SetNextItemWidth(-FLT_MIN);
if (ImGui::BeginCombo(comboLabels[i], selected_peripheral_type,
ImGuiComboFlags_NoArrowButton)) {
ImGuiComboFlags_NoArrowButton)) {
// Handle all available peripheral types
for (int j = 0; j < 2; j++) {
bool is_selected = selected_type == j;
@ -368,7 +375,8 @@ void MainMenuInputView::Draw()
bound_state->peripherals[i] = NULL;
}
// Change the peripheral type to the newly selected type
// Change the peripheral type to the newly selected
// type
bound_state->peripheral_types[i] =
(enum peripheral_type)j;
@ -377,11 +385,12 @@ void MainMenuInputView::Draw()
bound_state->peripherals[i] =
g_malloc(sizeof(XmuState));
memset(bound_state->peripherals[i], 0,
sizeof(XmuState));
}
sizeof(XmuState));
xemu_save_peripheral_settings(
active, i, bound_state->peripheral_types[i], NULL);
xemu_save_peripheral_settings(
active, i, bound_state->peripheral_types[i],
NULL);
}
}
if (is_selected) {