USA supply drop zone exploit
Closed this issue · 7 comments
If a US supply drop zone lost power while under Contruction (example: disabled by player owned microwave tank force fire) then the first supply drop will happen immediately if construction finish at that state without waiting the initial 2 minutes.
causing the drop zone to (in-a-way) refund $1500, (or $1650 with supply chain upgrades). cutting it cost down to 1000 (or 850 with upgrade)
(this could also be related to the particle cannon gate animation glitch)
I am looking at the code and I think I may see what is causing this. OCLUpdate is responsible for spawning the supply drops. On creation, m_nextCreationFrame is set to 0. In update() this is checked for 0 and recalculates it.
However, at the top of update() there is a check for the object being disabled. If this is true, m_nextCreationFrame is incremented by one and update() returns.
I can work on this once I figure out how to compile the source code and test =)
Very similar issue to #215 and I think they are caused by the same bug, although with different effect.
I did a test with building two particle cannons at the same time, but disabling it at different moments. One particle cannon finished 45 seconds after I disabled it and started its counter at 4:45. The other particle cannon finished 15 seconds after I disabled it and started its counter at 4:15.
Additionally
- The building animation goes to the dish coming out of the ground (and this animation keeps looping)
- The particle canon icon in the building abilities list is fully colored, indicating it is ready.
- The particle cannon cannot be fired, so some other check related to the timer is preventing it. In the side bar, the particle cannon image is also not ready, seemingly following the timer as well.
Issue seems to be in SpecialPowerModule.cpp
There are two variables that hold when the special power is ready. m_availableOnFrame in the SpecialPowerModule.cpp and m_readyFrame in Player::getOrStartSpecialPowerReadyFrame. I think getOrStartSpecialPowerReadyFrame() is used for powers that are manually activated by the player and m_readyFrame for powers that are automatically activated (such as the supply drop zone)_
I think if the building is paused during construction, m_availableOnFrame is never set (still 0) and m_readyFrame is set when the construction finishes.
When unpaused, two things happen
- m_availableOnFrame is equal to the number of paused frames, which is smaller than the current frame. The routine isReady() will trigger. For the supply drop zone, isReady() is used to get a supply, because it is an automated process. isReady() is also used for building elements of the particle cannon (animation and building ability icon).
- m_readyFrame is summed with the number of paused frames. Because the paused frames are counted before the finished construction, the sum is higher than the nominal timer value. m_readyFrame is also used for the right screen side icon and the check if a user can actually fire the weapon.
The solution would be to
- make sure that m_availableOnFrame is set when construction finishes when building is paused
- the pauseFrameCount is reset to 0 when construction finishes.
Not sure exactly where to do this, as I can't figure out why m_availableOnFrame is not set when the construction finishes. Something is omitting the initialization due to the pause.
As far as I can tell, these two issues are probably not related.
OCL and SpecialPowers seem to be two different things. OCL spawns objects under various conditions. This MAY happen when a special power is activated, but it also happens when some units attack. In the case of the supply drop zone, it has a class OCLUpdate that counts frames and automatically spawns the supply drop plane.
The ParticleUplinkCannonUpdate class seems to manage the Particle Cannon state machine. It also has a reference to the SpecialPowerModule and does some manipulation of the variables you mentioned. I have gone over the ParticleUplinkCannonUpdate class and couldn't find anything that immediately stood out as a problem like in the OCLUpdate class. This is because the ParticleUplinkCannonUpdate class is a lot more complex as it manages various particles, sounds, and animations. Also, it seems the devs wanted all of the timers for special powers to be located in the Player class so the problem may be there.
As far as I can tell, SpecialPowers can only be activated by the player. The only difference between them seems to be that some have a shared synced timer and others don't.
This bug is fixed in INI by giving the structure Armor 0% subdual damage during construction, so it cannot be attacked by it. So we need to check how subdual affects the OCL timer.
All objects have a disabled flag. Being subdued sets this flag. Disabled types can be found in DisabledTypes.h
OCLUpdate is the class that handles the automated OCL creations.
in OCLUpdate::OCLUpdate()
All variables used to count frames till OCL creation are initialized to 0 instead of the correct frame the OCL needs to be created. The important variable to note is m_nextCreationFrame which holds the frame that will trigger.
OCLUpdate::update() is therefore responsible for updating m_nextCreationFrame with the correct value. It does this by checking if m_nextCreationFrame == 0 then calling setNextCreationFrame() which sets it to the correct value.
However, at the very top of OCLUpdate::update(), before all other logic, there is a check for if the object's disabled flag. If it is disabled, m_nextCreationFrame is incremented by one.
Normally, this check would be skipped and instead shouldCreate() will be called each frame. shouldCreate() will return false because the object is under construction. Once the structure is subdued, the check getObject()->isDisabled() returns true and increments m_nextCreationFrame by one each frame. When the structure finishes building and is no longer disabled, the logic that calls setNextCreationFrame() and updates m_nextCreationFrame to the proper frame is never called because m_nextCreationFrame =/= 0. Instead, the Supply drop is immediately called because m_nextCreationFrame should be < TheGameLogic->getFrame() almost certainly.
Solution:
Either, call setNextCreationFrame() in the constructor or move the if( m_nextCreationFrame == 0 ){...} block the top of OCLUpdate::update().
I am fairly certain this is the fix, but I am still unable to compile at this time. If anyone is able to compile and test this solution, feel free to tackle this issue. @xezon I hope this explanation makes sense and explains how the bug is fixed by disabling subdue damage in the INI.
Thanks for checking. I did not look at code but what you write reads reasonable. When we have the code fix, then we can also revert the data fix.
yeah this is a cheeky exploit which I've seen a few players take advantage of. would be nice to fix in future.