drnovice Posted April 1, 2013 Share Posted April 1, 2013 I've published on this Thread a Collection of ALL hex editing discussions about Dune 2 EXE. I've collected from all topics regards Dune 2 Hex Editing, researches for bugs fixed and game customizing. All the infos have been sorted and cataloged, within URL link reference and a short explanation regards the modify. All Documents are in PDF format. Only v1.07-EU supported. I hope that this will serve to Dune 2 fans to reach better what they would like looking for and understand more clearly about Hex Editing. :) A special Thanks is for MrFlibble about forum support and many suggestions, Nyerguds about his wonderful EXE editor, Segra about his IDA db research that has permitted to recognized many steps on functions that regulates game playing of Dune 2. Again many Thanks to all users that have contributed with them posts to make possibile all this Collection. :) Main topics:Full Bilateral Alliance between two factions (when play as Atreides or as Fremen for example)Fix Conquest Map Vanishing House bugFix Concrete Slabs bugFix remap colours of House IXFix Loop ReinforcementsRemove Decay Structures built on concrete slabsIncrease limit number of units that Sandworms can swallowFix GUI of Units Menu that 'loses' focus on selected unit (11/04/2013)Fix use of Timeout Key on INI Scenario files as victory condition (21/04/2013)Fix: Saboteur no more detonates with Move command (29/04/2013)Fix: capturing structures with deviated units did not unlock tech (03/05/2013)Fix: Windtrap power information incorrect after capturing a Windtrap (03/05/2013)Fix: Radar scan double-counted some units and missed others (03/06/2013) - CORRECTIONChange Sandworm best target behaviour to avoid attacking Fremen Warrior/Warriors (03/06/2013)Last Update: 03/06/2013 Dune 2 v1.07-EU - Hex Editing PDF Collection.zip P.S. Bytes opcodes can be a bit different between v1.07-EU and segra's IDA db, because of db is obtained from a different version. 1 Quote Link to comment Share on other sites More sharing options...
dynasty Posted April 2, 2013 Share Posted April 2, 2013 Nice! [FIXED] I only had a quick glance, but I didn't see a patch to fix the 'random' unit deselection:https://github.com/OpenDUNE/OpenDUNE/pull/166 And also a patch to allow you to cancel an upgrade when the structure is damaged during the upgrade process:https://github.com/OpenDUNE/OpenDUNE/issues/118 1 Quote Link to comment Share on other sites More sharing options...
drnovice Posted April 2, 2013 Author Share Posted April 2, 2013 Yes I didn't see on this forum discussions about these changes, we can update the Collection every time we find out a solution about new stuff (hex editing or by c++ functions). Quote Link to comment Share on other sites More sharing options...
MrFlibble Posted April 4, 2013 Share Posted April 4, 2013 drnovice, thanks for your marvellous efforts yet again! dynasty, your input is highly appreciated! If you see other things that need to be mentioned, don't hesitate to post them here! (e.g. I didn't even know the things in your post above are fixable :)) Quote Link to comment Share on other sites More sharing options...
drnovice Posted April 5, 2013 Author Share Posted April 5, 2013 @dynasty: I saw fixes about your reports, but I don't understand how they work...However if you want that I find this code in opcode EXE, by segra's db IDA, I can try looking for them. :) Quote Link to comment Share on other sites More sharing options...
drnovice Posted April 10, 2013 Author Share Posted April 10, 2013 2 documents added to the collection: how to set count number for Sandworm swallowed units and the fix about 'lost' focus of selected units by GUI units menu. :) 1 Quote Link to comment Share on other sites More sharing options...
MrFlibble Posted April 11, 2013 Share Posted April 11, 2013 Cool, nice progress! And as always, thanks for your hard work! :D Quote Link to comment Share on other sites More sharing options...
drnovice Posted April 11, 2013 Author Share Posted April 11, 2013 MrFlibble I could give you a release for DuneX with fix about Sardaukar troops and other. ;) Quote Link to comment Share on other sites More sharing options...
drnovice Posted April 16, 2013 Author Share Posted April 16, 2013 Another update: added the fix to victory condition of Timeout Key on INI Scenario file. :) Here it's explained the workaround. 1 Quote Link to comment Share on other sites More sharing options...
MrFlibble Posted April 17, 2013 Share Posted April 17, 2013 Excellent work as usual! Keep 'em coming! ^_^ Quote Link to comment Share on other sites More sharing options...
dynasty Posted April 20, 2013 Share Posted April 20, 2013 For the timeout, the code needs to be:finish = (g_timerGame - g_tickScenarioStart >= mapSettingTimeout) because the game timer is not reset to 0 at any stage. Otherwise, the timeout condition will trigger prematurely in all but for the first level you play when starting up the game. Because Dune II doesn't check for victory/defeat in the first two minutes of a level, we need to test with 3 minutes. Create a survival scenario using:Timeout=10800 (3 min * 60 sec/min * 60 ticks/sec)WinFlags=11LoseFlags=9start scenario, wait 3 minutes -> win.exit Dune II, and start it up again.start scenario, wait 1 minute, restart scenario, wait 2 minutes -> win. Wrong! Quote Link to comment Share on other sites More sharing options...
drnovice Posted April 21, 2013 Author Share Posted April 21, 2013 Thanks for explaination. :)I'm looking c++ code only recently, and I don't know still all about these variables... btw should be enough room on EXE (since I've nopED some bytes) to get subtraction in opcode: http://forum.dune2k.com/topic/18875-dune-2-exe-editing-programming-issues/page-25#entry380308 I saw why first 2 minutes the game don't checks about end condition:if (g_timerGame - g_tickScenarioStart < 7200) return false; The correct variable in c++ code should be g_scenario.timeOut:/* Check for reaching timeout */ if (!win && (g_scenario.loseFlags & 0x8) != 0) { win = (g_timerGame - g_tickScenarioStart >= g_scenario.timeOut); }mapSettingTimeout is the name that segra write into opcode. :) EDITED: I tested about it and now it works. I've updated Attached Collection file. Quote Link to comment Share on other sites More sharing options...
drnovice Posted May 3, 2013 Author Share Posted May 3, 2013 New update by the fixes explained on related thread. 1 Quote Link to comment Share on other sites More sharing options...
drnovice Posted June 12, 2013 Author Share Posted June 12, 2013 Correction of Radar unit count fix, and added how to change Sandworm 'best target' behaviour to avoid attacking Fremen Warriors. ;)On this page the explaination. 2 Quote Link to comment Share on other sites More sharing options...
MrFlibble Posted November 18, 2013 Share Posted November 18, 2013 I was wondering, would it be possible to track down and fix the bugs related to building repair? I have no idea if they are related or not, but the effects are as follows:Repairs in v1.07 cost much less than the formula suggests, because of numbers being rounded up.Structures that have more than 500 hit points get repaired for free (this results in free repairs for the Palace). Quote Link to comment Share on other sites More sharing options...
drnovice Posted November 19, 2013 Author Share Posted November 19, 2013 I go back to my honeymoon... :P http://forum.dune2k.com/topic/18875-exe-editing-programming-issues/page-12 Maybe we can do a research about 200h value (512 decimal). Quote Link to comment Share on other sites More sharing options...
dynasty Posted November 20, 2013 Share Posted November 20, 2013 The v1.07 repair cost formula (cost per tick, which is up to 5hp) is: repairCost = ((2 * 256 / si->o.hitpoints) * si->o.buildCredits + 128) / 256; Whereas the v1.0 repair formula appears to be: repairCost = ((10 * 256 / si->o.hitpoints) * si->o.buildCredits + 128) / 256; "Appears to be" because I didn't locate it in the binary(I haven't tried, but I'd love to have this verified). The divisions here round down, so the repair cost for structures withgreater than 512 hitpoints becomes free. The "+ 128" bit is just forrounding up. The OpenDUNE code is in the commit linked below (search for 0x2000,emu_Math_ValueToPercent, emu_Math_PercentToValue). You probably couldjust invert the order of emu_Math_ValueToPercent andemu_Math_PercentToValue to get something decent, but the repair costfor the other structures might change do to rounding. https://github.com/OpenDUNE/OpenDUNE/commit/eaeea887a50c4567dfdc413263b185d88a685350 2 Quote Link to comment Share on other sites More sharing options...
drnovice Posted November 20, 2013 Author Share Posted November 20, 2013 Here the assembly: seg007:0315seg007:0315 loc_17AC5: ; CODE XREF: gameHandleBuildings:loc_17AC0jseg007:0315 B8 02 00 mov ax, 2seg007:0318 50 push axseg007:0319seg007:0319 loc_17AC9: ; Load Full Pointer to ES:xxseg007:0319 C4 1E BC 84 les bx, buildingDataPtrCurrentseg007:031Dseg007:031D loc_17ACD:seg007:031D 26 FF 77 10 push es:[bx+_buildingData.HitPoints]seg007:0321seg007:0321 loc_17AD1: ; Call Procedureseg007:0321 9A 2A 00 C4 42 call j_unknownCalcseg007:0326 59 pop cxseg007:0327 59 pop cxseg007:0328 8B F0 mov si, axseg007:032A 56 push siseg007:032Bseg007:032B loc_17ADB: ; Load Full Pointer to ES:xxseg007:032B C4 1E BC 84 les bx, buildingDataPtrCurrentseg007:032F 26 FF 77 16 push es:[bx+_buildingData.Cost]seg007:0333seg007:0333 loc_17AE3: ; Call Procedureseg007:0333 9A 20 00 C4 42 call j_unknownCalc2seg007:0338seg007:0338 loc_17AE8:seg007:0338 59 pop cxseg007:0339 59 pop cxseg007:033Aseg007:033A loc_17AEA:seg007:033A 8B F0 mov si, axseg007:033Cseg007:033C loc_17AEC: ; Load Full Pointer to ES:xxseg007:033C C4 1E 3E 39 les bx, houseGamePtrCurrentseg007:0340seg007:0340 loc_17AF0: ; Compare Two Operandsseg007:0340 26 39 77 12 cmp es:[bx+_houseGame.credits], siseg007:0344 72 60 jb short loc_17B56 ; Jump if Below (CF=1)seg007:0346seg007:0346 loc_17AF6: ; Load Full Pointer to ES:xxseg007:0346 C4 1E 3E 39 les bx, houseGamePtrCurrentseg007:034A 26 29 77 12 sub es:[bx+_houseGame.credits], si ; Integer Subtractionseg007:034E C4 1E C0 84 les bx, buildingGamePtrCurrent ; Load Full Pointer to ES:xxseg007:0352seg007:0352 loc_17B02:seg007:0352 26 8A 47 08 mov al, es:[bx+_buildingGame.houseID]seg007:0356seg007:0356 loc_17B06: ; AL -> AX (with sign)seg007:0356 98 cbwseg007:0357 3B 06 2C 3A cmp ax, houseHumanID ; Compare Two Operandsseg007:035Bseg007:035B loc_17B0B: ; Jump if Zero (ZF=1)seg007:035B 74 12 jz short buildingRepairMoreseg007:035Dseg007:035D loc_17B0D: ; Compare Two Operandsseg007:035D 83 3E A6 38 03 cmp missionNumberPrevious, 3seg007:0362 7D 0B jge short buildingRepairMore ; Jump if Greater or Equal (SF=OF)seg007:0364seg007:0364 loc_17B14: ; Load Full Pointer to ES:xxseg007:0364 C4 1E C0 84 les bx, buildingGamePtrCurrentseg007:0368 26 83 47 0E 03 add es:[bx+_buildingGame.HitPoints], 3 ; Addseg007:036Dseg007:036D loc_17B1D: ; Jumpseg007:036D EB 09 jmp short loc_17B28seg007:036F ; ---------------------------------------------------------------------------seg007:036Fseg007:036F buildingRepairMore: ; CODE XREF: gameHandleBuildings:loc_17B0Bjseg007:036F ; gameHandleBuildings+35Bjseg007:036F C4 1E C0 84 les bx, buildingGamePtrCurrent ; Load Full Pointer to ES:xxseg007:0373 26 83 47 0E 05 add es:[bx+_buildingGame.HitPoints], 5 ; Addseg007:0378seg007:0378 loc_17B28: ; CODE XREF: gameHandleBuildings:loc_17B1Djseg007:0378 C4 1E C0 84 les bx, buildingGamePtrCurrent ; Load Full Pointer to ES:xxseg007:037Cseg007:037C loc_17B2C:seg007:037C 26 8B 47 0E mov ax, es:[bx+_buildingGame.HitPoints]seg007:0380seg007:0380 loc_17B30: ; Load Full Pointer to ES:xxseg007:0380 C4 1E BC 84 les bx, buildingDataPtrCurrentseg007:0384seg007:0384 loc_17B34: ; Compare Two Operandsseg007:0384 26 3B 47 10 cmp ax, es:[bx+_buildingData.HitPoints]seg007:0388seg007:0388 loc_17B38: ; Jump if Less or Equal (ZF=1 | SF!=OF)seg007:0388 7E 1A jle short loc_17B54seg007:038Aseg007:038A loc_17B3A: ; Load Full Pointer to ES:xxseg007:038A C4 1E BC 84 les bx, buildingDataPtrCurrentseg007:038E 26 8B 47 10 mov ax, es:[bx+_buildingData.HitPoints]seg007:0392 C4 1E C0 84 les bx, buildingGamePtrCurrent ; Load Full Pointer to ES:xxseg007:0396seg007:0396 loc_17B46:seg007:0396 26 89 47 0E mov es:[bx+_buildingGame.HitPoints], ax The functions j_unknownCalc and j_unknownCalc2 make the expression showed by dynasty: ovr191:0155 ; int __cdecl far unknownCalc(int max,int current)ovr191:0155 unknownCalc proc far ; CODE XREF: j_unknownCalcJovr191:0155ovr191:0155 var_2 = word ptr -2ovr191:0155 max = word ptr 6ovr191:0155 current = word ptr 8ovr191:0155ovr191:0155 55 push bpovr191:0156 8B EC mov bp, spovr191:0158 83 EC 02 sub sp, 2 ; Integer Subtractionovr191:015Bovr191:015B loc_557AB:ovr191:015B 8B 46 08 mov ax, [bp+current]ovr191:015E 32 F6 xor dh, dh ; Logical Exclusive ORovr191:0160 8A D4 mov dl, ahovr191:0162ovr191:0162 loc_557B2:ovr191:0162 8A E0 mov ah, alovr191:0164 32 C0 xor al, al ; Logical Exclusive ORovr191:0166 8B 5E 06 mov bx, [bp+max]ovr191:0169ovr191:0169 loc_557B9: ; CODE XREF: unknownCalc+28jovr191:0169 83 FA 00 cmp dx, 0 ; Compare Two Operandsovr191:016C 74 11 jz short loc_557CF ; Jump if Zero (ZF=1)ovr191:016E 05 01 00 add ax, 1 ; Addovr191:0171 83 D2 00 adc dx, 0 ; Add with Carryovr191:0174 D1 EA shr dx, 1 ; Shift Logical Rightovr191:0176ovr191:0176 loc_557C6: ; Rotate Through Carry Rightovr191:0176 D1 D8 rcr ax, 1ovr191:0178 83 C3 01 add bx, 1 ; Addovr191:017B D1 EB shr bx, 1 ; Shift Logical Rightovr191:017D EB EA jmp short loc_557B9 ; Jumpovr191:017F ; ---------------------------------------------------------------------------ovr191:017Fovr191:017F loc_557CF: ; CODE XREF: unknownCalc+17jovr191:017F C7 46 FE FF FF mov [bp+var_2], 0FFFFhovr191:0184 83 FB 00 cmp bx, 0 ; Compare Two Operandsovr191:0187ovr191:0187 loc_557D7: ; Jump if Zero (ZF=1)ovr191:0187 74 05 jz short loc_557DEovr191:0189 F7 F3 div bx ; Unsigned Divideovr191:018B 89 46 FE mov [bp+var_2], axovr191:018Eovr191:018E loc_557DE: ; CODE XREF: unknownCalc:loc_557D7jovr191:018E 8B 46 FE mov ax, [bp+var_2]ovr191:0191 EB 00 jmp short $+2 ; Jumpovr191:0193 8B E5 mov sp, bpovr191:0195 5D pop bpovr191:0196ovr191:0196 locret_557E6: ; Return Far from Procedureovr191:0196 CB retfovr191:0196 unknownCalc endp and ovr191:0129 ; int __cdecl far unknownCalc2(int multiplyer,int)ovr191:0129 unknownCalc2 proc far ; CODE XREF: j_unknownCalc2Jovr191:0129ovr191:0129 var_2 = word ptr -2ovr191:0129 multiplyer = word ptr 6ovr191:0129 pM = word ptr 8ovr191:0129ovr191:0129 55 push bp ; 46db:0129ovr191:012A 8B EC mov bp, spovr191:012C 83 EC 02 sub sp, 2 ; Integer Subtractionovr191:012F 8B 56 06 mov dx, [bp+multiplyer]ovr191:0132 8B 46 08 mov ax, [bp+pM]ovr191:0135 F7 E2 mul dx ; Unsigned Multiplication of AL or AXovr191:0137 05 80 00 add ax, 80h ; Addovr191:013A 83 D2 00 adc dx, 0 ; Add with Carryovr191:013Dovr191:013D loc_5578D:ovr191:013D 8A C4 mov al, ahovr191:013Fovr191:013F loc_5578F:ovr191:013F 8A E2 mov ah, dlovr191:0141 80 FE 00 cmp dh, 0 ; Compare Two Operandsovr191:0144 74 03 jz short loc_55799 ; Jump if Zero (ZF=1)ovr191:0146 B8 FF FF mov ax, 0FFFFh ; was overflow in axovr191:0149ovr191:0149 loc_55799: ; CODE XREF: unknownCalc2+1Bjovr191:0149 89 46 FE mov [bp+var_2], axovr191:014Covr191:014C loc_5579C:ovr191:014C 8B 46 FE mov ax, [bp+var_2]ovr191:014Fovr191:014F loc_5579F: ; Jumpovr191:014F EB 00 jmp short $+2ovr191:0151 8B E5 mov sp, bpovr191:0153 5D pop bpovr191:0154ovr191:0154 locret_557A4: ; Return Far from Procedureovr191:0154 CB retfovr191:0154 unknownCalc2 endp 1 Quote Link to comment Share on other sites More sharing options...
drnovice Posted November 20, 2013 Author Share Posted November 20, 2013 So, how would you like to fix the bug about 512+ HP structures repair?I saw on OpenDune source code that it's fixed so:repairCost = si->o.buildCredits * 2 / si->o.hitpoints;Maybe it could be possibile have enough room for bytes. Quote Link to comment Share on other sites More sharing options...
drnovice Posted November 21, 2013 Author Share Posted November 21, 2013 The v1.07 repair cost formula (cost per tick, which is up to 5hp) is: repairCost = ((2 * 256 / si->o.hitpoints) * si->o.buildCredits + 128) / 256; Whereas the v1.0 repair formula appears to be: repairCost = ((10 * 256 / si->o.hitpoints) * si->o.buildCredits + 128) / 256; "Appears to be" because I didn't locate it in the binary (I haven't tried, but I'd love to have this verified). [...]Yes I disassembled by IDA Pro even v1.0 and the expression is that showed, with the 10 instead 2 ;) Quote Link to comment Share on other sites More sharing options...
dynasty Posted November 25, 2013 Share Posted November 25, 2013 Yes I disassembled by IDA Pro even v1.0 and the expression is that showed, with the 10 instead 2 ;)Cool, thanks! So, how would you like to fix the bug about 512+ HP structures repair?I saw on OpenDune source code that it's fixed so:repairCost = si->o.buildCredits * 2 / si->o.hitpoints;Maybe it could be possibile have enough room for bytes.I don't like this formula much. In v1.07, the repair costs per tick for each structure work out to be:0, if palace5, if heavy factory or repair facility1, if barracks or turret2, everything elseUsing the OpenDUNE formula, the repair costs change like this:+1, for palace+2, for repair factory+1, for heavy factory, windtrap, and barracks-1, for refinery and outpostThe heavy factory and repair facility changes seem quite significant since the AI likes to target them.You might as well just do:if (heavy factory || repair facility) cost = 5;else if (turret || barracks) cost = 1else cost = 2;Hopefully that fits. Quote Link to comment Share on other sites More sharing options...
drnovice Posted November 25, 2013 Author Share Posted November 25, 2013 I think it should affect the relationship between hp and cost of every structure, indipendently to the fixed values that we saw on original game.e.g. CY have relation to 1:1 (400 hp and 400 [pseudo]cost) so it could be 1 as repair cost.If we have a relation to 1:2 (near Windtrap) the repair cost would be 2 or 1:3 (as Heavy Factory) the repair cost would be 5 and so on... A general formula could be:repairCost = (si->o.buildCredits * 5) / (si->o.hitpoints * 3);Since it could expect a structure that have a relation 2:1 (hp : cost) and the repair cost would be 0 for free! Quote Link to comment Share on other sites More sharing options...
dynasty Posted November 26, 2013 Share Posted November 26, 2013 I don't think you can actually divide by the hitpoints, which is whythey go through the whole * 256 / hitpoints, * cost / 256 thing. If you don't care about keeping the repair cost of the otherstructures the same, then inverting the order ofemu_Math_ValueToPercent and emu_Math_PercentToValueseems like the easiest thing to do. 1 Quote Link to comment Share on other sites More sharing options...
drnovice Posted November 26, 2013 Author Share Posted November 26, 2013 emu_Math_ValueToPercent & emu_Math_PercentToValue are unknownCalc & unknownCalc2 in asm?Why I can't divide by the hp? I try with dosbox debugger:e.g. Repair Facility:mov ax,2BCh ; 700 costmov bx,5mul bxmov si,axmov ax,C8h ; 200 hpmov bx,3mul bxmov di,axmov ax,sidiv direturn AX=5 another example, with a hypothetical structure having hp=500 and cost=299:mov ax,12Bh ; 299 costmov bx,5mul bxmov si,axmov ax,1F4h ; 500 hpmov bx,3mul bxmov di,axmov ax,sidiv direturn AX=0 as well. Quote Link to comment Share on other sites More sharing options...
drnovice Posted November 27, 2013 Author Share Posted November 27, 2013 The change about my formula if anyone would like to try: seg007:0315 loc_17AC5: ; CODE XREF: gameHandleBuildings:loc_17AC0jseg007:0315 B8 02 00 mov ax, 2seg007:0318 50 push axseg007:0319seg007:0319 loc_17AC9: ; Load Full Pointer to ES:xxseg007:0319 C4 1E BC 84 les bx, buildingDataPtrCurrentseg007:031Dseg007:031D loc_17ACD:seg007:031D 26 FF 77 10 push es:[bx+_buildingData.HitPoints]seg007:0321seg007:0321 loc_17AD1: ; Call Procedureseg007:0321 9A 2A 00 C4 42 call j_unknownCalcseg007:0326 59 pop cxseg007:0327 59 pop cxseg007:0328 8B F0 mov si, axseg007:032A 56 push siseg007:032Bseg007:032B loc_17ADB: ; Load Full Pointer to ES:xxseg007:032B C4 1E BC 84 les bx, buildingDataPtrCurrentseg007:032F 26 FF 77 16 push es:[bx+_buildingData.Cost]seg007:0333seg007:0333 loc_17AE3: ; Call Procedureseg007:0333 9A 20 00 C4 42 call j_unknownCalc2seg007:0338seg007:0338 loc_17AE8:seg007:0338 59 pop cxseg007:0339 59 pop cxseg007:0315 C4 1E 30 84 les bx, buildingDataPtrCurrentseg007:0319 26 8B 47 10 mov ax, es:[bx+_buildingData.HitPoints]seg007:031D B9 03 00 mov cx, 3seg007:0320 F7 E1 mul cx ; Unsigned Multiplication of AL or AXseg007:0322 90 90 90 90 90 90 nop ; No Operationsseg007:0328 8B F0 mov si, axseg007:032A 26 8B 47 16 mov ax, es:[bx+_buildingData.Cost]seg007:032E B9 05 00 mov cx, 5seg007:0331 F7 E1 mul cx ; Unsigned Multiplication of AL or AXseg007:0333 F7 F6 div si ; Unsigned Divideseg007:0335 90 90 90 90 90 nop ; No Operationsseg007:033Aseg007:033A loc_17AEA:seg007:033A 8B F0 mov si, ax Search for: b8 02 00 50 c4 1e 30 84 26 ff 7710 9a 2a 00 ca 32 59 59 8b f0 56 c4 1e 30 84 26ff 77 16 9a 20 00 ca 32 59 59 8b f0Replace with: c4 1e 30 84 26 8b 47 10 b9 03 00f7 e1 90 90 90 90 90 90 8b f0 26 8b 47 16 b9 0500 f7 e1 f7 f6 90 90 90 90 90 8b f0 I tested the cost on Mission 9:Palace: 1 per tick (5hp)Repair Facility: 5 per tickR-Turret: 2 per tickCY: 1 per tickWindtrap: 2 per tick 1 Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.