Version 16
[Hedgewars_Game_Hacks.git] / Game_Hacks_v16.lua
blob751fa30bdec77775990af50f53ff813a7ca6a588
1 --[[
2 Game Hacks
4 INTRODUCTION
5 This is a style which allows you to manipulate over 100 different aspects of Hedgewars
6 using the script parameter in the game scheme.
7 Game Hacks is supported for Hedgewars version 0.9.22 and later, some additional parameters
8 are available from 0.9.23.
10 DETAILED DESCRIPTION
11 Game Hacks adds additional game modifiers and settings in the following categories:
12 - Environment
13 - Hedgehogs
14 - Turn-based play
15 - Sudden Death events
16 - Weapon parameters, including per-weapon damage (for 0.9.23 or later)
17 - Utility parameters
18 - Eye-candy/gimmicks
20 The most important enabled modifiers will be displayed and explained briefly
21 in the mission panel shown at the start of the game or when you hit the exit
22 key (default: [Esc]). As a rule of thumb, modifiers which are not obvious and
23 have a big impact on gameplay are mentioned. Modifiers which are either obvious
24 or only have little or no impact are usually nor mentioned. Also, most modified
25 weapons and utility effects are usually not mentioned.
27 One important rule: If there is a conflict between one of Hedgewars'
28 built-in settings and a script setting, the script setting takes precedence.
30 With an empty script parameter, the style only does cosmetic changes:
31 - Messages show number of remaining Birdy eggs and RC plane bombs
32 - Game modifier “Wind influences almost everything” is shown in mission panel
33 - Generic ammo symbols for Extra Time and Extra Damage
35 BASIC SYNTAX
36 This script can be configured mostly with the script parameters.
37 The script parameters are specified in a key=value format each,
38 and each pair is delimeted by a comma.
39 Each value has one of multiple parameter types,
40 which are described below.
41 Each key can be specified in long form or in short form. The long form is descriptive.
42 The short form is only 1 or 2 characters long, but harder to remember.
44 Note that the entire script parameter field should not become longer than 245 characters, longer input can lead
45 to problems in network games. This is a problem of Hedgewars. If you are using a lot of keys, you can use the
46 short form. However, even with the short form it is possible to exhaust the character limit if you use a lot of
47 parameters, so keep this in mind.
49 It is recommended to use the long form when it is possible, because the short form is hard to remember and read.
51 All values are optional.
52 See also the “EXAMPLES” section below.
54 Parameter types (see list of keys):
55 num:
56         A whole number.
57         Examples: 534, 54, -13
58 hex:
59         A whole non-negative number in hexadecimal notation. Valid digits are 0-9 and A-F or a-f.
60         Examples: E, FF, 84AE, a0a0a0
61 time:
62         Time. Times are interpreted as milliseconds by default, but you can append “ms” or “s” for milliseconds
63         or seconds, respectively. Seconds can be specified as decimal numbers with “.” as the decimal seperator
64         and with up to 3 decimal places, but milliseconds must always be whole numbers.
65         Examples (all mean 2.5 seconds): 2500, 2500ms, 2.5s
67         A percentage or raw number. If you append a percent sign to the number, it is interpreted as percentage
68         from the default raw number. Only use whole numbers for percentages. Note that specifying the raw number
69         may be much more precise, but for most of the time, specifying the percentage might be precise enough for you.
70         Examples: 2000, 100%, 125%
71 truth:
72         Used for on/off states. Can be either “true” (feature is enabled) or “false” (feature is disabled).
73         You could also just use the first letter of the words to save space.
74 choice:
75         You can choose one of multiple textual values which is written in the description.
78 In the table below, some types will have an asterisk (“*”). This means that the special value “inf” (or short “i”)
79 is allowed here, meaning a infinite amout or time.
80 Some types will have a dash (“-”). This means that you can also specify a range of values by writing
81 two values of this type seperated by a dash (e.g. “4-52”). The first value must be smaller or equal than the second
82 value, otherwise they will be ignored.
84 Some parameters permit special values, see the description.
85 Most parameters out of reasonable bounds (i.e. a barrel health of 0 or negative gravity) will be capped for technical
86 reasons to avoid a buggy or unexpected behaviour. This is a security feature but can be disabled if you set forcelimits
87 to false. WARNING: A correct operation of Game Hacks is NOT guaranteed with forcelimits=false since you would be
88 allowed to enter nonsensical values.
90 LIST OF KEYS
92 Key                     Short   Type    Default Description
93 -----------------------+-------+-------+-------+-------------------------------------------------------------------------
94 <<< ENVIRONMENT >>>
95 barrelhealth            b       num     60      Initial health of barrels.
96 healthcratehealth       eh      num-    N/A     Health in health crates will be randomly set within this range
97 sturdycrates            ec      truth   false   Crates can't be destroyed.
98 maxwind                 w       %       100     Limit the maximum wind strength (0-100).
99 fairwind                W       truth   false   Wind stays the same within the same round.
100 gravity                 g       %       100     Base gravity (without utilities) in percent.
101 girders                 G       num     0       Number of randomly placed girders, not all might be placed if too many. EXPERIMENTAL!
102 airflameshurt           a       truth   false   Flames in mid-air can hurt hedgehogs.
103 stickyflames            ef      choice  default “always”: All flames keep burning through turns. “never”: No flames do that. “default”: Flames are unchanged.
104 nosnow                  Q       truth   false   Snow does not pile up in themes where it normally snows (i.e. Snow, Christmas)
106 <<< HEDGEHOGS >>>
107 nojump                  j       truth   false   Hogs can't jump.
108 noknock                 k       truth   false   “Hogs of Steel”: Hogs can't be knocked away by sliding hogs.
109 hogfriction             0       num     0       Difference from default hog friction. Sane values are from -10 to 10.
110 poisondamage            P       num     5       Damage for poisoned hogs per round.
111 poisonkills             n       truth   false   Poison reduces health down to 0 instead of 1.
112 poison                  p       truth   false   All hedgehogs start poisoned.
113 epidemic                he      truth   false   Poisoned hedgehogs infect other hedgehogs they touch.
114 teamcure                t       truth   false   Collecting a health crate will remove poison from all hogs in team.
115 sharehealth             h       truth   false   Health of crates is shared among team mates.
116 davidandgoliath         d       truth   false   The 1st hog in team removes half the health of its team mates and adds it to its own.
117 donorbox                q       truth   false   The last dying hog (or the king in King Mode) of a team leaves a box with all the hog weapons inside.
118 maxpower                hm      truth   false   Thrown/launched weapons will be thrown/launched at full power (like mortar).
120 <<< TURNS >>>
121 ready                   r       time    5s      Ready time before the turn starts.
122 initialswitch           i       truth   false   Free hog selection at start of turn. EXPERIMENTAL!
123 strategictools          z       truth   false   Girders, rubbers, landsprays, dirt balls and resurrections end the turn.
125 <<< SUDDEN DEATH >>>
126 sdpoison                S       truth   false   Poison all hogs in Sudden Death (once).
127 sd1hp                   1       truth   false   Set the health of all hogs to 1 HP in Sudden Death (once).
129 <<< FUEL / DURATION >>>
130 saucerfuel              f       %*      2000    Fuel of a flying saucer.
131 birdyenergy             B       %*      2000    Flapping energy of Birdy.
132 landsprayfuel           L       %       1000    Fuel of landspray.
133 freezerfuel             u       %       1000    Fuel of freezer.
134 flamethrowerfuel        F       %       500     Fuel of flamethrower.
135 rcplanetimer            C       time*   15s     Time of RC plane before fuel is gone.
136 minetimer               8       time-   3s      Timer of ALL ordinary mines, with milliseconds precision. Use range for a random timer within the range.
137 stickyminetimer         s       time-   500ms   Timer of sticky mines. Use range for a random timer within the range.
138 airminetimer            Y       time-   750ms   Custom timer of air mines. Use range for a random timer within the range.
139 hellishtimer            6       time    5s      Timer of hellish hand-grenade. Use “manual” or “m” for allowing to set timer by user.
140 dynamitetimer           5       time    5s      Timer of dynamite. Use “manual” or “m” for allowing to set timer by user.
141 caketimer               c       %       2048    Walking time of cake (percentage).
142 beedelay                x       time    500ms   Delay of homing bee before it starts homing.
143 beetimer                y       time    5s      Max. flight time of homing bee after it started homing
144 drillrockettimer        3       time    5s      Detonation timer of drill rocket (not from plane). Use “manual” or “m” for allowing to set timer by user.
145 napalmbombtimer         tN      time    1s      Detonation timer of a napalm bomb.
146 kamikazerange           K       %       2048    Range of kamikaze.
147 balltimer               m       time    5s      Explosion timer of ball from ball gun. Use “manual” or “m” for allowing to manually set timer with the ball gun.
148 pickhammertimer         I       time*   4s      Active time of pickhammer.
149 blowtorchtimer          T       time*   7.5s    Active time of blowtorch.
150 poisoncloudtimer        tp      time    5s      Life timer of a poison cloud from old limburger. 5 seconds max.
152 <<< WEAPON EFFECTS >>>
153 birdyeggs               E       num*    2       Number of eggs for Birdy
154 rcplanebombs            R       num*    3       Number of bombs of RC plane.
155 ballgunballs            U       num     51      Number of balls from ballgun.
156 pianobounces            O       num     5       Number of terrain bounces of piano before it falls into water.
157 airattackbombs          A       num     6       Bombs in an air attack.
158 minestrikemines         M       num     6       Mines in a mine strike.
159 drillstrikedrills       D       num     6       Drills in a drill strike.
160 napalmbombs             N       num     6       Bombs in a napalm attack.
161 planedrops              4       num     6       Shorthand: Set all above 4 parameters at once.
162 airattackgap            wA      %       30      Distance between 2 missile drops in air attack.
163 minestrikegap           wM      %       30      Distance between 2 mine drops in mine strike.
164 drillstrikegap          wD      %       30      Distance between 2 drill drops in drill strike.
165 napalmgap               wN      %       30      Distance between 2 napalm bomb drops in napalm attack.
166 planedropgap            2       %       30      Shorthand: Set all above 4 parameters at once.
167 dudminehealth           wu      %       35      How much “health” dud mines have before they explode.
168 seductionrange          7       %       250     Range of seduction in pixels. EXPERIMENTAL!
169 airmineseekrange        wr      %*      175     Distance (in pixels) at which air mines start seeking.
170 airmineseekspeed        we      %       536871  Speed at which air mines seek hogs.
171 airminefriction         wf      %       1610613 Air friction of air mine.
172 hammerstrength          wh      %*      125     How deep a (successful) hammer hit digs into land.
173 kamikazetrigger         wk      truth   false   If true, kamikaze can be detonated early with the attack key.
174 deaglestrength          wd      %       50      How deep a desert eagle shot digs into land.
175 sniperstrength          ws      %       50      How deep a sniper rifle shot digs into land.
176 sinestrength            wS      num     1       Thickness of sine gun shot.
177 mudballstrength         wm      %       200000  Pushing strength of a mud ball. 0.9.23 or later!
179 <<< DAMAGE AND EXPLOSION SIZE >>>
180 --- NOTE: Except for airminedamage, all of these are for 0.9.23 or later!
182 airminedamage           da      %       30      Explosion damage and range of air mine.
183 minedamage              d8      %       50      Mine damage.
184 stickyminedamage        dt      %       30      Sticky mine damage.
185 grenadedamage           dg      %       50      Grenade damage.
186 hellishdamage           dH      %       90      Hellish hand-grenade damage.
187 clusterbombdamage       dl      %       20      Cluster bomb damage (main bomb).
188 clusterdamage           dL      %       25      Damage of a cluster (from cluster bomb or mortar).
189 melondamage             dm      %       75      Melon bomb damage (main piece).
190 melonpiecedamage        dM      %       75      Melon bomb piece damage.
191 balldamage              dU      %       40      Damage of a ball from the ball gun.
192 bazookadamage           db      %       50      Bazooka shell damage.
193 mortardamage            do      %       20      Mortar damage (main projectile).
194 beedamage               dx      %       50      Bee damage.
195 dynamitedamage          d5      %       75      Dynamite damage.
196 cakedamage              dc      %       75      Cake damage.
197 batdamage               dB      %       30      Baseball bat damage.
198 shoryukendamage         dF      %       30      Shoryuken damage.
199 whipdamage              dw      %       30      Whip damage.
200 rcplanedamage           dr      %       25      RC plane damage.
201 cleaverdamage           dv      %       40000   Scaling value of the impact damage of a cleaver. The damage does not linearly increase.
202 eggdamage               dE      %       10      Damage of an egg (from Birdy).
203 pianodamage             dp      %       80      Damage of a single Piano explosion (note: each impact creates 3 explosions).
204 limburgerdamage         di      %       20      Old limburger damage.
205 deagledamage            dd      %       7       Damage of a single Desert Eagle shot.
206 shotgundamage           dO      %       25      Damage of a single shotgun shot.
207 sniperdamage            ds      %       100000  Scaling value of the sniper rifle's damage.
208 sinedamage              dS      %       35      Damage of a sine wave from the sine gun.
209 kamikazedamage          dK      %       30      Damage of a kamikaze impact and its explosion.
210 airbombdamage           dA      %       30      Damage of a bomb from an air strike or en RC plane.
211 drillrocketdamage       d3      %       50      Damage of a launched drill rocket.
212 drillstrikedrilldamage  dD      %       30      Damage of a drill rocket from the drill strike.
213 blowtorchdamage         dT      %       2       Damage of a single hit with a blowtorch.
214 pickhammerdamage        dI      %       6       Damage of a single hit with a pickhammer.
215 flamedamage             df      %       2       Damage of a small flame (larger flames deal 3 or 4 times this damage)
216 hogdamage               dh      %       30      Damage of the explosion of a dying hedgehog.
217 cratedamage             dC      %       25      Damage of an exploding crate.
218 barreldamage            de      %       75      Damage of an exploding barrel.
219 hammerdamage            dj      num     3       Denominator of the hammer damage. Actual damage will be 1/<number>*<health>.
220 hammerextradamage       dJ      num     2       Denominator of the hammer damage while extra damage is on. Actual damage will be 1/<number>*<health>.
222 <<< UTILITY EFFECTS >>>
223 buildrange              J       %*      256     Max. building range of girder.
224 extratime               e       time    30s     Time you get with Extra Time utility.
225 lowgravity              l       %       50      Percentage of base gravity used when low gravity utility is used.
226 resurrectorrange        9       %       100     Range of resurrector in pixels. EXPERIMENTAL!
227 timeboxturns            ut      num*    N/A     TimeBox will return after this many additional turns. See parameter notes for details.
228 pickhammerstraightdown  up      truth   false   If true, the pick hammer will be able to dig straight down only and it direction can not be changed.
229 buildlimit              ub      num*    inf     How many constructions, rubbers and landsprays can be used per turn (inf = no limit).
231 <<< EYE CANDY / GIMMICKS >>>
232 shoppaborder            o       truth   false   Landscape has a decorative “danger” stripe on its edges.
233 ropecolor               xr      hex     D8D8D8  Rope color (will slightly change rope style). Use value “clan” to make rope appear in clan color.
234 ropeopacity             xR      %       255     Opacity of ropes (will slighty change rope style). 255=full opacity.
236 <<< SCRIPT CONFIGURATION >>>
237 forcelimits             Z       truth   true    Cap most number-based values at reasonable bounds. WARNING: If false, correct operation is NOT guaranteed
238 hedgepot                H       truth   false   Change 1-5 random game settings and 0-1 weapons/utilities.
242 PARAMETER NOTES
244 strategictools:
245         - Minor annoyance: The hedgehog will have skip turn selected in the next turn after a girder was placed
246         - Disables infinite attack mode.
248 initialswitch:
249         - It is still a bit awkward to use, i.e. it is not active in the “ready” phase.
251 seductionrange, resurrectorrange:
252         - Two circles will be drawn.
253         - The brighter shows the actual range, the other circle is the original range
255 girders:
256         - Girders will be placed with a small buffer zone around them to avoid girders going to close to things and
257           each other
258         - Hedgehogs and objects will NOT spawn on the girders; girders are inserted after everything else has been
259           generated and placed
260         - If the girders would become too crammed in the landscapes, Game Hacks decides to stop adding more girders
261         - With forcelimits=false, the above limit does not apply, but the loading time might increase considerably
262           for a huge number of girders (>100000)
263         - Known quirks:
264                 - Possibly poor girder placement for custom and “unorthodox” girder shapes from a theme
265                 - Considered experimental, the behaviour of this parameter may change in future
267 planedrops, planedropgap:
268         - If you additionally explicitly specify parameters for the explicit plane drops,
269           (airattackbombs, airattackgap, etc.) those explicit values take precedence
271 gravity, lowgravity:
272         - Low gravity utility will modify gravity based on the base gravity
273         - Disables built-in low gravity game modifier
275 hogfriction:
276         - Frictions determines how “well” the hogs slide, low friction means more sliding power
277         - Note that this scale is not linear
278         - Use these values as guide:
279                 * 9989=max. friction (hedgehogs don't slide at all)
280                 * 20=very high friction
281                 * 10=high friction
282                 * 0=default friction
283                 * -5=low friction
284                 * -8=very low friction
285                 * -10=extremely low friction
286         - Use values in-between for fine-tuning
288 gravity, lowgravity, maxwind:
289         - It does not matter if you append a percent sign or not
291 fairwind, maxwind:
292         - Overwrites the built-in “disable wind” game modifier
294 minetimer:
295         - Will overwrite the default mine timer in the game scheme editor
296         - Air mine timers will default to 750ms if not specified with airminetimer
297         - The built-in mine timer setting will also affect the air mine timer
298         - Use this setting when you need precision of 1 millisecond
299         - Use the built-in setting when you need precision of 1 second only
300         - Only use this key when you really need it
302 dynamitetimer:
303         - Animation is supported for up to 10s
304         - Longer times are perfectly okay, but the dynamite is not animated until timer is at 10s or lower.
306 poisoncloudtimer:
307         - Set forcelimits to false if you need to use timers longer than 5s.
308         - However: Times above 5s are glitchy, the color and transparency of the poison cloud become awkward.
309         - Use a time of 0 for a poison-free explosion of the old limburger.
311 barrelhealth:
312         - Barrels will start frozen if health is above 60 (this is only a graphical effect).
314 sd1hp, sdpoison:
315         - Be aware that the normal Sudden Death health decrease can still apply on top of these
316           effects. It is recommended (but not mandatory) to set it to 0
317         - If both Sudden Death health decrease and water rise are set to 0, the Sudden Death effect will
318           be faked by the script but the background won't change
320 sharehealth:
321         - Health is shared as equally as possible. If the division leaves a remainder, the remainder will
322           be split among some of the hedgehogs, beginning with the current hog and continuing with the next ones
324 healthcratehealth:
325         - If this is set, the health crate amount set in the game scheme will be ignored
327 davidandgoliath
328         - If the division leaves a remainder, the result is rounded down
330 donorbox:
331         - The donor box will replace the grave
333 timeboxturns:
334         - With value 0, the TimeBox returns as fast as possible which will be in the next turn, turning the TimeBox basically into a random teleportation
335         - Value 1 refers to the turn after the next turn, and so on.
336         - With the value “inf”, the TimeBox will return only (!) in Sudden Death or when this is the final hog in the team, not earlier
337         - Regardless of settings, the TimeBox follows the standard TimeBox behaviour of always returning in Sudden Death or when it is the final hog in the team
339 EXAMPLES
341 saucerfuel=4000
342 --> The flying saucer has 200% fuel.
344 saucerfuel=200%
345 --> The flying saucer has 200% fuel.
347 saucerfuel=inf
348 --> The flying saucer has infinite fuel.
350 stickyminetimer=0, nojump=true
351 --> Sticky mines explode instantly and jumping is disabled.
353 minetimer=1s-2s
354 --> Mines have a random time which may be between 1 and 2 seconds long.
356 ready=10s
357 --> The ready timer is set to 10 seconds.
359 poison=true, poisondamage=10
360 --> All hedgehogs are poisoned from the beginning and take 10 poison damage per turn.
362 gravity=200
363 --> The base gravity is doubled. It will go down to 100% when low gravity utility is used in-game (50% of base gravity).
365 gravity=200%, lowgravity=25%
366 --> The base gravity is doubled. It will go down to 25% of that base gravity (!) when the low gravity utility is used, which is 50% of the default gravity.
368 g=200%,l=25%
369 --> Same as the previous example, but it uses the short form.
371 ropecolor=FF0000
372 --> All ropes are red.
377 HedgewarsScriptLoad("/Scripts/Locale.lua")
378 HedgewarsScriptLoad("/Scripts/Params.lua")
379 HedgewarsScriptLoad("/Scripts/Tracker.lua")
381 -- Save several gears
382 local birdyGear = nil
383 local rcPlaneGear = nil
384 local saucerGear = nil
385 local kamikazeGear = nil
386 local planeGears = {}
387 local beeGears = {}
388 local mineGears = {}
389 local ropeGear = nil
390 local dynaGears = {}
391 local healthCaseGearsTodo = {} -- List of health crates for which the health still needs to be set
392 local hogGears = {}
393 local poisonCloudGears = {}
394 local tardisGears = {}
395 local specialJumpingGear = nil  -- Save gear for “no jumping” mode
397 -- Serveral status variables
398 local gameStarted = false
399 local setAirPlaneHealth = false
400 local switchStage = 0
401 local hogCounter = 0
402 local roundWind                 -- Used for fair wind
403 local lastRound                 -- TotalRounds value for last turn
404 local sdState = 0               -- Save Sudden Death state (0=not active, 1=starts in next turn, 2=active)
405 local girderPlaced              -- Used for strategic tools
406 local teams = {}                -- Table indexed by team names, values are number of hogs (at start of game)
407 local poisonKillDone = false    -- Has the “poison kills” modifier been applied in this turn yet?
408 local donors = {}               -- Table of hogs which are about to “donate”
409 local donorBoxes = {}           -- Table of donor boxes, values are tables which contain ammo counts
410 local flaggedGrave              -- Remember a grave for 1 tick, used by donor boxes
411 local hogNumbers = {}
412 local lastConstruction          -- Remember last placed girder or rubber. Used for girder/rubber range
413 local rangeCircle               -- Used for seduction and resurrector
414 local manualTimer               -- Used to save the last manual timer used by the user
415 local setWeaponMessage
416 local releaseShotInNextTurn     -- Temporary variable for max. launching power
417 local stuffBuilt = 0            -- How many constructions, rubbers and landsprays have been used this turn (used for constructionlimit)
418 local extraDamageUsed = false   -- Whether extra damage has been used in this turn
420 local tmpSwitchCount
421 local tmpSelectedAmmoType
423 -- List of weapons that have a power meter; used by maxPower
424 -- Longterm TODO: Keep this up-to-date for new Hedgewars releases
425 local powerWeapons = { amGrenade, amClusterBomb, amBazooka, amBee, amWatermelon,
426 amHellishBomb, amDrill, amMolotov, amGasBomb, amSMine, amSnowball, amKnife,
427 amAirMine }
429 -- More variables grabbed from the engine
430 local cMaxWindSpeed_QWORD = 1073742
431 local cMaxPower = 1500
434 --[[ Parser helpers ]]
436 function minmax(n, min, max)    
437         if forceLimits then
438                 if max and min then
439                         return math.min(max, math.max(min, n))
440                 elseif max then
441                         return math.min(max, n)
442                 elseif min then
443                         return math.max(min, n)
444                 else
445                         return n
446                 end
447         else
448         -- This happens if the “unreasonable values” protection is off
449                 return n
450         end
453 function parseRange(key, str, unitParser, ...)
454         if str == nil then return nil end
455         local minval, maxval
456         subMin, subMax = string.match(str, "([^-]+)-([^-]+)")
457         if subMin ~= nil and subMax ~= nil then
458                 minval = unitParser(key, subMin, ...)
459                 maxval = unitParser(key, subMax, ...)
460         else
461                 minval = unitParser(key, str, ...)
462                 maxval = minval
463         end
464         if minval ~= nil and maxval ~= nil then
465                 if minval > maxval then
466                         minval = nil
467                         maxval = nil
468                 end
469         end
470         return minval, maxval
474 function parsePercentOrNumber(key, str, reference, min, max)
475         if str == nil then return nil end
477         local n
478         if str == "inf" or str == "i" then
479                 return "inf"
480         elseif string.sub(str, string.len(str), string.len(str)) == "%" then
481                 local substr = string.sub(str, 1, string.len(str)-1)
482                 n = truncStrNum(substr)
483                 if type(n) == "number" then
484                         local ret = div(n * reference, 100)
485                         return minmax(ret, min, max)
486                 else
487                         return nil
488                 end
489         else
490                 n = truncStrNum(str)
491                 if type(n) == "number" then
492                         return minmax(n, min, max)
493                 else
494                         return nil
495                 end
496         end
499 --[[ 
500 - Leave original value if last characters are “ms”
501 - Multiply by 1000 if last character is “s”
502 - Leave original value if raw number was supplied
503 - min and max apply to raw number ]]
504 function parseTime(key, str, min, max)
505         if str == nil then return nil end
507         local n
508         local len = string.len(str)
510         -- infinite time
511         if str == "inf" or str == "i" then
512                 return "inf"
513         -- milliseconds
514         elseif string.sub(str, len-1, len) == "ms" then
515                 local substr = string.sub(str, 1, len-2)
516                 n = truncStrNum(substr)
517         -- seconds
518         elseif string.sub(str, len, len) == "s" then
519                 local substr = string.sub(str, 1, len-1)
520                 local prePoint, postPoint = string.match(substr, "(%d*)%.(%d*)")
521                 if postPoint == nil then
522                         prePoint = string.match(substr, "(%d*)")
523                         postPoint = "000"
524                 end
525                 prePoint = tonumber(prePoint)
527                 if prePoint == nil then
528                         return nil
529                 end
531                 local postPointN = {}
533                 for i=1,3 do
534                         local subN = string.sub(postPoint, i, i)
535                         if tonumber(subN) ~= nil then
536                                 postPointN[i] = tonumber(subN)
537                         else
538                                 postPointN[i] = 0
539                         end
540                 end
542                 n = prePoint * 1000
544                 for i=1,3 do
545                         n = n + postPointN[i] * math.pow(10, 3-i)
546                 end
550                 if type(n) ~= "number" then return nil end
551         -- raw number (interpreted as milliseconds)
552         else
553                 n = truncStrNum(str)
554         end
555         if type(n) ~= "number" then
556                 return nil
557         end
558         return minmax(n, min, max)
561 function parseTimeRange(key, str, min, max)
562         return parseRange(key, str, parseTime, min, max)
565 function parseInt(key, str, min, max)
566         if str == "inf" or str == "i" then
567                 return "inf"
568         else
569                 n = truncStrNum(str)
570                 if n ~= nil then
571                         return minmax(n, min, max)
572                 else
573                         return nil
574                 end
575         end
578 function parseIntRange(key, str, min, max)
579         return parseRange(key, str, parseInt, min, max)
582 function parseHex(key, str, min, max)
583         if str == nil then return nil end
584         local s = string.match(str, "(%x*)")
585         if s == nil then return nil end
586         local n = tonumber("0x"..str)
587         if str == "inf" or str == "i" then
588                 return "inf"
589         elseif type(n) == "number" then
590                 return minmax(n, min, max)
591         else
592                 return nil
593         end
596 function parseBool(key, str, default)
597         if str == "true" or str == "t" then
598                 return true
599         elseif str == "false" or str == "f" then
600                 return false
601         else
602                 return nil 
603         end
606 function multiParse(keys, parseFunction, ...) 
607         for i=1,#keys do
608                 local result = parseFunction(keys[i], params[keys[i]], ...)
609                 if result ~= nil then
610                         return parseFunction(keys[i], params[keys[i]], ...)
611                 end
612         end
613         return nil
616 function onParameters()
617         parseParams()
619         --[[ DEVELOPER NOTE:
620         Remove a free letter (for the short form) from this list when you introduce a new parameter with a 1-letter short form.
621         Free letters:
622                 v, V
623         ]]
625         -- Script config parameters first
626         forceLimits = multiParse({"forcelimits", "Z"}, parseBool)
627         if forceLimits == nil then forceLimits = true end
628         hedgePot = multiParse({"hedgepot", "H"}, parseBool)
630         -- Now for all the other params
632         if params["minetimer"] == "random" or params["8"] == "random" or params["minetimer"] == "r" or params["8"] == "r" then
633                 mineTimerMin = 0
634                 mineTimerMax = 5000
635         else
636                 mineTimerMin, mineTimerMax = multiParse({"minetimer", "8"}, parseTimeRange, 0)
637         end
638         if params["stickyminetimer"] == "random" or params["s"] == "random" or params["stickyminetimer"] == "r" or params["s"] == "r" then
639                 stickyMineTimerMin = 0
640                 stickyMineTimerMax = 3000
641         else
642                 stickyMineTimerMin, stickyMineTimerMax = multiParse({"stickyminetimer", "s"}, parseTimeRange, 0)
643         end
644         if params["airminetimer"] == "random" or params["Y"] == "random" or params["airminetimer"] == "r" or params["Y"] == "r" then
645                 airMineTimerMin = 0
646                 airMineTimerMax = 1300
647         else
648                 airMineTimerMin, airMineTimerMax = multiParse({"airminetimer", "Y"}, parseTimeRange, 0)
649         end
650         if params["stickyflames"] == "always" or params["stickyflames"] == "never" then
651                 stickyFlames = params["stickyflames"]
652         elseif params["ef"] == "always" or params["ef"] == "never" then
653                 stickyFlames = params["ef"]
654         end
656         healthCaseAmountMin, healthCaseAmountMax = multiParse({"healthcratehealth", "eh"}, parseIntRange, 0)
657         sturdyCrates = multiParse({"sturdycrates", "ec"}, parseBool)
659         gravity = multiParse({"gravity", "g"}, parsePercentOrNumber, 100, 1)
660         dudMineHealth = multiParse({"dudminehealth", "wu"}, parsePercentOrNumber, 35, 1)
661         barrelHealth = multiParse({"barrelhealth", "b"}, parseInt, 1)
662         extraTime = multiParse({"extratime", "e"}, parseTime, 1000)
663         saucerFuel = multiParse({"saucerfuel", "f"}, parsePercentOrNumber, 2000, 1)
664         birdyEnergy = multiParse({"birdyenergy", "B"}, parsePercentOrNumber, 2000, 1)
665         landsprayFuel = multiParse({"landsprayfuel", "L"}, parsePercentOrNumber, 1000, 1)
666         freezerFuel = multiParse({"freezerfuel", "u"}, parsePercentOrNumber, 1000, 1)
667         flamethrowerFuel = multiParse({"flamethrowerfuel", "F"}, parsePercentOrNumber, 500, 1)
668         rcPlaneTimer = multiParse({"rcplanetimer", "C"}, parseTime, 1)
669         rcPlaneBombs = multiParse({"rcplanebombs", "R"}, parseInt, 0)
670         birdyEggs = multiParse({"birdyeggs", "E"}, parseInt, 0)
671         pianoBounces = multiParse({"pianobounces", "O"}, parseInt, 1)
672         ballGunBalls = multiParse({"ballgunballs", "U"}, parseInt, 1)
673         cakeTimer = multiParse({"caketimer", "c"}, parsePercentOrNumber, 2048, 1)
674         kamikazeRange = multiParse({"kamikazerange", "K"}, parsePercentOrNumber, 2048, 0)
675         kamikazeTrigger = multiParse({"kamikazetrigger", "wk"}, parseBool)
676         blowTorchTimer = multiParse({"blowtorchtimer", "T"}, parseTime, 1)
677         pickHammerTimer = multiParse({"pickhammertimer", "I"}, parseTime, 1)
678         planeDrops = multiParse({"planedrops", "4"}, parseInt, 1)
679         airAttackBombs = multiParse({"airattackbombs", "A"}, parseInt, 1) or planeDrops
680         napalmBombs = multiParse({"napalmbombs", "N"}, parseInt, 1) or planeDrops
681         mineStrikeMines = multiParse({"minestrikemines", "M"}, parseInt, 1) or planeDrops
682         drillStrikeDrills  = multiParse({"drillstrikedrills", "D"}, parseInt, 1) or planeDrops
683         planeDropGap = multiParse({"planedropgap", "2"}, parsePercentOrNumber, 30, 1)
684         airAttackGap = multiParse({"airattackgap", "wA"}, parsePercentOrNumber, 30, 1) or planeDropGap
685         napalmGap = multiParse({"napalmgap", "wN"}, parsePercentOrNumber, 30, 1) or planeDropGap
686         mineStrikeGap = multiParse({"minestrikegap", "wM"}, parsePercentOrNumber, 30, 1) or planeDropGap
687         drillStrikeGap = multiParse({"drillstrikegap", "wD"}, parsePercentOrNumber, 30, 1) or planeDropGap
688         napalmBombTimer = multiParse({"napalmbombtimer", "tN"}, parseTime, 0)
689         girders = multiParse({"girders", "G"}, parseInt, 0)
690         ready = multiParse({"ready", "r"}, parseTime, 0)
691         airFlamesHurt = multiParse({"airflameshurt", "a"}, parseBool)
692         maxWind = multiParse({"maxwind", "w"}, parsePercentOrNumber, 100, 0, 100)
693         lowGravity = multiParse({"lowgravity", "l"}, parsePercentOrNumber, 100, 1)
694         noJumping = multiParse({"nojump", "j"}, parseBool)
695         noSnow = multiParse({"nosnow", "Q"}, parseBool)
696         initialSwitch = multiParse({"initialswitch", "i"}, parseBool)
697         strategicTools = multiParse({"strategictools", "z"}, parseBool)
698         maxPower = multiParse({"maxpower", "hm"}, parseBool)
699         shoppaBorder = multiParse({"shoppaborder", "o"}, parseBool)
700         poison = multiParse({"poison", "p"}, parseBool)
701         epidemic = multiParse({"epidemic", "he"}, parseBool)
702         poisonDamage = multiParse({"poisondamage", "P"}, parseInt, 1)
703         poisonKills = multiParse({"poisonkills", "n"}, parseBool)
704         teamCure = multiParse({"teamcure", "t"}, parseBool)
705         shareHealth = multiParse({"sharehealth", "h"}, parseBool)
706         davidAndGoliath = multiParse({"davidandgoliath", "d"}, parseBool)
707         donorBox = multiParse({"donorbox", "q"}, parseBool)
708         fairWind = multiParse({"fairwind", "W"}, parseBool)
709         noKnock = multiParse({"noknock", "k"}, parseBool)
710         sdOneHP = multiParse({"sd1hp", "1"}, parseBool)
711         sdPoison = multiParse({"sdpoison", "S"}, parseBool)
712         poisonCloudTimer = multiParse({"poisoncloudtimer", "tp"}, parseTime, 0, 5000)
713         beeTimer1 = multiParse({"beedelay", "x"}, parseTime, 1)
714         beeTimer2 = multiParse({"beetimer", "y"}, parseTime, 1)
715         -- timeboxnotimer is a legacy parameter which is still unofficially supported;
716         -- users should now use timeboxturns instead
717         tardisReturnEmergencyOnly = multiParse({"timeboxnotimer", "uT"}, parseBool)
718         tardisReturnTurns = multiParse({"timeboxturns", "ut"}, parseInt, 0)
719         -- Fall-back condition for the legacy parameter timeboxnotimer
720         if(tardisReturnTurns == nil and tardisReturnEmergencyOnly ~= nil) then
721                 -- legacy timeboxnotimer is the equivalent of timeboxturns=inf
722                 tardisReturnTurns = "inf"
723         end
724         pickHammerStraightDown = multiParse({"pickhammerstraightdown", "up"}, parseBool)
725         buildLimit = multiParse({"buildlimit", "ub"}, parseInt, 1)
727         if params["balltimer"] == "m" or params["m"] == "m" or params["balltimer"] == "manual" or params["m"] == "manual" then
728                 ballTimer = "manual"
729         else
730                 ballTimer = multiParse({"balltimer", "m"}, parseTime, 1)
731         end
732         deagleStrength = multiParse({"deaglestrength", "wd"}, parsePercentOrNumber, 50)
733         sniperStrength = multiParse({"sniperstrength", "ws"}, parsePercentOrNumber, 50)
734         hammerStrength = multiParse({"hammerstrength", "wh"}, parsePercentOrNumber, 125, 1)
735         if params["hellishtimer"] == "m" or params["6"] == "m" or params["hellishtimer"] == "manual" or params["6"] == "manual" then
736                 hhgTimer = "manual"
737         else
738                 hhgTimer = multiParse({"hellishtimer", "6"}, parseTime, 1)
739         end
740         if params["dynamitetimer"] == "m" or params["5"] == "m" or params["dynamitetimer"] == "manual" or params["5"] == "manual" then
741                 dynamiteTimer = "manual"
742         else
743                 dynamiteTimer = multiParse({"dynamitetimer", "5"}, parseTime, 0)
744         end
745         airMineFriction = multiParse({"airminefriction", "wf"}, parsePercentOrNumber, div(cMaxWindSpeed_QWORD*3, 2), 1)
746         airMineSeekSpeed = multiParse({"airmineseekspeed", "we"}, parsePercentOrNumber, div(cMaxWindSpeed_QWORD, 2), 1)
747         airMineSeekRange = multiParse({"airmineseekrange", "wr"}, parsePercentOrNumber, 175, 0)
748         airMineDamage = multiParse({"airminedamage", "da"}, parsePercentOrNumber, 30, 0)
749         if params["drillrockettimer"] == "m" or params["3"] == "m" or params["drillrockettimer"] == "manual" or params["3"] == "manual" then
750                 drillRocketTimer = "manual"
751         else
752                 drillRocketTimer = multiParse({"drillrockettimer", "3"}, parseTime, 0)
753         end
755         -- Parameters for 0.9.22 or later
756         if SetGearFriction ~= nil then
757                 hogFriction = multiParse({"hogfriction", "0"}, parseInt, -10, 9989)
758         end
759         -- 0.9.23 or later
760         if SetGearValues ~= nil then
761                 -- Damage
762                 mineDamage = multiParse({"minedamage", "d8"}, parsePercentOrNumber, 50, 0)
763                 stickyMineDamage = multiParse({"stickyminedamage", "dt"}, parsePercentOrNumber, 30, 0)
764                 grenadeDamage = multiParse({"grenadedamage", "dg"}, parsePercentOrNumber, 50, 0)
765                 hhgDamage = multiParse({"hellishdamage", "dH"}, parsePercentOrNumber, 90, 0)
766                 clusterBombDamage = multiParse({"clusterbombdamage", "dl"}, parsePercentOrNumber, 20, 0)
767                 clusterDamage = multiParse({"clusterdamage", "dL"}, parsePercentOrNumber, 25, 0)
768                 melonDamage = multiParse({"melondamage", "dm"}, parsePercentOrNumber, 75, 0)
769                 melonPieceDamage = multiParse({"melonpiecedamage", "dM"}, parsePercentOrNumber, 75, 0)
770                 ballDamage = multiParse({"balldamage", "dU"}, parsePercentOrNumber, 40, 0)
771                 bazookaDamage = multiParse({"bazookadamage", "db"}, parsePercentOrNumber, 50, 0)
772                 mortarDamage = multiParse({"mortardamage", "do"}, parsePercentOrNumber, 20, 0)
773                 beeDamage = multiParse({"beedamage", "dx"}, parsePercentOrNumber, 50, 0)
774                 dynamiteDamage = multiParse({"dynamitedamage", "d5"}, parsePercentOrNumber, 75, 0)
775                 cakeDamage = multiParse({"cakedamage", "dc"}, parsePercentOrNumber, 75, 0)
776                 batDamage = multiParse({"batdamage", "dB"}, parsePercentOrNumber, 30, 0)
777                 shoryukenDamage = multiParse({"shoryukendamage", "dF"}, parsePercentOrNumber, 30, 0)
778                 whipDamage = multiParse({"whipdamage", "dw"}, parsePercentOrNumber, 30, 0)
779                 rcPlaneDamage = multiParse({"rcplanedamage", "dr"}, parsePercentOrNumber, 25, 0)
780                 cleaverDamage = multiParse({"cleaverdamage", "dv"}, parsePercentOrNumber, 40000, 0)
781                 eggDamage = multiParse({"eggdamage", "dE"}, parsePercentOrNumber, 10, 0)
782                 pianoDamage = multiParse({"pianodamage", "dp"}, parsePercentOrNumber, 80, 0)
783                 limburgerDamage = multiParse({"limburgerdamage", "di"}, parsePercentOrNumber, 20, 0)
784                 deagleDamage = multiParse({"deagledamage", "dd"}, parsePercentOrNumber, 7, 0)
785                 shotgunDamage = multiParse({"shotgundamage", "dO"}, parsePercentOrNumber, 25, 0)
786                 sniperDamage = multiParse({"sniperdamage", "ds"}, parsePercentOrNumber, 100000, 0)
787                 sineDamage = multiParse({"sinedamage", "dS"}, parsePercentOrNumber, 35, 0)
788                 kamikazeDamage = multiParse({"kamikazedamage", "dK"}, parsePercentOrNumber, 30, 0)
789                 airBombDamage = multiParse({"airbombdamage", "dA"}, parsePercentOrNumber, 30, 0)
790                 drillRocketDamage = multiParse({"drillrocketdamage", "d3"}, parsePercentOrNumber, 50, 0)
791                 drillStrikeDrillDamage = multiParse({"drillstrikedrilldamage", "dD"}, parsePercentOrNumber, 30, 0)
792                 blowTorchDamage = multiParse({"blowtorchdamage", "dT"}, parsePercentOrNumber, 2, 0)
793                 pickHammerDamage = multiParse({"pickhammerdamage", "dI"}, parsePercentOrNumber, 6, 0)
794                 hammerDamage = multiParse({"hammerdamage", "dj"}, parseInt, 1)
795                 hammerExtraDamage = multiParse({"hammerextradamage", "dJ"}, parseInt, 1)
796                 flameDamage = multiParse({"flamedamage", "df"}, parsePercentOrNumber, 2, 0)
797                 hogDamage = multiParse({"hogdamage", "dh"}, parsePercentOrNumber, 30, 0)
798                 crateDamage = multiParse({"cratedamage", "dC"}, parsePercentOrNumber, 25, 0)
799                 barrelDamage = multiParse({"barreldamage", "de"}, parsePercentOrNumber, 75, 0)
801                 -- Misc.
802                 mudballPush = multiParse({"mudballstrength", "wm"}, parsePercentOrNumber, 200000, 0)
803                 sineStrength = multiParse({"sinestrength", "wS"}, parseInt, 1)
804                 resurrectorRange = multiParse({"resurrectorrange", "9"}, parsePercentOrNumber, 100)
805                 seductionRange = multiParse({"seductionrange", "7"}, parsePercentOrNumber, 250)
806                 ropeOpacity = multiParse({"ropeopacity", "xR"}, parsePercentOrNumber, 255, 0, 255)
807                 if params["ropecolor"] == "clan" or params["xr"] == "clan" then
808                         ropeColor = "clan"
809                 else
810                         ropeColor = multiParse({"ropecolor", "xr"}, parseHex, 0x0, 0xFFFFFF)
811                 end
812         end
813         buildRange = multiParse({"buildrange", "J"}, parsePercentOrNumber, 256, 1)
816 function onGameInit()
817         -- Build desciption text
818         local goalArray = {}
820         -- Activate the hedgepot
821         local hpModWeaps, hpTexts
822         if hedgePot == true then
823                 hpModWeaps, hpTexts = hedgepot()
824                 -- Text comes later
825         end
828         -- Most significant game hacks first
829         if gravity ~= nil and gravity ~= 100 then
830                 if gravity > 100 then
831                         table.insert(goalArray, string.format(loc("High gravity: Base gravity is %d%%."), math.floor(gravity)))
832                 else
833                         table.insert(goalArray, string.format(loc("Low gravity: Base gravity is %d%%."), math.floor(gravity)))
834                 end
835                 DisableGameFlags(gfLowGravity)
836         end
837         if strategicTools == true then
838                 table.insert(goalArray, loc("Strategic tools: Girders, rubbers, dirt balls, land sprays and resurrections end your turn."))
839                 DisableGameFlags(gfInfAttack)
840         end
841         if buildLimit == 1 then
842                 table.insert(goalArray, string.format(loc("Slow builder: You may use only one construction, rubber or land spray per turn."), buildLimit))
843         elseif buildLimit ~= nil then
844                 table.insert(goalArray, string.format(loc("Slow builder: You may use construction, rubber and land spray up to %d time(s) per turn."), buildLimit))
845         end
846         if maxPower == true then
847                 table.insert(goalArray, loc("Maximum launching power: Weapons will always be fired with full power."))
848         end
849         if buildRange == "inf" then
850                 if SetMaxBuildDistance ~= nil then
851                         SetMaxBuildDistance(0)
852                 end
853                 table.insert(goalArray, loc("Construction site: Girders and rubbers can be placed without range limits."))
854         end
855         if noJumping == true then
856                 table.insert(goalArray, loc("No jumping: Hedgehogs can't jump."))
857         end
858         if noKnock == true then
859                 table.insert(goalArray, loc("Hogs of Steel: Hedgehogs won't be pushed away by sliding hedgehogs."))
860         end
861         if hogFriction ~= nil then
862                 if hogFriction >= 9989 then
863                         table.insert(goalArray, loc("Extremely sticky hedgehogs: Hedgehogs won't slide at all."))
864                 elseif hogFriction > 0 then
865                         table.insert(goalArray, loc("Sticky hedgehogs: Hedgehog won't slide as much."))
866                 elseif hogFriction < 0 then
867                         table.insert(goalArray, loc("Slippery hedgehogs: Hedgehog will slide more easily, watch your step!"))
868                 end
869         end
871         if sturdyCrates == true then
872                 table.insert(goalArray, loc("Sturdy crates: Crates can not be destroyed."))
873         end
874         if teamCure == true then
875                 table.insert(goalArray, loc("TeamCure™: Health crates cure all team mates of poison."))
876         end
877         if shareHealth == true then
878                 table.insert(goalArray, loc("Health Socialism: Health in crates is shared among team comrades."))
879         end
880         if epidemic == true then
881                 table.insert(goalArray, loc("Epidemic: Poisoned hedgehogs infect others by touching."))
882         end
883         if poisonKills == true then
884                 table.insert(goalArray, loc("Lethal poison: Poison can reduce the health to 0."))
885         end
886         if airFlamesHurt == true then
887                 table.insert(goalArray, loc("Dangerous falling flames: Flames in mid-air can hurt hedgehogs."))
888         end
889         if stickyFlames == "always" then
890                 table.insert(goalArray, loc("Long-lived fire: All flames keep burning between turns."))
891         elseif stickyFlames == "never" then
892                 table.insert(goalArray, loc("Short-lived fire: All flames quickly burn out and do not keep burning between turns."))
893         end
894         if donorBox == true then
895                 if GetGameFlag(gfKing) then
896                         table.insert(goalArray, loc("Donor boxes: A dying king leaves a box with all the hog's weapons inside."))
897                 else
898                         table.insert(goalArray, loc("Donor boxes: The last dying hog of a team leaves a box with all the hog's weapons inside."))
899                 end
900         end
902         -- Weapon/utiliy tweaks later
904         -- Mines timer
905         -- We set MinesTime to 3000 to suppress the engine's message
906         if mineTimerMin == 0 and mineTimerMax == 0 then
907                 table.insert(goalArray, loc("Mine timer: Mines explode instantly."))
908                 MinesTime = 3000
909         elseif mineTimerMin ~= mineTimerMax then
910                 local str
911                 if(mineTimerMin % 1000 == 0 and mineTimerMax % 1000 == 0) then
912                         str = string.format(loc("Mine timer: Mines explode after %.0f-%.0f seconds."), mineTimerMin/1000, mineTimerMax/1000)
913                 else
914                         str = string.format(loc("Mine timer: Mines explode after %.1f-%.1f seconds."), mineTimerMin/1000, mineTimerMax/1000)
915                 end
916                 table.insert(goalArray, str)
917                 MinesTime = 3000
918         elseif mineTimerMin ~= nil then
919                 local str
920                 if(mineTimerMin % 1000 == 0 and mineTimerMax % 1000 == 0) then
921                         str = string.format(loc("Mine timer: Mines explode after %.0f second(s)."), mineTimerMin/1000)
922                 else
923                         str = string.format(loc("Mine timer: Mines explode after %.1f second(s)."), mineTimerMin/1000)
924                 end
925                 table.insert(goalArray, str)
926                 MinesTime = 3000
927         end
928         if stickyMineTimerMin == 0 and stickyMineTimerMax == 0 then
929                 table.insert(goalArray, loc("Sticky mine timer: Sticky mines explode instantly."))
930         elseif stickyMineTimerMin ~= stickyMineTimerMax then
931                 local str
932                 if(stickyMineTimerMin % 1000 == 0 and stickyMineTimerMax % 1000 == 0) then
933                         str = string.format(loc("Sticky mine timer: Sticky mines explode after %.0f-%.0f seconds."), stickyMineTimerMin/1000, stickyMineTimerMax/1000)
934                 else
935                         str = string.format(loc("Sticky mine timer: Sticky mines explode after %.1f-%.1f seconds."), stickyMineTimerMin/1000, stickyMineTimerMax/1000)
936                 end
937                 table.insert(goalArray, str)
938         elseif stickyMineTimerMin ~= nil then
939                 local str
940                 if(stickyMineTimerMin % 1000 == 0 and stickyMineTimerMax % 1000 == 0) then
941                         str = string.format(loc("Sticky mine timer: Sticky mines explode after %.0f second(s)."), stickyMineTimerMin/1000)
942                 else
943                         str = string.format(loc("Sticky mine timer: Sticky mines explode after %.1f second(s)."), stickyMineTimerMin/1000)
944                 end
945                 table.insert(goalArray, str)
946         end
947         if airMineTimerMin == 0 and airMineTimerMax == 0 then
948                 table.insert(goalArray, loc("Air mine timer: Air mines explode instantly."))
949         elseif airMineTimerMin ~= airMineTimerMax then
950                 local str
951                 if(airMineTimerMin % 1000 == 0 and airMineTimerMax % 1000 == 0) then
952                         str = string.format(loc("Air mine timer: Air mines explode after %.0f-%.0f seconds."), airMineTimerMin/1000, airMineTimerMax/1000)
953                 else
954                         str = string.format(loc("Air mine timer: Air mines explode after %.1f-%.1f seconds."), airMineTimerMin/1000, airMineTimerMax/1000)
955                 end
956                 table.insert(goalArray, str)
957         elseif airMineTimerMin ~= nil then
958                 local str
959                 if(airMineTimerMin % 1000 == 0 and airMineTimerMax % 1000 == 0) then
960                         str = string.format(loc("Air mine timer: Air mines explode after %.0f second(s)."), airMineTimerMin/1000)
961                 else
962                         str = string.format(loc("Air mine timer: Air mines explode after %.1f second(s)."), airMineTimerMin/1000)
963                 end
964                 table.insert(goalArray, str)
965         end
966         if kamikazeTrigger then
967                 table.insert(goalArray, loc("Kamikaze Pro: Kamikaze can be detonated early with the attack key."))
968         end
969         if saucerFuel == "inf" and birdyEnergy == "inf" and rcPlaneTimer == "inf" then
970                 table.insert(goalArray, loc("Free flying: Unlimited fuel or flight time with flying saucer, Birdy and RC plane."))
971         else
972                 if saucerFuel == "inf" then
973                         table.insert(goalArray, loc("Endless saucer fuel: Flying saucers never run out of fuel."))
974                 elseif saucerFuel ~= nil and saucerFuel ~= 2000 then
975                         table.insert(goalArray, string.format(loc("Flying saucer fuel: The flying saucer has %d%% fuel."), round(saucerFuel/20)))
976                 end
977                 if birdyEnergy == "inf" then
978                         table.insert(goalArray, loc("Badass Birdy: Birdy can fly forever."))
979                 elseif birdyEnergy ~= nil then
980                         if birdyEnergy > 2000 then
981                                 table.insert(goalArray, string.format(loc("Energetic Birdy: Birdy has %d%% stamina."), round(birdyEnergy/20)))
982                         elseif birdyEnergy < 2000 then
983                                 table.insert(goalArray, string.format(loc("Weak Birdy: Birdy has %d%% stamina."), round(birdyEnergy/20)))
984                         end
985                 end
986                 if rcPlaneTimer == "inf" then
987                         table.insert(goalArray, loc("Endless RC plane fuel: RC planes never run out of fuel."))
988                 elseif rcPlaneTimer ~= nil and rcPlaneTimer ~= 15000 then
989                         table.insert(goalArray, string.format(loc("RC plane fuel: RC planes can fly for %d second(s)."), round(rcPlaneTimer/1000)))
990                 end
991         end
992         if lowGravity ~= nil and lowGravity ~= 50 then
993                 if lowGravity <= 100 then
995                         table.insert(goalArray, string.format(loc("Modified low gravity: Low gravity utility modifies the gravity to %d%% of the base gravity."), math.floor(lowGravity)))
996                 else
997                         table.insert(goalArray, string.format(loc("High gravity utility: Low gravity utility modifies the gravity to %d%% of the base gravity."), math.floor(lowGravity)))
998                 end
999                 DisableGameFlags(gfLowGravity)
1000         end
1001         if extraTime ~= nil and extraTime ~= 30000 then
1002                 table.insert(goalArray, string.format(loc("Modified extra time: Using extra time gives you %.0f second(s)."), extraTime/1000))
1003         end
1005         -- Hedgepot: Random modified property ofbweapon/utility property
1006         if hedgePot and hpModWeaps > 0 then
1007                 -- Assumes that only 1 weapon/utility is changed
1008                 table.insert(goalArray, string.format(loc("Surprise supplies: A random property was changed: %s"), hpTexts[1]))
1009         end
1013         -- Engine flags come at last
1014         if GetGameFlag(gfMoreWind) then
1015                 -- Add a goal message for this game modifier because it was forgotten in engine
1016                 -- FIXME: Remove this as soon the engine has fixed it!
1017                 table.insert(goalArray, string.format(loc("Crazy wind: Almost everything is influenced by wind.")))
1018         end
1020         -- Concatenate the entire array for the final Goals string
1021         if #goalArray > 0 then
1022                 local goalString = ""
1023                 for i=1,#goalArray do
1024                         goalString = goalString .. goalArray[i] .. "|"
1025                 end
1026                 Goals = goalString
1027         end
1029         -- Misc. setup (no messages)
1030         if ready ~= nil then
1031                 Ready = ready
1032         end
1033         if shoppaBorder == true then
1034                 EnableGameFlags(gfShoppaBorder)
1035         end
1036         if fairWind == true or maxWind ~= nil then
1037                 -- Disable engine's wind because we want to do it our own way
1038                 EnableGameFlags(gfDisableWind)
1039         end
1041         -- Set gravity
1042         if gravity ~= nil then
1043                 SetGravity(gravity)
1044         end
1046         trackTeams()
1049 function onGearAdd(gear)
1050         local gt = GetGearType(gear)
1052         -- Gear hacks
1053         if gt == gtHedgehog then
1054                 trackGear(gear)
1055                 if epidemic then
1056                         hogGears[gear] = true
1057                 end
1059                 local teamName = GetHogTeamName(gear)
1060                 if hogNumbers[teamName] == nil then
1061                         -- first hog
1062                         hogNumbers[teamName] = { [1] = gear }
1063                 else
1064                         table.insert(hogNumbers[teamName], gear)
1065                 end
1066                 if teams[teamName] == nil then
1067                         teams[teamName] = 1
1068                 else
1069                         teams[teamName] = teams[teamName] + 1
1070                 end
1072                 if poison == true then
1073                         local dmg
1074                         if poisonDamage == nil then
1075                                 dmg = 5
1076                         else
1077                                 dmg = poisonDamage
1078                         end
1079                         SetEffect(gear, hePoisoned, dmg)
1080                 end
1082                 if noKnocking then
1083                         disableKnocking(gear)
1084                 end
1085                 if hogFriction ~= nil then
1086                         -- This assumes a default friction of 9989
1087                         local newFriction = GetGearFriction(gear) - hogFriction
1088                         SetGearFriction(gear, newFriction)
1089                 end
1090                 if hogDamage ~= nil then
1091                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, hogDamage)
1092                 end
1093         end
1094         if gt == gtCase then
1095                 if crateDamage ~= nil then
1096                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, crateDamage)
1097                 end
1098                 -- Random health case health
1099                 if healthCaseAmountMin ~= nil and healthCaseAmountMax ~= nil then
1100                         table.insert(healthCaseGearsTodo, gear)
1101                 end
1102                 if sturdyCrates then
1103                         SetState(gear, bor(GetState(gear), gstNoDamage))
1104                 end
1105         end
1106         if gt == gtExplosives then
1107                 if barrelHealth ~= nil then
1108                         SetHealth(gear, barrelHealth)
1109                         if barrelHealth > 60 then
1110                                 SetState(gear, bor(GetState(gear), gstFrozen))
1111                         end
1112                 end
1113                 if barrelDamage ~= nil then
1114                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, barrelDamage)
1115                 end
1116         end
1117         if gt == gtShell then
1118                 if bazookaDamage ~= nil then
1119                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, bazookaDamage)
1120                 end
1121         end
1122         if gt == gtMortar then
1123                 if mortarDamage ~= nil then
1124                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, mortarDamage)
1125                 end
1126         end
1127         if gt == gtGrenade then
1128                 if grenadeDamage ~= nil then
1129                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, grenadeDamage)
1130                 end
1131         end
1132         if gt == gtClusterBomb then
1133                 if clusterBombDamage ~= nil then
1134                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, clusterBombDamage)
1135                 end
1136         end
1137         if gt == gtCluster then
1138                 if clusterDamage ~= nil then
1139                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, clusterDamage)
1140                 end
1141         end
1142         if gt == gtMine then
1143                 mineGears[gear] = true
1144                 if mineTimerMin ~= nil then
1145                         if mineTimerMin ~= mineTimerMax then
1146                                 SetTimer(gear, mineTimerMin + GetRandom(mineTimerMax-mineTimerMin+1))
1147                         else
1148                                 SetTimer(gear, mineTimerMin)
1149                         end
1150                 end
1151                 if mineDamage ~= nil then
1152                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, mineDamage)
1153                 end
1154         end
1155         if gt == gtSMine then
1156                 if stickyMineTimerMin ~= nil then
1157                         if stickyMineTimerMin ~= stickyMineTimerMax then
1158                                 SetTimer(gear, stickyMineTimerMin + GetRandom(stickyMineTimerMax-stickyMineTimerMin+1))
1159                         else
1160                                 SetTimer(gear, stickyMineTimerMin)
1161                         end
1162                 elseif stickyMineTimer ~= nil then
1163                         SetTimer(gear, stickyMineTimer)
1164                 end
1165                 if stickyMineDamage ~= nil then
1166                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, stickyMineDamage)
1167                 end
1168         end
1169         if gt == gtAirMine then
1170                 if airMineTimerMin ~= nil then
1171                         local t
1172                         if airMineTimerMin ~= airMineTimerMax then
1173                                 t = airMineTimerMin + GetRandom(airMineTimerMax-airMineTimerMin+1)
1174                         else
1175                                 t = airMineTimerMin
1176                         end
1177                         SetTimer(gear, t)
1178                         SetGearValues(gear, nil, nil, t)   -- WDTimer
1179                 end
1180                 if airMineSeekSpeed ~= nil or airMineSeekRange ~= nil then
1181                         local r = airMineSeekRange
1182                         if airMineSeekRange == "inf" then
1183                                 r = 0xFFFFFFFF
1184                         end
1185                         SetGearValues(gear, r, airMineSeekSpeed)
1186                 end
1187                 if airMineFriction ~= nil then
1188                         SetGearPos(gear, airMineFriction)
1189                 end
1190                 if airMineDamage ~= nil then
1191                         SetGearValues(gear, nil, nil, nil, nil, nil, airMineDamage)   -- Karma
1192                 end
1193         end
1194         if gt == gtJetpack then
1195                 saucerGear = gear
1196                 if type(saucerFuel) == "number" then
1197                         SetHealth(gear, saucerFuel)
1198                 end
1199         end
1200         if gt == gtIceGun then
1201                 if freezerFuel ~= nil then
1202                         SetHealth(gear, freezerFuel)
1203                 end
1204         end
1205         if gt == gtFlamethrower then
1206                 if flamethrowerFuel ~= nil then
1207                         SetHealth(gear, flamethrowerFuel)
1208                 end
1209         end
1210         if gt == gtLandGun then
1211                 if buildLimit ~= nil then
1212                         if stuffBuilt >= buildLimit then
1213                                 -- Destroy land gun immediately if construction limit is exceeded
1214                                 PlaySound(sndDenied)
1215                                 AddCaption(loc("Building limit exceeded!"), 0xFFFFFFFF, capgrpAmmostate)
1216                                 SetHealth(gear, 0)
1217                                 -- This may have reduced ammo by one, so we have to return it
1218                                 local ammo = GetAmmoCount(CurrentHedgehog, amLandGun)
1219                                 if(ammo ~= 100) then
1220                                         AddAmmo(CurrentHedgehog, amLandGun, ammo + 1)
1221                                 end
1222                         else
1223                                 stuffBuilt = stuffBuilt + 1
1224                                 AddCaption(string.format(loc("Buildings left: %d"), buildLimit - stuffBuilt), 0xFFFFFFFF, capgrpAmmostate)
1225                         end
1226                 end
1227                 if landsprayFuel ~= nil then
1228                         SetHealth(gear, landsprayFuel)
1229                 end
1230         end
1231         if gt == gtRCPlane then
1232                 rcPlaneGear = gear
1233                 if type(rcPlaneTimer) == "number" then
1234                         SetTimer(gear, rcPlaneTimer)
1235                 end
1236                 if rcPlaneBombs == "inf" then
1237                         -- Set RC plane bombs to a arbitrarily large number, but later it will be ensured this stays always above 0
1238                         SetHealth(gear, 9001)
1239                 elseif rcPlaneBombs ~= nil then
1240                         SetHealth(gear, rcPlaneBombs)
1241                 end
1242                 if rcPlaneDamage ~= nil then
1243                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, rcPlaneDamage)
1244                 end
1245         end
1246         if gt == gtAirBomb then
1247                 if airBombDamage ~= nil then
1248                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, airBombDamage)
1249                 end
1250         end
1251         if gt == gtBirdy then
1252                 birdyGear = gear
1253                 if type(birdyEnergy) == "number" then
1254                         SetHealth(gear, birdyEnergy)
1255                 end
1256                 if birdyEggs == "inf" then
1257                         -- Set eggs to a arbitrarily large number, but later it will be ensured this stays always above 0
1258                         SetFlightTime(gear, 9001)
1259                 elseif birdyEggs ~= nil then
1260                         SetFlightTime(gear, birdyEggs)
1261                 end
1262         end
1263         if gt == gtEgg then
1264                 if eggDamage ~= nil then
1265                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, eggDamage)
1266                 end
1267         end
1268         if gt == gtPiano then
1269                 if pianoBounces ~= nil then
1270                         SetGearPos(gear, 5 - pianoBounces)
1271                 end
1272                 if pianoDamage ~= nil then
1273                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, pianoDamage)
1274                 end
1275         end
1276         if gt == gtAirAttack then
1277                 planeGears[gear] = true
1278                 local planeType = GetState(gear)
1279                 if planeType == 0 then
1280                         -- air attack
1281                         if airAttackBombs ~= nil then
1282                                 SetHealth(gear, airAttackBombs)
1283                         end
1284                         if airAttackGap ~= nil then
1285                                 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, airAttackGap)
1286                         end
1287                 elseif planeType == 1 then
1288                         -- mine strike
1289                         if mineStrikeMines ~= nil then
1290                                 SetHealth(gear, mineStrikeMines)
1291                         end
1292                         if mineStrikeGap ~= nil then
1293                                 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, mineStrikeGap)
1294                         end
1295                 elseif planeType == 2 then
1296                         -- napalm
1297                         if napalmBombs ~= nil then
1298                                 SetHealth(gear, napalmBombs)
1299                         end
1300                         if napalmGap ~= nil then
1301                                 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, napalmGap)
1302                         end
1303                 elseif planeType == 3 then
1304                         -- drill strike
1305                         if drillStrikeDrills ~= nil then
1306                                 SetHealth(gear, drillStrikeDrills)
1307                         end
1308                         if drillStrikeGap ~= nil then
1309                                 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, drillStrikeGap)
1310                         end
1311                 end
1312         end
1313         if gt == gtNapalmBomb and napalmBombTimer ~= nil then
1314                 SetTimer(gear, napalmBombTimer)
1315         end
1316         if gt == gtBallGun and ballGunBalls ~= nil then
1317                 SetTimer(gear, ballGunBalls * 100 - 99)
1318         end
1319         if gt == gtBall then
1320                 if ballTimer ~= nil then
1321                         local timer
1322                         if type(ballTimer) == "number" then
1323                                 timer = ballTimer
1324                         elseif ballTimer == "manual" then
1325                                 if manualTimer == nil then
1326                                         manualTimer = 5
1327                                 end
1328                                 timer = manualTimer*1000
1329                         end
1330                         SetTimer(gear, timer)
1331                 end
1332                 if ballDamage ~= nil  then
1333                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, ballDamage)
1334                 end
1335         end
1336         if gt == gtSniperRifleShot then
1337                 if sniperStrength ~= nil then
1338                         SetHealth(gear, sniperStrength)
1339                 end
1340                 if sniperDamage ~= nil  then
1341                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, sniperDamage)
1342                 end
1343         end
1344         if gt == gtDEagleShot then
1345                 if deagleStrength ~= nil then
1346                         SetHealth(gear, deagleStrength)
1347                 end
1348                 if deagleDamage ~= nil  then
1349                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, deagleDamage)
1350                 end
1351         end
1352         if gt == gtShotgunShot then
1353                 if shotgunDamage ~= nil  then
1354                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, shotgunDamage)
1355                 end
1356         end
1357         if gt == gtHellishBomb then
1358                 if hhgTimer ~= nil then
1359                         local timer
1360                         if type(hhgTimer) == "number" then
1361                                 timer = hhgTimer
1362                         elseif hhgTimer == "manual" then
1363                                 if manualTimer == nil then
1364                                         manualTimer = 5
1365                                 end
1366                                 timer = manualTimer*1000
1367                         end
1368                         SetTimer(gear, timer)
1369                 end
1370                 if hhgDamage ~= nil then
1371                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, hhgDamage)
1372                 end
1373         end
1374         if gt == gtWatermelon then
1375                 if melonDamage ~= nil  then
1376                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, melonDamage)
1377                 end
1378         end
1379         if gt == gtMelonPiece then
1380                 if melonPieceDamage ~= nil  then
1381                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, melonPieceDamage)
1382                 end
1383         end
1384         if gt == gtDynamite then
1385                 if dynamiteTimer ~= nil then
1386                         local timer
1387                         if type(dynamiteTimer) == "number" then
1388                                 timer = dynamiteTimer
1389                         elseif dynamiteTimer == "manual" then
1390                                 if manualTimer == nil then
1391                                         manualTimer = 5
1392                                 end
1393                                 timer = manualTimer*1000
1394                         end
1396                         SetTimer(gear, timer)
1397                         if timer > 10000 then
1398                                 SetTag(gear, 32)
1399                         elseif timer > 5000 then
1400                                 SetTag(gear, 32 + div(10000-timer, 166))
1401                         else
1402                                 SetTag(gear, div(5000-timer, 166))
1403                         end
1404                 end
1405                 if dynamiteDamage ~= nil then
1406                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, dynamiteDamage)
1407                 end
1408                 dynaGears[gear] = true
1409         end
1410         if gt == gtGasBomb then
1411                 if limburgerDamage ~= nil then
1412                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, limburgerDamage)
1413                 end
1414         end
1415         if gt == gtPoisonCloud and poisonCloudTimer ~= nil then
1416                 if poisonCloudTimer == 0 then
1417                         DeleteGear(gear)
1418                 else
1419                         SetTimer(gear, poisonCloudTimer)
1420                 end
1421         end
1422         if gt == gtSineGunShot then
1423                 if sineStrength ~= nil then
1424                         SetGearValues(gear, nil, nil, nil, sineStrength)
1425                 end
1426                 if sineDamage ~= nil then
1427                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, sineDamage)
1428                 end
1429         end
1430         if gt == gtResurrector and resurrectorRange ~= nil then
1431                 SetGearValues(gear, nil, nil, nil, resurrectorRange)
1432         end
1433         if gt == gtSeduction and seductionRange ~= nil then
1434                 SetGearValues(gear, nil, nil, nil, seductionRange)
1435         end
1436         if gt == gtBee then
1437                 if beeTimer1 ~= nil then
1438                         SetTimer(gear, beeTimer1)
1439                 end
1440                 if beeTimer1 ~= nil or beeTimer2 ~= nil then
1441                         SetGearPos(gear, 0)
1442                 end
1443                 if beeDamage ~= nil then
1444                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, beeDamage)
1445                 end
1446                 beeGears[gear] = true
1447         end
1448         if gt == gtDrill then
1449                 -- Is this a normal drill (not from drill strike)?
1450                 if band(GetState(gear), gsttmpFlag) == 0 then
1451                         if drillRocketTimer ~= nil then
1452                                 local timer
1453                                 if type(drillRocketTimer) == "number" then
1454                                         timer = drillRocketTimer
1455                                 elseif drillRocketTimer == "manual" then
1456                                         if manualTimer == nil then
1457                                                 manualTimer = 5
1458                                         end
1459                                         timer = manualTimer*1000
1460                                 end
1461                                 SetTimer(gear, timer)
1462                         end
1463                         if drillRocketDamage ~= nil then
1464                                 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, drillRocketDamage)
1465                         end
1466                 else
1467                         if drillStrikeDrillDamage ~= nil then
1468                                 SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, drillStrikeDrillDamage)
1469                         end
1470                 end
1471         end
1472         if gt == gtCake then
1473                 if cakeTimer ~= nil then
1474                         SetHealth(gear, cakeTimer)
1475                 end
1476                 if cakeDamage ~= nil then
1477                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, cakeDamage)
1478                 end
1479         end
1480         if gt == gtFirePunch then
1481                 if shoryukenDamage ~= nil then
1482                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, shoryukenDamage)
1483                 end
1484         end
1485         if gt == gtWhip then
1486                 if whipDamage ~= nil then
1487                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, whipDamage)
1488                 end
1489         end
1490         if gt == gtShover then
1491                 if batDamage ~= nil then
1492                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, batDamage)
1493                 end
1494         end
1495         if gt == gtKamikaze then
1496                 kamikazeGear = gear
1497                 if kamikazeRange ~= nil then
1498                         SetHealth(gear, kamikazeRange)
1499                 end
1500                 if kamikazeDamage ~= nil then
1501                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, kamikazeDamage)
1502                 end
1503         end
1504         if gt == gtKnife then
1505                 if cleaverDamage ~= nil then
1506                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, cleaverDamage)
1507                 end
1508         end
1509         if gt == gtHammer then
1510                 -- Hammer damage
1511                 if(extraDamageUsed and hammerExtraDamage ~= nil) then
1512                         -- Hammer with Extra Damage
1513                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, hammerExtraDamage)
1514                 elseif((not extraDamageUsed) and (hammerDamage ~= nil)) then
1515                         -- Hammer without Extra Damage
1516                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, hammerDamage)
1517                 end
1518         end
1519         if gt == gtHammerHit and hammerStrength ~= nil then
1520                 if hammerStrength == "inf" then
1521                         SetTimer(gear, 0)
1522                 else
1523                         SetTimer(gear, hammerStrength)
1524                 end
1525         end
1526         if gt == gtPickHammer then
1527                 if pickHammerTimer == "inf" then
1528                         SetTimer(gear, 0)
1529                 elseif pickHammerTimer ~= nil then
1530                         SetTimer(gear, pickHammerTimer)
1531                 end
1532                 if pickHammerDamage ~= nil then
1533                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, pickHammerDamage)
1534                 end
1535                 -- Disallow pickhammer to be moved left and right
1536                 if pickHammerStraightDown == true then
1537                         SetGearMessage(CurrentHedgehog, band(GetGearMessage(CurrentHedgehog), bnot(gmLeft+gmRight)))
1538                         -- Input mask will be restored when pick hammer is deleted
1539                         SetInputMask(band(GetInputMask(), bnot(gmLeft+gmRight)))
1540                 end
1541         end
1542         if gt == gtBlowTorch then
1543                 if blowTorchTimer == "inf" then
1544                         SetTimer(gear, 0)
1545                 elseif blowTorchTimer ~= nil then
1546                         SetTimer(gear, blowTorchTimer)
1547                 end
1548                 if blowTorchDamage ~= nil then
1549                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, blowTorchDamage)
1550                 end
1551         end
1552         if gt == gtGrave and donorBox then
1553                 flaggedGrave = gear
1554         end
1555         if gt == gtGirder then
1556                 local ignore = false
1557                 if buildLimit ~= nil then
1558                         if stuffBuilt >= buildLimit then
1559                                 AddCaption(loc("Building limit exceeded!"), 0xFFFFFFFF, capgrpAmmostate)
1560                                 DeleteGear(gear)
1561                                 ignore = true
1562                         end
1563                 end
1564                 if not ignore then
1565                         lastConstruction = gear
1566                 end
1567         end
1568         if gt == gtFlake and noSnow == true then
1569                 -- Get rid of snow flakes
1570                 -- But only replace snow flakes, not the flakes from land spray!
1571                 if band(GetState(gear), gsttmpFlag) == 0 then
1572                         -- Replace snow flake gear with (harmless) visual gear flake
1573                         local x, y = GetGearPosition(gear)
1574                         local dx, dy = GetGearVelocity(gear)
1575                         DeleteGear(gear)
1576                         AddVisualGear(x, y, vgtFlake, 0, false)
1577                 end
1578         end
1579         if gt == gtRope then
1580                 -- Rope color and rope opacity
1581                 if ropeColor ~= nil or ropeOpacity ~= nil then
1582                         -- Mostly just some bitmask stuff (color is in RGBA format)
1583                         local opacityMask
1584                         if ropeOpacity ~= nil then
1585                                 opacityMask = bor(0xFFFFFF00, ropeOpacity)
1586                         else
1587                                 opacityMask = 0xFFFFFFFF
1588                         end
1589                         local actualColor
1590                         if ropeColor == "clan" then
1591                                 actualColor = band(GetClanColor(GetHogClan(CurrentHedgehog)), opacityMask)
1592                         elseif ropeColor ~= nil then
1593                                 actualColor = band(ropeColor*0x100+0xFF, opacityMask)
1594                         else
1595                                 actualColor = band(0xD8D8D8FF, opacityMask)
1596                         end
1598                         -- Set rope to “line” mode (needed for coloization to work)
1599                         SetTag(gear, 1)
1600                         -- Set rope color
1601                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, actualColor)
1602                 end
1603                 ropeGear = gear
1604         end
1605         if gt == gtTardis then
1606                 if tardisReturnTurns ~= nil then
1607                         -- Remember the TimeBox' gear ID for later use
1608                         tardisGears[gear] = tardisReturnTurns
1609                 end
1610         end
1611         if gt == gtSnowball then
1612                 if strategicTools then
1613                         -- Strategic tools: Snowball
1614                         strategicToolsEndTurn(3000, false)
1615                 end
1616                 if mudballPush ~= nil then
1617                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, mudballPush)
1618                 end
1619         end
1621         if gt == gtFlame then
1622                 -- Mid-air flames hurt hedgehogs
1623                 if airFlamesHurt == true then
1624                         SetFlightTime(gear, 0)
1625                 end
1626                 -- Make all flames sticky
1627                 if stickyFlames == "always" then
1628                         SetState(gear, bor(GetState(gear), gsttmpFlag))
1629                 -- Make all flames non-sticky
1630                 elseif stickyFlames == "never" then
1631                         SetState(gear, band(GetState(gear), bnot(gsttmpFlag)))
1632                 end
1633                 if flameDamage ~= nil then
1634                         SetGearValues(gear, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, flameDamage)
1635                 end
1636         end
1638         -- No jumping mode
1639         if noJumping and (gt == gtJetpack or gt == gtRope or gt == gtParachute) then
1640                 specialJumpingGear = gear
1641                 SetInputMask(bor(GetInputMask(), gmLJump))
1642                 SetInputMask(band(GetInputMask(), bnot(gmHJump)))
1643         end
1646 function onHogRestore(gear)
1647         if epidemic then
1648                 hogGears[gear] = true
1649         end
1652 function onGearDelete(gear)
1653         local gt = GetGearType(gear)
1655         -- Clear temp. gear variables
1656         if gt == gtHedgehog then
1657                 hogGears[gear] = nil
1658         elseif gt == gtBirdy then
1659                 birdyGear = nil
1660         elseif gt == gtRCPlane then
1661                 rcPlaneGear = nil
1662         elseif gt == gtJetpack then
1663                 saucerGear = nil
1664         elseif gt == gtDynamite then
1665                 dynaGears[gear] = nil
1666         elseif gt == gtRope then
1667                 ropeGear = nil
1668         elseif gt == gtAirAttack then
1669                 planeGears[gear] = nil
1670         elseif gt == gtTardis then
1671                 tardisGears[gear] = nil
1672         elseif gt == gtMine then
1673                 mineGears[gear] = nil
1674         end
1676         if gt == gtHedgehog then
1677                 if donorBox and donors[gear] then
1678                         if flaggedGrave ~= nil then
1679                                 DeleteGear(flaggedGrave)
1680                                 flaggedGrave = nil
1681                         end
1682                         local x, y = GetGearPosition(gear)
1684                         local ammos = {}
1685                         local ammoTypes = {}
1687                         --[[ NOTE: This is a hack to get the number of available
1688                         ammo types in Hedgewars.
1689                         TODO: Update this code whenever ammo types are extended,
1690                         or when the engine allows to query this number directly
1691                         somehow.
1692                         ]]
1693                         local maxAmmoID = 58    -- Ammo count in 0.9.22
1694                         for i=1,maxAmmoID do
1695                                 ammoTypes[i] = i-1
1696                         end
1697                         for i=1,#ammoTypes do
1698                                 if ammoTypes[i] ~= amSkip then
1699                                         local count = GetAmmoCount(gear, ammoTypes[i])
1700                                         if count > 0 then
1701                                                 ammos[ammoTypes[i]] = count
1702                                         end
1703                                 end
1704                         end
1706                         local box = SpawnFakeAmmoCrate(x, y, false, false)
1707                         donorBoxes[box] = ammos
1708                 end
1709                 trackDeletion(gear)
1710         end
1712         if gt == gtBee and (beeTimer1 ~= nil or beeTimer2 ~= nil) then
1713                 beeGears[gear] = nil
1714         end
1716         -- Collect donor crate and award its contents to the collector
1717         if donorBox and gt == gtCase and donorBoxes[gear] ~= nil then
1718                 if band(GetGearMessage(gear), gmDestroy) ~= 0 then
1719                         for ammoType, addCount in pairs(donorBoxes[gear]) do
1720                                 modAmmo(CurrentHedgehog, ammoType, addCount)
1721                         end
1722                         PlaySound(sndShotgunReload)
1723                         AddCaption(loc("Donor box collected!"), GetClanColor(GetHogClan(CurrentHedgehog)), capgrpAmmoinfo)
1724                         donorBoxes[gear] = nil
1725                 end
1726         end
1728         -- Health Crate stuff
1729         if (teamCure or shareHealth) and gt == gtCase and band(GetGearPos(gear), 0x2) ~= 0 then
1730                 if band(GetGearMessage(gear), gmDestroy) ~= 0 then
1731                         -- TeamCure™ 
1732                         if teamCure then
1733                                 runOnHogsInTeam(function(hog)
1734                                         SetEffect(hog, hePoisoned, 0)
1735                                 end, GetHogTeamName(CurrentHedgehog))
1736                         end
1738                         -- Health Socialism
1739                         if shareHealth then
1740                                 --[[ Okay, here's how it works:
1741                                 The health pack is divided by the number of hogs in the
1742                                 team. The integer result of box health divided by hogs is then
1743                                 added to each member.
1744                                 If there is a remainder, it is split up among the hogs this
1745                                 way: We start counting from the current hedgehog, and give it
1746                                 another HP. We continue with the next hogs in team order and
1747                                 give them 1 HP each until the remainder is "used up".
1748                                 After this algorithm, the entire health pack has been used up.
1750                                 Examples:
1751                                 - 50 HP box, 5 team members: 50/10=10, so each gets 10 HP.
1752                                 - Hog 2 collects a 30 HP box, 4 team members:
1753                                   30/4 = 7, remainder 2. So:
1754                                   +8 HP for Hog 2 and Hog 3 (current and next), +7 HP for Hog 1 and Hog 4.
1755                                 - 1 HP, 8 team members: +1 HP only for current hog.
1756                                 ]]
1759                                 -- REALLY complicated hack to find out the correct hog order
1760                                 local teamHogTable = {}
1761                                 local CurrentHedgehogID
1762                                 runOnHogsInTeam(function(hog)
1763                                         table.insert(teamHogTable, hog)
1764                                         if hog == CurrentHedgehog then
1765                                                 CurrentHedgehogID = #teamHogTable
1766                                         end
1767                                 end, GetHogTeamName(CurrentHedgehog))
1769                                 local newTeamHogTable = { CurrentHedgehog }
1770                                 for i=#teamHogTable, CurrentHedgehogID+1, -1 do
1771                                         table.insert(newTeamHogTable, teamHogTable[i])
1772                                 end
1773                                 for i=1, CurrentHedgehogID-1 do
1774                                         table.insert(newTeamHogTable, teamHogTable[i])
1775                                 end
1777                                 -- Now finally heal some hogs
1778                                 local boxHealth = GetHealth(gear)
1779                                 local healthPart = div(boxHealth, #teamHogTable)
1780                                 local remainder = math.fmod(boxHealth, #teamHogTable)
1782                                 for i=1, #newTeamHogTable do
1783                                         local heal
1784                                         local hog = newTeamHogTable[i]
1785                                         if i > remainder then
1786                                                 heal = healthPart
1787                                         else
1788                                                 heal = healthPart + 1
1789                                         end
1790                                         SetHealth(hog, GetHealth(hog) + heal)
1791                                 end
1793                                 -- Undo the original health crate effect
1794                                 SetHealth(CurrentHedgehog, GetHealth(CurrentHedgehog) - boxHealth)
1796                                 -- Overwrite health message
1797                                 AddCaption(string.format(loc("+%d (shared)"), boxHealth), GetClanColor(GetHogClan(CurrentHedgehog)), capgrpAmmoinfo)
1798                         end
1799                 end
1800         end
1802         -- No jumping mode
1803         if noJumping and (gt == gtJetpack or gt == gtRope or gt == gtParachute) then
1804                 specialJumpingGear = nil
1805                 SetInputMask(band(GetInputMask(), bnot(gmLJump + gmHJump)))
1806         end
1808         -- Force pick hammer to dig straight down
1809         if pickHammerStraightDown and gt == gtPickHammer then
1810                 -- Enable left and right again for pick hammer
1811                 SetInputMask(bor(GetInputMask(), gmLeft+gmRight))
1812         end
1814         if gt == gtKamikaze then
1815                 kamikazeGear = nil
1816         end
1818         -- Strategic tools: Resurrection and land spray
1819         if strategicTools then
1820                 if gt == gtResurrector then
1821                         strategicToolsEndTurn(3000, false)
1822                 elseif gt == gtLandGun then
1823                         strategicToolsEndTurn(4000, false)
1824                 end
1825         end
1827         -- For some reason using blow torch enables knocking again
1828         if noKnock and gt == gtBlowTorch then
1829                 runOnHogs(disableKnocking)
1830         end
1833 function onHogHide(gear)
1834         hogGears[gear] = nil
1837 function onGameTick20()
1838         -- Reset saucer/rc plane fuel and birdy energy if infinite
1839         if rcPlaneTimer == "inf" and rcPlaneGear then
1840                 SetTimer(rcPlaneGear, 15000)
1841         end
1842         if saucerFuel == "inf" and saucerGear then
1843                 -- TODO: Also prevent the fuel indicator from being shown
1844                 SetHealth(saucerGear, 2000)
1845         end
1846         if birdyEnergy == "inf" and birdyGear then
1847                 SetHealth(birdyGear, 2000)
1848         end
1850         -- Main bee timer
1851         if beeTimer2 ~= nil then
1852                 for bee, _ in pairs(beeGears) do
1853                         if GetGearPos(bee) == 0 then
1854                                 if GetTimer(bee) <= 20 then
1855                                         SetFlightTime(bee, GetTimer(bee))
1856                                         SetGearPos(bee, 1)
1857                                 end
1858                         elseif GetGearPos(bee) == 1 then
1859                                 SetTimer(bee, beeTimer2)
1860                                 SetGearPos(bee, 2)
1861                         end
1862                 end
1863         end
1865         -- Dud mine health
1866         if dudMineHealth ~= nil then
1867                 for mine, _ in pairs(mineGears) do
1868                         if GetHealth(mine) == 0 then
1869                                 -- Dud mines explode when Damage reaches 35
1870                                 SetGearValues(mine, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 35 - dudMineHealth) -- Damage
1871                                 mineGears[mine] = nil
1872                         end
1873                 end
1874         end
1876         -- Epidemic
1877         if epidemic then
1878                 local tdist = 22        -- Touch distance
1879                 for hog1, t1 in pairs(hogGears) do
1880                         if GetEffect(hog1, hePoisoned) >= 1 and GetHealth(hog1) > 0 then
1881                                 local x1, y1 = GetGearPosition(hog1)
1882                                 for hog2, t2 in pairs(hogGears) do
1883                                         local x2, y2 = GetGearPosition(hog2)
1884                                         if GetEffect(hog2, hePoisoned) == 0 and GetEffect(hog2, heInvulnerable) == 0 and GetHealth(hog2) > 0 then
1885                                                 if (x2 > x1 - tdist) and (x2 < x1 + tdist) and (y2 > y1 - tdist) and (y2 < y1 + tdist) and GetHealth(hog2) > 0 then
1886                                                         SetEffect(hog2, hePoisoned, GetEffect(hog1, hePoisoned))
1887                                                 end
1888                                         end
1889                                 end
1890                         end
1891                 end
1892         end
1894         -- Custom poison damage
1895         if poisonDamage ~= nil then
1896                 runOnHogs(adjustPoisonDamage)
1897         end
1899         -- Kill poisoned 
1900         if poisonKills and not poisonKillDone then
1901                 if TurnTimeLeft == 0 and CurrentHedgehog ~= nil then
1902                         runOnHogs(doPoisonKill)
1903                         poisonKillDone = true
1904                 end
1905         end
1907         -- TimeBox timers
1908         if tardisReturnTurns ~= nil then
1909                 for gear, value in pairs(tardisGears) do
1910                         if GetGearPos(gear) == 4 then -- TimeBox is travelling through time
1911                                 if type(value) == "number" and value <= 0 then
1912                                         -- Special case: The turn counter is already at 0, so we can return immediately
1913                                         SetTimer(gear, 0)
1914                                         tardisGears[gear] = nil
1915                                 elseif value == "inf" then
1916                                         -- Constantly set the TimeBox timer to a high value to make sure it never reaches 0
1917                                         -- so we can manually trigger the return of the TimeBox
1918                                         SetTimer(gear, 1000000)
1919                                 end
1920                         end
1921                 end
1922         end
1924         -- Donor boxes
1925         if donorBox then
1926                 runOnHogs(checkDonors)
1927         end
1929         -- Show delayed message for modified weapon timers
1930         if setWeaponMessage then
1931                 local curAmmo = GetCurAmmoType()
1932                 if (curAmmo == amHellishBomb and hhgTimer == "manual")
1933                 or (curAmmo == amDynamite and dynamiteTimer == "manual")
1934                 or (curAmmo == amDrill and drillRocketTimer == "manual")
1935                 or (curAmmo == amBallgun and ballTimer == "manual") then
1936                         if manualTimer == nil then
1937                                 manualTimer = 5
1938                         end
1939                         FakeAmmoinfoCaption(curAmmo)
1940                         setWeaponMessage = nil
1941                 end
1942         end
1944         -- Initial switch (Experimental!)
1945         if (CurrentHedgehog ~= nil and initialSwitch == true) then
1946                 if (TurnTimeLeft > 0) and (TurnTimeLeft ~= TurnTime) and (switchStage < 5) then
1947                         hogCounter = 0
1949                         runOnHogsInTeam(countHog, GetHogTeamName(CurrentHedgehog) )
1951                         if hogCounter > 1 then
1952                                 switchStage = switchStage + 1
1954                                 if switchStage == 1 then
1955                                         tmpSwitchCount = GetAmmoCount(CurrentHedgehog, amSwitch)
1956                                         AddAmmo(CurrentHedgehog, amSwitch, 100)
1957                                 elseif switchStage == 2 then
1958                                         SetWeapon(amSwitch)
1959                                 elseif switchStage == 3 then
1960                                         SetGearMessage(CurrentHedgehog, gmAttack)
1961                                 elseif switchStage == 4 then
1962                                         switchStage = 6
1963                                         AddAmmo(CurrentHedgehog, amSwitch, tmpSwitchCount)
1964                                 end
1966                         else
1967                                 switchStage = 6
1968                         end
1969                 end
1970         end
1972         -- Random health case contents
1973         if healthCaseAmountMin ~= nil then
1974                 for h=0, #healthCaseGearsTodo do
1975                         local gear = healthCaseGearsTodo[h]
1976                         if band(GetGearPos(gear), 0x2) ~= 0 then
1977                                 SetHealth(gear, healthCaseAmountMin + GetRandom(healthCaseAmountMax-healthCaseAmountMin+1))
1978                         end
1979                 end
1980                 healthCaseGearsTodo = {}
1981         end
1984 function onGameTick()
1985         if maxPower then
1986                 if CurrentHedgehog ~= nil then
1987                         if band(GetGearMessage(CurrentHedgehog), gmAttack) ~= 0 then
1988                                 local at = GetCurAmmoType()
1989                                 local ok = false
1990                                 -- Check if this weapon is power-based
1991                                 for i=1,#powerWeapons do
1992                                         if at == powerWeapons[i] then
1993                                                 ok = true
1994                                                 break
1995                                         end
1996                                 end
1997                                 if ok then
1998                                         -- Immediately set to full launching power and force-release the attack message in the next tick
1999                                         SetGearValues(CurrentHedgehog, nil, cMaxPower - 1)
2000                                         if releaseShotInNextTick then
2001                                                 SetGearMessage(CurrentHedgehog, band(GetGearMessage(CurrentHedgehog), bnot(gmAttack)))
2002                                                 releaseShotInNextTick = false
2003                                         else
2004                                                 releaseShotInNextTick = true
2005                                         end
2006                                 end
2007                         end
2008                 end
2009         end
2011         if strategicTools then
2012                 if girderPlaced == true then
2013                         girderPlaced = false
2014                         strategicToolsEndTurn(4999, true)
2015                 end
2016         end
2018         if donorBox then
2019                 flaggedGrave = nil
2020         end
2022         if dynamiteTimer ~= nil then
2023                 for dynaGear, v in pairs(dynaGears) do
2024                         if GetTimer(dynaGear) > 10000 then
2025                                 SetTag(dynaGear, 33)
2026                         elseif GetTimer(dynaGear) == 5166 then
2027                                 SetTag(dynaGear, 0)
2028                         end
2029                 end
2030         end
2032         -- Draw range circles for changed resurrector and seduction ranges
2033         -- TODO: Also remove the original circle as soon the engine permits it (original circles are currently (0.9.21-0.9.22) hardcoded)
2034         local dontDraw = false
2036         if band(GetState(CurrentHedgehog), gstWait+gstAttacking+gstAttacked+gstAnimation+gstHHJumping) == 0 and
2037         band(GetState(CurrentHedgehog), gstHHDriven) ~= 0 and
2038         band(GetGearMessage(CurrentHedgehog), gmLeft+gmRight) == 0 and
2039         ropeGear == nil then
2040                 if resurrectorRange ~= nil and GetCurAmmoType() == amResurrector then
2041                         if rangeCircle then
2042                                 SetVisualGearValues(rangeCircle, GetX(CurrentHedgehog), GetY(CurrentHedgehog), 20, 200, 0, 0, 100, resurrectorRange, 4, 0xFFFF80EE)
2043                         else
2044                                 rangeCircle = AddVisualGear(GetX(CurrentHedgehog), GetY(CurrentHedgehog), vgtCircle, 0, true)
2045                                 SetVisualGearValues(rangeCircle, GetX(CurrentHedgehog), GetY(CurrentHedgehog), 20, 200, 0, 0, 100, resurrectorRange, 4, 0xFFFF80EE)
2046                         end
2047                 elseif seductionRange ~= nil and GetCurAmmoType() == amSeduction then
2048                         if rangeCircle then
2049                                 SetVisualGearValues(rangeCircle, GetX(CurrentHedgehog), GetY(CurrentHedgehog), 20, 200, 0, 0, 100, seductionRange, 4, 0xFF8080EE)
2050                         else
2051                                 rangeCircle = AddVisualGear(GetX(CurrentHedgehog), GetY(CurrentHedgehog), vgtCircle, 0, true)
2052                                 SetVisualGearValues(rangeCircle, GetX(CurrentHedgehog), GetY(CurrentHedgehog), 20, 200, 0, 0, 100, seductionRange, 4, 0xFF8080EE)
2053                         end
2054                 else
2055                         dontDraw = true
2056                 end
2057         else
2058                 dontDraw = true
2059         end
2060         if dontDraw then
2061                 if rangeCircle ~= nil then
2062                         DeleteVisualGear(rangeCircle)
2063                 end
2064                 rangeCircle = nil
2065         end
2068 function onGameStart()
2070         -- Place random girders
2071         if girders ~= nil then
2072                 local points = {}
2073                 -- Will be set to true if we have exceeded the tryLimit once
2074                 local dontBotherTrying = false
2075                 local buffer = 160
2076                 local tryLimit = 500
2077                 --[[ The complexitiy of this code is O(n^2) but the algorithm may terminate early
2078                 if it fails to validly place a girder ]]
2079                 for i=1,girders do
2080                         local x, y
2081                         -- Try a couple of times to place a girder without being too close to other placed girders.
2082                         local tries = 0
2083                         while tries < tryLimit do
2084                                 -- Get a random girder position
2085                                 tries = tries + 1
2086                                 x, y = GetRandom(LAND_WIDTH-buffer)+buffer, GetRandom(LAND_HEIGHT-buffer)+buffer
2087                                 if dontBotherTrying then
2088                                         --[[ To reduce the complexity, we don't go through the next loop if we have once
2089                                         exceeded the tryLimit ]]
2090                                         break
2091                                 end
2092                                 local placementOkay = true
2093                                 -- Check collisions with gears and land
2094                                 --[[ NOTE: Remove this line to make girders appear anywhere, even in land and near gears. In combination with
2095                                 EraseSprite, this can be used to create nice “girder holes”. The HWP file currently contains a legacy sprite
2096                                 for erasing (custom sprite 1).
2097                                 This MAY be a nice candidate for another parameter but it might be a bit glitchy ]]
2098                                 placementOkay = not TestRectForObstacle(x-div(buffer,2), y-div(buffer,2), x+div(buffer,2), y+div(buffer,2), false)
2099                                 -- Check whether this position it is too close to other girders
2100                                 if placementOkay then
2101                                         for p=1,#points do
2102                                                 if math.sqrt(math.pow(math.abs(points[p].x - x), 2) + math.pow(math.abs(points[p].y -y), 2)) < buffer then
2103                                                         placementOkay = false
2104                                                         break
2105                                                 end
2106                                         end
2107                                 end
2108                                 if placementOkay then
2109                                         break
2110                                 end
2111                         end
2112                         if tries >= tryLimit then
2113                                 --[[ If we have once tried too many times, we stop adding girders unless
2114                                 forceLimits is false. DontBotherTrying is used to kinda reduce the algorithmic
2115                                 complexity for the following iterations if forceLimits is false ]]
2116                                 dontBotherTrying = true
2117                                 if forceLimits then
2118                                         break
2119                                 end
2120                         end
2121                         table.insert(points, {x=x, y=y})
2122                 end
2123         
2124                 -- Place girders
2125                 for i=1,#points do
2126                         PlaceSprite(points[i].x, points[i].y, sprAmGirder, 0)
2127                 end
2128         end
2130         -- David and Goliath
2131         if davidAndGoliath then
2132                 for teamName, teamMembers in pairs(teams) do
2133                         hogCounter = 0
2134                         local goliath
2135                         local healthForGoliath = 0
2136                         runOnHogsInTeam(function(hog)
2137                                 -- Goliath
2138                                 if hogCounter == 0 then
2139                                         goliath = hog
2140                                 -- David
2141                                 else
2142                                         --[[ Remove half of the health (rounded down) ]]
2143                                         local removedHealth = div(GetHealth(hog), 2)
2144                                         healthForGoliath = healthForGoliath + removedHealth
2145                                         SetHealth(hog, GetHealth(hog) - removedHealth)
2146                                 end
2147                                 hogCounter = hogCounter + 1
2148                         end, teamName)
2150                         -- Add all health to Goliath
2151                         SetHealth(goliath, GetHealth(goliath) + healthForGoliath)
2152                 end
2153         end
2156 function onHogAttack(ammoType)
2157         if ammoType == amExtraTime then
2158                 local n = 30
2159                 if extraTime ~= nil then
2160                         n = extraTime
2161                         TurnTimeLeft = TurnTimeLeft - 30000 + extraTime
2162                 end
2163         elseif ammoType == amExtraDamage then
2164                 extraDamageUsed = true
2165         elseif ammoType == amLowGravity then
2166                 if gravity ~= nil or lowGravity ~= nil then
2167                         local base = 100
2168                         if gravity then
2169                                 base = gravity
2170                         end
2171                         if lowGravity == nil then
2172                                 lowGravity = 50
2173                         end
2174                         SetGravity(div(base * lowGravity, 100))
2175                 end
2176         end
2179 function FakeAmmoinfoCaption(ammoType)
2180         local ammoCount = GetAmmoCount(CurrentHedgehog, ammoType)
2181         local str
2183         local infStr, finStr
2185         if ammoType == amHellishBomb then
2186                 finStr = loc("Hellish hand-grenade (%d), %d sec")
2187                 infStr = loc("Hellish hand-grenade, %d sec")
2188         elseif ammoType == amDynamite then
2189                 finStr = loc("Dynamite (%d), %d sec")
2190                 infStr = loc("Dynamite, %d sec")
2191         elseif ammoType == amDrill then
2192                 finStr = loc("Drill rocket (%d), %d sec")
2193                 infStr = loc("Drill rocket, %d sec")
2194         elseif ammoType == amBallgun then
2195                 finStr = loc("Ballgun (%d), %d sec")
2196                 infStr = loc("Ballgun, %d sec")
2197         end
2199         if ammoCount == 100 then
2200                 str = string.format(infStr, manualTimer)
2201         else
2202                 str = string.format(finStr, GetAmmoCount(CurrentHedgehog, ammoType), manualTimer)
2203         end
2204         AddCaption(str, GetClanColor(GetHogClan(CurrentHedgehog)), capgrpAmmoinfo)
2207 function onTimer(timer)
2208         manualTimer = timer
2209         local curAmmo = GetCurAmmoType()
2210         if CurrentHedgehog ~= nil then
2211                 if (curAmmo == amHellishBomb and hhgTimer == "manual")
2212                 or (curAmmo == amDynamite and dynamiteTimer == "manual")
2213                 or (curAmmo == amDrill and drillRocketTimer == "manual")
2214                 or (curAmmo == amBallgun and ballTimer == "manual") then
2215                         FakeAmmoinfoCaption(curAmmo)
2216                 end
2217         end
2220 function onSetWeapon()
2221         setWeaponMessage = true
2223 onSlot = onSetWeapon
2225 function onAttack()
2226         -- Show informational messages about remaining shots (if finite) for certain weapons
2227         -- TODO: Rewrite this part when the engine creates similar messages on its own
2228         if birdyGear ~= nil then
2229                 if birdyEggs == "inf" then
2230                         -- We kinda “fake” infinity by making sure we never run out of eggs
2231                         SetFlightTime(birdyGear, 9001)
2232                 else
2233                         -- Finite eggs: Show number of remaining eggs
2234                         AddCaption(string.format(loc("Eggs left: %d"), math.max(0, GetFlightTime(birdyGear)-1)), 0xFFFFFFFF, capgrpAmmostate)
2235                 end
2236         end
2237         if rcPlaneGear ~= nil then
2238                 if rcPlaneBombs == "inf" then
2239                         -- We kinda “fake” infinity by making sure we never run out of bombs
2240                         SetHealth(rcPlaneGear, 9001)
2241                 else
2242                         -- Finite bombs: Show number of remaining bombs
2243                         AddCaption(string.format(loc("Bombs left: %d"), math.max(0, GetHealth(rcPlaneGear)-1)), 0xFFFFFFFF, capgrpAmmostate)
2244                 end
2245         end
2246         if kamikazeGear ~= nil then
2247                 if kamikazeTrigger then
2248                         SetHealth(kamikazeGear, 0)
2249                 end
2250         end
2253 function onNewTurn()
2254         gameStarted = true
2255         releaseShotInNextTick = false
2256         manualTimer = nil
2257         extraDamageUsed = false
2258         if buildLimit ~= nil then
2259                 stuffBuilt = 0
2260                 if buildRange == "inf" then
2261                         SetMaxBuildDistance(0)
2262                 elseif buildRange ~= nil then
2263                         SetMaxBuildDistance(buildRange)
2264                 else
2265                         SetMaxBuildDistance()
2266                 end
2267         end
2269         -- Initialize initial switch
2270         if initialSwitch then
2271                 switchStage = 0
2272         end
2274         -- No jumping mode
2275         if noJumping then
2276                  SetInputMask(band(GetInputMask(), bnot(gmLJump + gmHJump)))
2277         end
2279         -- Disable knocking
2280         if noKnock then
2281                 runOnHogs(disableKnocking)
2282         end
2284         -- Set gravity
2285         if gravity ~= nil then
2286                 SetGravity(gravity)
2287         end
2289         -- Adjust wind
2290         if fairWind or maxWind ~= nil then
2291                 local newWind
2292                 if maxWind ~= nil then
2293                         if maxWind == 0 then
2294                                 newWind = 0
2295                         else
2296                                 newWind = GetRandom(maxWind*2+1) - maxWind
2297                         end
2298                 else
2299                         newWind = GetRandom(201)-100
2300                 end
2302                 if fairWind then
2303                         if lastRound == nil or lastRound ~= TotalRounds then
2304                                 SetWind(newWind)
2305                         end
2306                 elseif maxWind ~= nil then
2307                         SetWind(newWind)
2308                 end
2309         end
2312         -- Sudden Death specials
2313         if TotalRounds == SuddenDeathTurns then
2314                 if sdState == 1 then
2315                         sdState = 2
2316                         -- Emulate Sudden Death effect if it is disabled by the game scheme
2317                         if HealthDecrease == 0 and WaterRise == 0 and (sdPoison or sdOneHP) then
2318                                 AddCaption(loc("Sudden Death!"))
2319                                 PlaySound(sndSuddenDeath)
2320                         end
2321                         if sdPoison then
2322                                 runOnHogs(function(hog)
2323                                         if poisonDamage ~= nil then
2324                                                 SetEffect(hog, hePoisoned, poisonDamage)
2325                                         else
2326                                                 SetEffect(hog, hePoisoned, 5)
2327                                         end
2328                                 end)
2329                         end
2330                         if sdOneHP then
2331                                 runOnHogs(function(hog)
2332                                         SetHealth(hog, 1)
2333                                 end)
2334                         end
2335                 else
2336                         sdState = 1
2337                 end
2338         end
2340         -- Reset “poison kills” modifier for this turn
2341         if poisonKills then
2342                 poisonKillDone = false
2343         end
2345         -- Force-return TimeBoxes when using turn-based return setting
2346         if tardisReturnTurns ~= nil then
2347                 for gear, value in pairs(tardisGears) do
2348                         if GetGearPos(gear) == 4 and type(value) == "number" then
2349                                 tardisGears[gear] = tardisGears[gear] - 1
2350                                 if tardisGears[gear] <= 0 then
2351                                         SetTimer(gear, 0)
2352                                         tardisGears[gear] = nil
2353                                 end
2354                         end
2355                 end
2356         end
2358         -- Count rounds
2359         lastRound = TotalRounds
2362 function onGirderPlacement(frameIdx, x, y)
2363         if(buildLimit ~= nil) then
2364                 stuffBuilt = stuffBuilt + 1
2365                 AddCaption(string.format(loc("Buildings left: %d"), buildLimit - stuffBuilt), 0xFFFFFFFF, capgrpAmmostate)
2366                 if stuffBuilt >= buildLimit then
2367                         SetMaxBuildDistance(1)
2368                 end
2369         end
2370         if strategicTools then
2371                 strategicToolsHandlePlacement()
2372         end
2375 onRubberPlacement = onGirderPlacement
2377 --[[ Helper functions ]]
2378 function hedgepot()
2379         local default_percent_min = 33
2380         local default_percent_max = 300
2381         local mutators = {
2382                 { type = "GameFlag", name = "gfLaserSight" },
2383                 { type = "GameFlag", name = "gfInvulnerable" },
2384                 { type = "GameFlag", name = "gfResetHealth" },
2385                 { type = "GameFlag", name = "gfVampire" },
2386                 { type = "GameFlag", name = "gfKarma" },
2387                 { type = "GameFlag", name = "gfShareAmmo" },
2388                 { type = "GameFlag", name = "gfResetWeps" },
2389                 { type = "GameFlag", name = "gfPerHogAmmo" },
2390                 { type = "GameFlag", name = "gfBottomBorder" },
2391                 { type = "CoreNumber", name = "HealthCaseProb", absolute_min = 0, absolute_max = 100 },
2392                 { type = "CoreNumber", name = "DamagePercent" },
2393                 { type = "CoreNumber", name = "MinesNum" },
2394                 { type = "CoreNumber", name = "MineDudPercent", absoulte_min = 0, absolute_max = 100 },
2395                 { type = "CoreNumber", name = "Explosives" },
2396                 { type = "GameFlag", name = "gfVampire" },
2397                 { type = "GameFlag", name = "gfKarma" },
2398                 { type = "GameFlag", name = "gfMoreWind" },
2399                 { type = "truth", name = "airFlamesHurt" },
2400                 { type = "truth", name = "poison" },
2401                 { type = "truth", name = "noJump" },
2402                 { type = "truth", name = "noKnock" },
2403                 { type = "truth", name = "teamCure" },
2404                 { type = "truth", name = "shareHealth" },
2405                 { type = "truth", name = "davidAndGoliath" },
2406                 { type = "truth", name = "initialSwitch" },
2407                 { type = "truth", name = "strategicTools" },
2408                 { type = "truth", name = "sdPoison" },
2409                 { type = "truth", name = "sdOneHP" },
2410                 { type = "truth", name = "poisonKills" },
2411                 { type = "truth", name = "epidemic" },
2412                 { type = "truth", name = "maxPower" },
2413                 { type = "truth", name = "fairWind" },
2414                 { type = "enum", name = "stickyFlames", enum = { "always", "never" } },
2415                 { type = "num", name = "maxWind", absolute_min = 0, absolute_max = 100, default = 100 },
2416                 { type = "num", name = "gravity", absolute_min = 10, absolute_max = 200, default = 100 },
2417                 { type = "num", name = "poisonDamage", absolute_min = 1, absolute_max = 20, default = 5 },
2418                 { type = "num", name = "barrelHealth", default = 60 },
2419                 { type = "num", name = "hogFriction", absolute_min = -10, absolute_max = 10, default = 0 },
2420                 { type = "num", name = "buildLimit", absolute_min = 1, absolute_max = 5, default = 2 },
2421         }
2422         local util_weapons_modifiers =
2423         {
2424                 { type = "truth", name = "kamikazeTrigger" },
2425                 { type = "truth", name = "pickHammerStraightDown", text=loc("Pick hammer (no skewing)") },
2426                 { type = "enum", name = "MinesTime", enum = { -1000, 0, 1000, 2000, 3000, 4000, 5000 } },
2427                 { type = "enum", name = "tardisReturnTurns", enum = { 0, 1, 3, 5, "inf", } },
2428                 { type = "set", name = "saucerFuel", value = "inf" },
2429                 { type = "set", name = "birdyEnergy", value = "inf" },
2430                 { type = "set", name = "birdyEggs", value = "inf", text=loc("Birdy eggs (infinite)") },
2431                 { type = "set", name = "rcPlaneBombs", value = "inf", text=loc("RC Plane bombs (infinite)") },
2432                 { type = "set", name = "hhgTimer", value = "manual", text=loc("Hellish hand-grenade timer (manual)")},
2433                 { type = "set", name = "ballTimer", value = "manual", text=loc("Ball timer (manual)")},
2434                 { type = "set", name = "buildRange", value = "inf" },
2435                 { type = "num", name = "buildRange", default = 256, text=loc("Construction/Rubber range (%d%%)"), numtype="perc" },
2436                 { type = "num", name = "saucerFuel", default = 2000 },
2437                 { type = "num", name = "birdyEnergy", default = 2000 },
2438                 { type = "num", name = "rcPlaneTimer", default = 15000},
2439                 { type = "num", name = "freezerFuel", default = 1000, text=loc("Freezer fuel (%d%%)"), numtype="perc"},
2440                 { type = "num", name = "flamethrowerFuel", default = 500, text=loc("Flamethrower fuel (%d%%)"), numtype="perc"},
2441                 { type = "num", name = "cakeTimer", default = 2048, text=loc("Cake walking time (%d%%)"), numtype="perc"},
2442                 { type = "num", name = "kamikazeRange", default = 2048, text=loc("Kamikaze range (%d%%)"), numtype="perc"},
2443                 { type = "num", name = "ballTimer", default = 5000, text=loc("Ball timer (%.1fs)"),numtype="time"},
2444                 { type = "num", name = "pickHammerTimer", default = 4000, text=loc("Pickhammer time (%.1fs)"), numtype="time"},
2445                 { type = "num", name = "blowTorchTimer", default = 7500, text=loc("Blowtorch time (%.1fs)"), numtype="time"},
2446                 { type = "num", name = "birdyEggs", default = 2, text=loc("Birdy eggs (%d)"), numtype="abs"},
2447                 { type = "num", name = "rcPlaneBombs", default = 3, text=loc("RC Plane bombs (%d)"), numtype="abs"},
2448                 { type = "num", name = "ballGunBalls", default = 51, text=loc("Number of ball gun balls (%d)"), numtype="abs"},
2449                 { type = "num", name = "pianoBounces", default = 5, text=loc("Piano terrain bounces (%d)"), numtype="abs"},
2450                 { type = "num", name = "airAttackBombs", default = 6, text=loc("Air attack bombs (%d)"), numtype="abs"},
2451                 { type = "num", name = "mineStrikeMines", default = 6, text=loc("Mine strike mines (%d)"), numtype="abs"},
2452                 { type = "num", name = "drillStrikeDrills", default = 6, text=loc("Drill strike drills (%d)"), numtype="abs"},
2453                 { type = "num", name = "deagleStrength", default = 50, text=loc("Desert Eagle digging strength (%d%%)"), numtype="perc"},
2454                 { type = "num", name = "sniperStrength", default = 50, text=loc("Sniper rifle digging strength (%d%%)"), numtype="perc"},
2455                 { type = "num", name = "hammerStrength", default = 125, text=loc("Hammer digging strength (%d%%)"), numtype="perc"},
2456                 { type = "num", name = "extraTime", default = 30000, absolute_min = 15000, absolute_max = 45000 },
2457                 { type = "num", name = "lowGravity", default = 50},
2458                 { type = "num", name = "drillRocketTimer", default = 5000, text=loc("Drill rocket timer (%.1fs)"), numtype="time"},
2459                 { type = "num", name = "hhgTimer", default = 5000, text=loc("Hellish hand-grenade timer (%.1fs)"), numtype="time"},
2460         }
2461         local conflicts = {
2462                 sdPoison = { "sdOneHP" },
2463                 sdOneHP = { "sdPoison" },
2464                 gfInvulnerable = { "gfVampire", "gfKarma", "teamCure", "poison", "sdPoison", "shareHealth", "gfResetHealth" },
2465                 gfVampire = { "gfInvulnerable" },
2466                 gfKarma = { "gfInvulnerable" },
2467                 teamCure = { "gfInvulnerable" },
2468                 poison = { "gfInvulnerable" },
2469                 sdPoison = { "gfInvulnerable" },
2470                 shareHealth = { "gfInvulnverable" },
2471                 gfResetHealth = { "gfInulnverable" },
2472                 gfArtillery = { "noJump" },
2473                 noJump = { "gfArtillery " },
2474         }
2476         local text_counter = 0
2477         local texts = {}
2479         local function roll(reel)
2480                 local pickID = GetRandom(#reel)+1
2481                 local pick = reel[pickID]
2482                 if pick.type == "GameFlag" then
2483                         if GetGameFlag(_G[pick.name]) == true then
2484                                 DisableGameFlags(_G[pick.name])
2485                         else
2486                                 EnableGameFlags(_G[pick.name])
2487                         end
2488                 elseif pick.type == "CoreNumber" then
2489                         if pick.absolute_min ~= nil and pick.absolute_max ~= nil then
2490                                 _G[pick.name] = GetRandom(pick.absolute_max - pick.absolute_min + 1) + pick.absolute_min
2491                         else
2492                                 _G[pick.name] = div((GetRandom(default_percent_max - default_percent_min) + default_percent_min) * _G[pick.name], 100)
2493                         end
2494                 elseif pick.type == "enum" then
2495                         local e = GetRandom(#pick.enum - 1)+1
2496                         _G[pick.name] = pick.enum[e]
2497                 elseif pick.type == "truth" then
2498                         _G[pick.name] = true
2499                 elseif pick.type == "num" then
2500                         if pick.absolute_min and pick.absolute_max then
2501                                 _G[pick.name] = GetRandom(pick.absolute_max - pick.absolute_min + 1) + pick.absolute_min
2502                         else
2503                                 _G[pick.name] = div((GetRandom(default_percent_max - default_percent_min) + default_percent_min) * pick.default, 100)
2504                         end
2505                 elseif pick.type == "set" then
2506                                 _G[pick.name] = pick.value
2507                 end
2508                 if pick.text ~= nil then
2509                         text_counter = text_counter + 1
2510                         if pick.type == "num" then
2511                                 local arg
2512                                 if pick.numtype == "abs" then
2513                                         arg = round(_G[pick.name])
2514                                 elseif pick.numtype == "time" then
2515                                         arg = _G[pick.name]/1000
2516                                 else
2517                                         arg = round(_G[pick.name]/pick.default*100)
2518                                 end
2519                                 table.insert(texts, string.format(pick.text, arg))
2520                         elseif pick.type == "set" then
2521                                 table.insert(texts, string.format(pick.text))
2522                         end
2523                 end
2524                 table.remove(reel, pickID)
2525         end
2527         -- “Roll” the “reels”
2528         local rolls_1 = GetRandom(5)+1
2529         local weapons_mod = GetRandom(100)
2530         local rolls_2 = 0
2532         for i=1,rolls_1 do
2533                 roll(mutators)
2534         end
2535         if weapons_mod < 20 then
2536                 roll(util_weapons_modifiers)
2537         end
2539         return text_counter, texts
2542 -- Functions for strategic tools
2543 function strategicToolsEndTurn(baseRetreatTime, girderHack)
2544         SetState(CurrentHedgehog, bor(GetState(CurrentHedgehog), gstAttacked))
2545         local retreatTime
2546         retreatTime = div(baseRetreatTime * GetAwayTime, 100)
2547         
2548         -- This avoids the “Hurry up!” taunt from being played
2549         if retreatTime == 5000 then retreatTime = 5001 end
2551         if girderHack then
2552                 --[[ This is an ugly hack which is needed after placement of a girder.
2553                         It is not neccessary for the other weapons, but it works for those
2554                         anyways.
2555                         If a girder or rubber is placed, and it was not the last one,
2556                         the player *actually can* still continue to select new weapons.
2557                         This hack forces Hedgewars to un-select the girder or rubber
2558                         so the hog can’t continue to select a new weapon.
2560                         FIXME: Get rid of this hack as soon as this weirdness is fixed in
2561                         Hedgewars.
2562                 ]]
2563                 SetWeapon(amSkip)
2565                 --[[ This is another hack to hide the previous message caused by selecting
2566                         skip. ]]
2567                 AddCaption(loc("Retreat!"), 0xFFFFFFFF, capgrpAmmoinfo)
2568         end
2570         TurnTimeLeft = retreatTime
2573 function strategicToolsHandlePlacement()
2574         girderPlaced = true
2575         strategicToolsEndTurn(5000, true)
2578 -- Handle modified poison damage
2579 function adjustPoisonDamage(hog)
2580         if GetEffect(hog, hePoisoned) > 0 then
2581                 if GetEffect(hog, hePoisoned) ~= poisonDamage then
2582                         SetEffect(hog, hePoisoned, poisonDamage)
2583                 end
2584         end
2587 -- Kill poisoned hogs with low health 
2588 function doPoisonKill(hog)
2589         local poison = GetEffect(hog, hePoisoned)
2590         local health = GetHealth(hog)
2591         if poison > 0 then
2592                 if health - poison <= 0 then
2593                         SetHealth(hog, 0)
2594                 end
2595         end
2598 -- Hogs of Steel mode
2599 function disableKnocking(gear)
2600         SetState(gear, bor(GetState(gear), gstNotKickable))
2603 -- Drop donor box if it was the last hog or the king
2604 function checkDonors(hog)
2605         if band(GetState(hog), gstHHDeath) ~= 0 and band(GetState(hog), gstDrowning) == 0 then
2606                 if GetGameFlag(gfKing) and hogNumbers[GetHogTeamName(hog)][1] == hog then
2607                         donors[hog] = true
2608                 else
2609                         hogCounter = 0
2610                         runOnHogsInTeam(countHog, GetHogTeamName(hog))
2611                         if hogCounter == 1 then
2612                                 donors[hog] = true
2613                         end
2614                 end
2615         end
2618 -- Misc.
2619 -- Used for tracking functions to count hogs in a team etc.
2620 function countHog(gear)
2621         hogCounter = hogCounter + 1
2624 function round(num)
2625         if num >= 0 then
2626                 return math.floor(num + 0.5)
2627         else
2628                 return math.ceil(num - 0.5)
2629         end
2632 -- Truncate string input number (safe math.floor(tonumber(x)) alternative)
2633 function truncStrNum(strNum)
2634         if strNum == nil then return nil end
2635         local s = string.match(strNum, "(%d*)")
2636         if s ~= nil then
2637                 return tonumber(s)
2638         else
2639                 return nil
2640         end
2643 -- Changes the ammo count of ammoType of the hog
2644 -- It adds addCount to the hog
2645 -- If addCount is 100, the hog gets infinite ammo
2646 -- For finite numbers, the hog can never end up with more than 99 of the same ammo
2647 function modAmmo(hog, ammoType, addCount)
2648         local currentCount = GetAmmoCount(hog, ammoType)
2649         if showMessage == nil then showMessage = true end
2650         if currentCount ~= 100 and addCount ~= 100 then
2651                 local newCount 
2652                 if addCount == 100 then
2653                         newCount = 100
2654                 else
2655                         newCount = math.max(0, math.min(99, currentCount + addCount))
2656                 end
2657                 AddAmmo(CurrentHedgehog, ammoType, newCount)
2658         end