1
2----------------------
3-- WALL TO WALL 0.7
4----------------------
5-- a shoppa minigame
6-- by mikade
7
8-- feel free to add map specific walls to LoadConfig, or post additional
9-- wall suggestions on our forum at: https://www.hedgewars.org/forum
10
11----------------
12--0.1
13----------------
14-- concept test
15
16----------------
17--0.2
18----------------
19-- unhardcoded turntimeleft, now uses shoppa default of 45s
20-- changed some things behind the scenes
21-- fixed oooooold radar bug
22-- added radar / script support for multiple crates
23-- tweaked weapons tables
24-- added surfing and changed crate spawn requirements a bit
25
26----------------
27--0.3
28----------------
29-- stuffed dirty clothes into cupboard
30-- improved user feedback
31-- added/improved experimental config system, input masks included :D
32
33----------------
34--0.4
35----------------
36-- for version 0.9.18, now detects border in correct location
37-- fix 0.3 config constraint
38-- remove unnecessary vars
39-- oops, remove hardcoding of minesnum,explosives
40-- ... and unhardcode turntime (again)... man, 30s is hard :(
41-- move some initialisations around
42-- numerous improvements to user feedback
43-- walls disappear after being touched
44-- added backwards compatibility with 0.9.17
45
46----------------
47--0.5
48----------------
49-- Support for multiple sets of walls per map (instead of “all or nothing”)
50-- Ropes, ShoppaKing, ShoppaHell and ShoppaNeon can now be played with the classic left and right walls
51-- New wall sets for Ropes, ShoppaNeon, ShoppaDesert, ShoppaWild, ShoppaKing and ShoppaHell, and more.
52-- Basic support for a bunch of Shoppa maps
53-- Alternative configuration method with Script parameter
54-- Possible to set max. number of weapons in game (script parameter only)
55-- Possible to set number of crates per turn
56-- Menu can be disabled (with script parameter) for insant game start
57-- WxW is now fully functional even without a map border.
58-- WxW now allows for almost all game modifiers and game settings to be changed
59-- More sound effects
60-- No smoke when hog is near near a WxW wall but Walls Before Crate rule is not in place
61-- More readable mission display after configuration has been accepted
62-- Hide “Surf Before Crate” setting if surfing is disabled for this map, or the bottom is active and water never rises
63-- Hide walls setting if script does not provide walls for map yet
64-- Bugfix: Other player was able to change the menu config in the short period before the first "turn"
65-- Lots of refactoring
66
67----------------
68--0.6
69----------------
70-- Bugfix: 2 crates spawned at the 1st turn if script parameter was set to “menu=false, walls=none” or similar
71-- Bugfix: Annoying faulty error message appeared when hitting attack when on a rope with a weapon selected
72
73
74----------------
75--0.7
76----------------
77-- To enforce the rules more strictly, all crates will be frozen at turn start if WBC or SBC rule is in place.
78--	The crates are unfrozen if you met the crate criteria (that is, surfed and/or bounced off all walls).
79--      Frozen crates can't be collected and appear as small white dots in the radar.
80-- Add support for the “Crate Before Attack” rule
81-- Add support for the “All But Last” rule
82-- Add support for the “Kill The Leader” rule
83-- Allow toggling crate radar with “switch hog” key while roping
84-- The game continues now with the first team after the menu has been closed (rather than the second team)
85
86----------------
87--TODO
88----------------
89-- achievements / try detect shoppa moves? :|
90-- maybe add ability for the user to place zones like in Racer?
91-- add more hard-coded values for specific maps
92
93
94--[[
95# CONFIGURATION
96
97By default, this script is easily configured via the in-game menu. The player of the first team can choose the rules and
98required walls (or none at all). After accepted, the game will start with the second team (!).
99
100= SCRIPT PARAMETER =
101
102Using the script parameter is optional, it mostly is just an alternative way for configuration and for convenience
103reasons, so often-used configurations can be saved and loaded.
104
105The script parameter is specified as a comma-sperated list of “key=value” pairs (see examples below).
106
107Truth values can be set true or false, and numeric values always use a whole number.
108
109== Basic parameters ==
110
111key		default	description
112----------------------------------------
113menu		true	Show configuration menu at the beginning. If no menu is used, a random wall set is used (see wall filters below)
114SBC		false	Surf Before Crate: Player must bounce off the water (“surfing”) before crates can be collected
115AFR		false	Attack From Rope: Players must attack from the rope. Weapons which can't be fired from rope are removed
116CBA		false	Crate Before Attack: Player must collect at least one crate before attacking
117attackrule	off	If present, enable one of the attack rules “ABL” or “KTL”:
118			ABL: All But Last: Players must not only attack the team with the lowest total health
119			KTL: Kill The Leader: If players hit some enemy hedgehog, at least one of them must be a hog from
120			the team with the highest total health.
121			The ABL and KTL rules exclude each other. If a player breaks the rule (if enabled), he must
122			skip in the next round.
123SW		false	Super Weapons: A few crates may contain very powerful weapons (melon, hellish grenade, RC plane, ballgun)
124maxcrates	12	Number of crates which can be at maximum in the game (limited to up to 100 to avoid lag)
125cratesperturn	1	Number of crates which appear each turn
126
127== Advanced parameters ==
128
129Wall filters: The following parameters allow you to filter out wall sets based on their type and number of walls.
130If this is used together with the menu, the filtered wall sets can't be selected. Without a menu, the wall set
131will be randomly selected among the wall sets that meet all criteria.
132
133If the criteria filter out all available wall sets of the map, the game is played without the Walls Before Crate rule.
134
135parameter	default	description
136----------------------------------------
137walls		N/A
138
139Permitted values:
140- leftright:		The left and right part of the border. Traditional W2W-style.
141- roof:			Only the top part of the border
142- leftrightroof:	Combination of the two above
143- inside:		Map-specific wall set where all walls are part of the terrain
144- mixed:		Map-specific wall set where some walls are part of the terrain, and some are part of the map border
145- none:			No walls required.
146- all:			Shorthand: All wall sets are allowed.
147
148Combination of multiple types is possible by concatenating the names with plus signs (see examples below).
149
150
151Restrict wall numbers: With the following parameters you can restrict the pool of wall sets to only those with a certain
152number of walls. Note that 2 walls are the most common type of wall set, as this is often available by default.
153
154parameter	default	description
155----------------------------------------
156minwalls	N/A	Filter out wall sets with less than this
157maxwalls	N/A	Filter out wall sets with more than this
158
159wallsnum	N/A	Shorthand: Combintion of minwalls and maxwalls if they are the equal.
160
161
162== Examples ==
163
164
165SBC=true
166--> Keep the menu, enable Surf Before Crate by default (if available).
167
168SBC=true, menu=false
169--> Enable Surf Before Crate (if available) and use the defaul walls set.
170
171AFR=true, menu=false, wallsnum=2
172--> Attack From Rope rule active, and use a random wall set with 2 walls
173
174menu=false, walls=leftright
175--> Always use the classic left/right wall set automatically. Traditional W2W-style.
176
177walls=none, menu=false
178--> Like classic Shoppa
179
180walls=leftright+inside+mixed, menu=false
181--> Randomly use either the left/right wall set, an Inside or Mixed wall set.
182
183
184
185= MORE GAME SCHEME CONFIGURATION =
186You can almost set everything in the game scheme freely, and the script will work just fine together with it.
187Feel free to experiment a bit.
188The only exception are the crate frequencies. Setting them has no effect, crates are handled uniquiely in this game.
189
190At this stage, the script does not allow for custom weapon sets.
191]]
192
193
194
195-----------------------------
196-- GO PONIES, GO PONIES, GO!
197-----------------------------
198
199HedgewarsScriptLoad("/Scripts/Locale.lua")
200HedgewarsScriptLoad("/Scripts/Tracker.lua")
201HedgewarsScriptLoad("/Scripts/Utils.lua")
202HedgewarsScriptLoad("/Scripts/Params.lua")
203
204-- HARDCODED values
205local ammoTypesNum = 58	-- number of weapon types (permanent TODO: Check this number for each Hedgewars version)
206local PlacementTime = 15000
207
208-- menu stuff
209local menuIndex = 1
210local menu = {}
211local preMenuCfg
212local postMenuCfg
213
214--[[ WxW preparation phase.
2150 = Game not started yet
2161 = Configuration phase
2172 = Hedgehog placement phase
218100 = Game phase
219]]
220local roundN = 0
221
222-- Used to select one of the wall sets
223-- 0: no walls
224-- 1 and above: ID of wall sets
225local wallSetID = 0
226
227-- Store the wall sets here
228local wallSets = {}
229
230-- Wall set types and wall number limits for filtering
231local allWallSetTypes = {"roof", "leftright", "leftrightroof", "mixed", "inside"}
232local allowedWallSetTypes = {roof=true, leftright=true, leftrightroof=true, mixed=true, inside=true}
233local minWalls, maxWalls = nil, nil
234
235-- config and wall variables
236local useMenu = true
237local AFR = false		-- Attack From Rope
238local WBC = true		-- Wall(s) Before Crate, will later only be set again in script parameter
239local CBA = false		-- Crate Before Attack
240local attackRule = nil		-- Either nil, "KTL" (Kill The Leader) or "ABL" (All But Last)
241local allowCrazyWeps = false	-- Super weapons
242local requireSurfer = false	-- Surf Before Crate
243local crateSpawned = false	-- Has the crate (or crates) been spawned in this turn yet?
244local cratesPerTurn = 1		-- How many crates appear per turn (respects crate limit)
245local maxCrates = 12		-- default crate limit, can be configured with params
246local maxCratesHard = 100	-- "hard" crate limit, to avoid extreme lagging due to many crates
247local crateGearsInGame = 0
248local wX = {}
249local wY = {}
250local wWidth = {}
251local wHeight = {}
252local wTouched = {}
253local wallsLeft = 0
254
255local hasSurfed = false
256local allWallsHit = false
257local crateCollected = false
258
259-- ABL and KTL stuff
260local teamNames = {}		-- List of all teams
261local teamsAttacked = {}	-- List of attacked teams (in this turn)
262local lastTeam = nil		-- Team with the least health. Determined only at start of turn. If it's a tie, use nil.
263local leaderTeam = nil		-- Team with the most health. Determined only at start of turn. If it's a tie, use nil.
264local runnerUpTeam = nil	-- Team with the second-most health
265local previousTeam = nil	-- Remember the name of the team in the previous turn
266
267local gTimer = 1
268local effectTimer = 1
269
270local ropeG = nil
271local allowCrate = true
272local crates = {}
273
274-- Variables for place hedgehogs mode
275local hogCount = 0		-- Used to detect the end of the hog placement phase
276local turnsCount = 0
277
278-- crate radar vars
279
280-- Set the initial radar mode here
281-- 0: Radar is always active
282-- 1: Radar is only active shortly after crate spawn
283-- 2: Radar is disabled
284local radarMode = 0
285
286local rCirc = {}
287local rAlpha = 255
288local rPingTimer = 0
289local m2Count = 0
290
291local weapons = {}
292
293local crazyWeps = {amWatermelon, amHellishBomb, amBallgun, amRCPlane}
294
295local groundWeps = 	{amBee, amShotgun,amDEagle,amFirePunch, amWhip,
296				amPickHammer, amBaseballBat, amCake,amBallgun,
297				amRCPlane, amSniperRifle, amBirdy, amBlowTorch,
298				amFlamethrower, amMortar, amHammer}
299
300local ropeWeps = {amGrenade, amClusterBomb, amBazooka, amMine, amDynamite,
301				amWatermelon, amHellishBomb, amDrill, amMolotov,
302				amSMine, amGasBomb}
303
304local msgColorTech = 0xFFBA00FF
305local msgColorWarn = 0xFF4000FF
306
307-- 0.9.18+ extra custom data for preset maps
308local MapList =
309	{
310	--name,					surfer, roof, 	LRwalls
311	{"Alien",				true, 	true,  true},
312	{"Atlantis Shoppa",			true, 	true,  true},
313	{"BasketballField",			false,  false, false},
314	{"BattleCity_v1",			true,	true, true},
315	{"BIGshoppa",				true,	true, true},
316	{"BambooPlinko",			true,	false, true},
317	{"BoatWxW",				true,	true,  true},
318	{"BrickShoppa",				false, 	false, true},
319	{"BubbleFlow",				true, 	false, true},
320	{"Citrouille",				true, 	true,  true},
321	{"Cave",				false, 	false, true},
322	{"Cheese_Ropes", 			false, 	true,  true},
323	{"CookieShoppa", 			true, 	false, true},
324	{"CrossRopes",				false,	false, true},
325	{"FutuShoppa",				true,	false, true},
326	{"Garden",				false,	false, true},
327	{"Glass Shoppa",			true, 	false, true},
328	{"GlassShoppa2",			true, 	false, true},
329	{"HardIce",      			false, 	false, true},
330	{"Industrial",       			false,	false, true},
331	{"Islands",       			true, 	false, true},
332	{"IslandsFlipped",     			true, 	false, true},
333	{"IslandsRearranged",  			true, 	false, true},
334	{"Hedgelove",       			true, 	false, true},
335	{"HellishRopes",       			false, 	false, true},
336	{"Hedgeland_v1",			true,	false, true},
337	{"HeyLandShoppa",			false,	false, true},
338	{"NeonStyle",       			false, 	false, true},
339	{"MaskedRopes",       			false, 	false, true},
340	{"Octorama",       			false, 	false, true},
341	{"Octoropisloppaking0.4",		true,   true,  true},
342	{"Pacman_v2",       			true,   false, true},
343	{"Purple",       			false, 	true,  true},
344	{"Purple_v2",       			false, 	true,  true},
345	{"RacerPlayground1",			false,  true,  true},
346	{"RacerPlayground2",			false,  true,  true},
347	{"RacerPlayground3",			false,  true,  true},
348	{"RacerPlayground4",			false,  true,  true},
349	{"red vs blue - Castle",     		true, 	false, true},
350	{"red vs blue - castle2",     		true, 	false, true},
351	{"red vs blue - True Shoppa Sky",	true,	false, true},
352	{"Ropes",       			false, 	true, true},
353	{"RopeLikeAKingInHellWithNeon",		false, 	true,  true},
354	{"Ropes Flipped",      			false, 	false, true},
355	{"Ropes Rearranged",      		false, 	false, true},
356	{"RopesRevenge0.1",    			false, 	true,  true},
357	{"RopesRevenge Flipped",    		true, 	false, true},
358	{"RopesThree",      			false, 	false, true},
359	{"RopesTwo",      			false, 	false, true},
360	{"Ruler",	      			false, 	false, true},
361	{"SandShoppa",				false,	false, true},
362	{"ShapeShoppa1.0",     			true, 	false, true},
363	{"ShapeShoppa Darkhow",      		true, 	false, true},
364	{"SheepyShoppa_v2",      		true, 	false, true},
365	{"shopppa",				false,  true,  true},
366	{"ShoppaCave2",      			true, 	false, true},
367	{"ShoppaChallenge",    			false, 	true, true},
368	{"ShoppaDesert",    			false, 	false, true},
369	{"ShoppaEvoRope_v1",			true, 	false, true},
370	{"ShoppaFun",      			true, 	false, true},
371	{"ShoppaFun2",      			true, 	false, true},
372	{"ShoppaGolf",      			false, 	false, true},
373	{"ShoppaHalloween",    			false, 	false, true},
374	{"ShoppaHell",      			false,	true,  false},
375	{"ShoppaHellFlipped",  			true,	true,  false},
376	{"ShoppaHellRemake",			false,	true,  false},
377	{"ShoppaKing",       			false, 	true, false},
378	{"ShoppaKingFlipped",      		true, 	false, false},
379	{"ShoppaKingSideways",      		true, 	true,  false},
380	{"ShoppaMeme",				false,	true, false},
381	{"ShoppaNeon",       			false, 	false, true},
382	{"ShoppaNeonFlipped",			true, 	false, true},
383	{"ShoppaOnePiece2",			false, 	true, false},
384	{"ShoppaQuotes2",			false,  true,  true},
385	{"ShoppaRainbow",			false,  false, false},
386	{"ShoppaRadigme",			false,  true,  true},
387	{"ShoppaSilhouette",			false,  false, true},
388	{"ShoppaSpace",				true,   false, true},
389	{"ShoppaSea",				true,  false, false},
390	{"ShoppaShapex_v1",			false,  true, true},
391	{"ShoppaSparkle",			true,  true, true},
392	{"ShoppaSky",				false,  false, true},
393	{"ShoppaSky2",				true,  false, true},
394	{"ShoppaSsion",				false,  false, true},
395	{"ShoppaStyle2",			true,  false, true},
396	{"ShoppaThology",			false,  false, true},
397	{"ShoppaTournament2012",		false,  false, true},
398	{"ShoppaWild",				false,  false, true},
399	{"Shoppawall",				false,  false, false},
400	{"ShoppaWall2",				false,  false, false},
401	{"ShBall",				false,  true, false},
402	{"ShHell",				false,  true, false},
403	{"ShNeon",       			false, 	false, true},
404	{"ShoppaSky",       			false, 	false, true},
405	{"SloppyShoppa",       			false, 	true,  true},
406	{"SloppyShoppa2",      			false, 	true,  true},
407	{"SkatePark",       			false, 	true,  true},
408	{"Snow_Ropes",       			false, 	true, false},
409	{"Sticks",       			true, 	false, true},
410	{"Symmetrical Ropes",       		false, 	false, true},
411	{"SpartanShoppa",       		false, 	true,  true},
412	{"TERRORmap",				false,  false,false},
413	{"Tetris",       			false, 	false, true},
414	{"TransRopes2",      			false, 	false, true},
415	{"TRBShoppa",      			false, 	false, true},
416	{"TrickyShoppa",      			false, 	true, false},
417	{"Towers",      			false, 	true,  true},
418	{"Wildmap",      			false, 	false, true},
419	{"Winter Shoppa",      			false, 	false, true},
420	{"WarShoppa",      			false, 	true,  true},
421	{"2Cshoppa",      			true, 	false, true},
422	}
423
424local Ropes_WallSet = {
425	{ add="none", {299,932,20,856}, {4056,0,30,1788} },
426	{ add="none", {299,109,20,779}, {4056,0,30,1788} },
427	{ add="none", {299,109,20,779}, {299,932,20,856}, {4056,0,30,1788} },
428	{ add="default", {2253,326,20,574}, {3280,326,33,253}, needsborder=false },
429	{ add="roof", {2322,326,457,20} },
430	{ add="default", {1092,934,54,262}, {2822,323,33,137}, needsborder=false },
431	{ add="none", {203,1193,20,595}, {3280,326,20,253}, needsborder=false },
432}
433local Shoppawall_WallSet = {
434	{ add="none", {80+290,61+878,20,1018}, {3433+290,61+878,20,1018}, default=true, needsborder=false },
435}
436
437-- List of map with special wall settings
438local SpecialMapList = {
439	["Ropes"] = Ropes_WallSet,
440	["HellishRopes"] = Ropes_WallSet,
441	["MaskedRopes"] = Ropes_WallSet,
442	["TransRopes2"] = Ropes_WallSet,
443	["ShoppaKing"] = {
444		{ add="none", {3777,1520,50,196}, {1658,338,46,670}, needsborder=false },
445		{ add="none", {125,0,30,2048}, {4066,515,30,1528}, default=true},
446	},
447	["ShoppaHell"] = {
448		{ add="none", {3491,697,30,1150}, {0,0,30,1847}, default=true},
449		{ add="none", {3810,0,30,1616}, {0,0,30,1847}, },
450		{ add="none", {2045,832,20,260}, {2107,832,20,260}, needsborder=false },
451		{ add="default", {2035,831,30,263}, {3968,1668,31,383}, needsborder=false },
452	},
453	["ShoppaNeon"] = {
454		{ add="default", {980,400,20,300}, {1940,400,20,300}, {3088,565,26,284}, {187,270,28,266}, needsborder=false },
455	},
456	["Shoppawall"] = Shoppawall_WallSet,
457	["ShoppaWall2"] = Shoppawall_WallSet,
458	["ShoppaDesert"] = {
459		{ add="none", {2322,349,20,471}, {295,93,24,1479}, needsborder=false },
460		{ add="none", {3001,1535,20,232}, {2264,349,20,495},{716,696,20,119}, needsborder=false },
461		{ add="leftright", {209,656,20,367},{2810,838,20,96}, needsborder=false},
462		{ add="none", {2649,0,445,20}, {2322,349,947,20},{299,696,381,20}},
463	},
464	["ShoppaOnePiece2"] = {
465		{ add="default", {42,0,20,2048}, {4048,0,20,2048}, needsborder=false, },
466		{ add="default", {42,0,20,2048}, {3852,273,20,1637}, needsborder=false, default="noborder" },
467	},
468	["ShoppaWild"] = {
469		{ add="default", {2123,1365,20,293}, {3102,1365,20,293}, {1215,1391,20,291}, needsborder=false },
470		{ add="none", {144,167,1904,20}, {2350,167,753,20}, {3793,167,303,20}, needsborder=false},
471	},
472	["ShoppaRainbow"] = {
473		{ add="none", {67+602,61+80,20,1847}, {2779+602,61+80,20,1847}, needsborder=false },
474	},
475}
476
477function BoolToCfgTxt(p)
478	if p == false then
479		return loc("Disabled")
480	else
481		return loc("Enabled")
482	end
483end
484
485function AttackRuleToCfgTxt(attackRule)
486	if attackRule == nil then
487		return loc("Disabled")
488	elseif attackRule == "ABL" then
489		return loc("All But Last")
490	elseif attackRule == "KTL" then
491		return loc("Kill The Leader")
492	else
493		return "ERROR"
494	end
495end
496
497function NewWallSet(newWallSet, wType)
498	-- Filter out wall sets which are not in allowed categories or have too many or few walls
499	if allowedWallSetTypes[wType] == true then
500		local inBounds = true
501		if minWalls ~= nil and #newWallSet < minWalls then
502			inBounds = false
503		end
504		if maxWalls ~= nil and #newWallSet > maxWalls then
505			inBounds = false
506		end
507		if inBounds then
508			table.insert(wallSets, newWallSet)
509		end
510	end
511end
512
513function MapsInit()
514	mapID = nil
515	margin = 20
516
517	--0.9.18+
518	for i = 1, #MapList do
519		if Map == MapList[i][1] then
520			mapID = i
521		end
522	end
523
524	-- Border conditions
525	-- Just a wrapper for MapHasBorder()
526	local border = MapHasBorder() == true
527	-- Left and right walls are available
528	local leftRight = (WorldEdge == weBounce) or (WorldEdge == weNone and border)
529
530	local left, right, roof
531
532	local startY, height
533	if (not border) and (WorldEdge == weBounce) then
534		-- Higher left/right walls for bouncy world edge without roof
535		local h = math.max(1024, LAND_HEIGHT)
536		height = h * 2
537		startY = TopY - h
538	else
539		-- Standard left/right wall height
540		height = WaterLine
541		startY = TopY + 10
542	end
543	left = {LeftX+10, startY, margin, height}
544	right = {RightX-10-margin, startY, margin, height}
545	roof = {LeftX+10, TopY+10, RightX-LeftX-20, margin}
546
547	if mapID ~= nil then
548		if border and MapList[mapID][3] == true then
549			NewWallSet({roof, desc=loc("Roof")}, "roof")
550			wallSetID = #wallSets
551		end
552		if leftRight and MapList[mapID][4] == true then
553			NewWallSet({left, right, desc=loc("Left and right")}, "leftright")
554			wallSetID = #wallSets
555		end
556		if leftRight and border and MapList[mapID][3] == true and MapList[mapID][4] == true then
557			NewWallSet({left, right, roof, desc=loc("Left, right and roof")}, "leftrightroof")
558		end
559
560		-- add map specific walls
561		if SpecialMapList[Map] ~= nil then
562			local insideID = 1
563			local previousInside = nil
564			local mixedID = 1
565			local previousMixed = nil
566
567			-- Helper function to build the wall set name.
568			-- Basically just to ensure that names like "Inside 1" are only used when there are at least 2 "Insides"
569			local function newInsideOrMixed(ws, previous_ws, id, string, stringD)
570				if id == 1 then
571					ws.desc = string
572				else
573					ws.desc = string.format(stringD, id)
574				end
575				if id == 2 then
576					previous_ws.desc = string.format(stringD, id-1)
577				end
578				id = id + 1
579				previous_ws = ws
580				return id, previous_ws
581			end
582			for ws=1,#SpecialMapList[Map] do
583				local walls = SpecialMapList[Map][ws]
584				if walls.needsborder == false then
585					local newwallset2 = {}
586					for w=1,#walls do
587						table.insert(newwallset2, walls[w])
588					end
589					insideID, previousInside = newInsideOrMixed(newwallset2, previousInside, insideID, loc("Inside"), loc("Inside %d"))
590					newwallset2.custom = true
591					NewWallSet(newwallset2, "inside")
592					if SpecialMapList[Map][ws].default == "noborder" then
593						wallSetID = #wallSets
594					end
595				end
596				local newwallset = {}
597				if border and leftRight and walls.add == "all" then
598					table.insert(newwallset, roof)
599					table.insert(newwallset, left)
600					table.insert(newwallset, right)
601				elseif walls.add == "default" then
602					if border and MapList[mapID][3] == true then
603						table.insert(newwallset, roof)
604					end
605					if leftRight and MapList[mapID][4] == true then
606						table.insert(newwallset, left)
607						table.insert(newwallset, right)
608					end
609				elseif border and walls.add == "roof" then
610					table.insert(newwallset, roof)
611				elseif leftRight and walls.add == "leftright" then
612					table.insert(newwallset, left)
613					table.insert(newwallset, right)
614				end
615				for w=1,#walls do
616					table.insert(newwallset, walls[w])
617				end
618				if border and leftRight and ((walls.add ~= "none" and walls.add ~= nil) or walls.needsborder ~= false) then
619					mixedID, previousMixed = newInsideOrMixed(newwallset, previousMixed, mixedID, loc("Mixed"), loc("Mixed %d"))
620					newwallset.custom = true
621					NewWallSet(newwallset, "mixed")
622				end
623				if SpecialMapList[Map][ws].default == true then
624					wallSetID = #wallSets
625				end
626			end
627		end
628
629	else
630		if border then
631			NewWallSet({roof, desc=loc("Roof")}, "roof")
632			wallSetID = #wallSets
633		end
634		if leftRight then
635			NewWallSet({left, right, desc=loc("Left and right")}, "leftright")
636			wallSetID = #wallSets
637		end
638		if leftRight and border then
639			NewWallSet({left, right, roof, desc=loc("Left, right and roof")}, "leftrightroof")
640		end
641	end
642
643	-- Choose random map when without without menu
644	if useMenu == false and #wallSets > 0 then
645		wallSetID = GetRandom(#wallSets)+1
646	end
647	-- Select first wall set by default if we still haven't selected anything for some reason
648	if wallSetID == 0 and #wallSets > 0 then
649		wallSetID = 1
650	end
651	-- But disabled walls from script parameter have higher priority
652	if WBC == false then
653		wallSetID = 0
654	end
655
656	if CanSurf() == false then
657		requireSurfer = false
658	end
659end
660
661function LoadConfig(p)
662	ClearWalls()
663	if p > 0 then
664		local walls = wallSets[p]
665		for i=1,#walls do
666			AddWall(walls[i][1], walls[i][2], walls[i][3], walls[i][4])
667		end
668	end
669
670end
671
672function AddWall(zXMin,zYMin, zWidth, zHeight)
673
674	table.insert(wX, zXMin)
675	table.insert(wY, zYMin)
676	table.insert(wWidth, zWidth)
677	table.insert(wHeight, zHeight)
678	table.insert(wTouched, false)
679
680end
681
682function ClearWalls()
683
684	wX = {}
685	wY = {}
686	wWidth = {}
687	wHeight = {}
688	wTouched = {}
689
690end
691
692-- Draw a single point for the crate radar
693function DrawBlip(gear)
694	if GetGearType(gear) ~= gtCase then
695		return
696	end
697
698	local baseColor, radius, alpha
699	if CurrentHedgehog == nil or band(GetState(CurrentHedgehog), gstHHDriven) == 0 then
700		radius = 40
701		baseColor = 0xFFFFFFFF
702		alpha = 255
703	elseif getGearValue(gear, "frozen") then
704		radius = 25
705		baseColor = 0xFFFFFFFF
706		alpha = math.min(255, rAlpha+127)
707	else
708		radius = 40
709		baseColor = GetClanColor(GetHogClan(CurrentHedgehog))
710		alpha = rAlpha
711	end
712	if getGearValue(gear,"CIRC") ~= nil then
713		SetVisualGearValues(getGearValue(gear,"CIRC"), getGearValue(gear,"RX"), getGearValue(gear,"RY"), 100, 255, 1, 10, 0, radius, 3, baseColor-alpha)
714	end
715end
716
717function TrackRadarBlip(gear)
718	if GetGearType(gear) ~= gtCase then
719		return
720	end
721
722	-- work out the distance to the target
723	g1X, g1Y = GetGearPosition(CurrentHedgehog)
724	g2X, g2Y = GetX(gear), GetY(gear)
725	q = g1X - g2X
726	w = g1Y - g2Y
727	-- Floating point operations are safe, it's only for visuals
728	r = math.sqrt( (q*q) + (w*w) )	--alternate
729
730	RCX = getGearValue(gear,"RX")
731	RCY = getGearValue(gear,"RY")
732
733	rCircDistance = r -- distance to circle
734
735	opp = w
736	if opp < 0 then
737		opp = opp*-1
738	end
739
740	-- work out the angle (theta) to the target
741	t = math.deg ( math.asin(opp / r) )
742
743	-- based on the radius of the radar, calculate what x/y displacement should be
744	NR = 150 -- radius at which to draw circs
745	NX = math.cos( math.rad(t) ) * NR
746	NY = math.sin( math.rad(t) ) * NR
747
748	if rCircDistance < NR then
749		RCX = g2X
750	elseif q > 0 then
751		RCX = g1X - NX
752	else
753		RCX = g1X + NX
754	end
755
756	if rCircDistance < NR then
757		RCY = g2Y
758	elseif w > 0 then
759		RCY = g1Y - NY
760	else
761		RCY = g1Y + NY
762	end
763
764	setGearValue(gear, "RX", RCX)
765	setGearValue(gear, "RY", RCY)
766
767end
768
769
770function HandleCircles()
771
772	if radarMode == 0 then
773		rAlpha = 0
774	elseif radarMode == 1 then
775		-- Only show radar for a short time after a crate spawn
776		if rAlpha ~= 255 then
777			rPingTimer = rPingTimer + 1
778			if rPingTimer == 100 then
779				rPingTimer = 0
780
781				rAlpha = rAlpha + 5
782				if rAlpha >= 255 then
783					rAlpha = 255
784				end
785			end
786		end
787	elseif radarMode == 2 then
788		rAlpha = 255
789	end
790
791	runOnGears(DrawBlip)
792
793	m2Count = m2Count + 1
794	if m2Count == 25 then
795		m2Count = 0
796
797		if (CurrentHedgehog ~= nil) and (rAlpha ~= 255) then
798			runOnGears(TrackRadarBlip)
799		end
800
801	end
802
803end
804
805-- Returns true if crates are allowed to be accessed right now (used for unfreezing and spawning)
806function AreCratesUnlocked()
807
808	local crateSpawn = true
809
810	if requireSurfer == true then
811		if hasSurfed == false then
812			crateSpawn = false
813		end
814	end
815
816	if #wTouched > 0 then
817		if allWallsHit == false then
818			crateSpawn = false
819		end
820	end
821
822	return crateSpawn
823
824end
825
826-- Freeze all crates,
827function FreezeCrates()
828
829	local cratesFrozen = 0
830	for crate, isCrate in pairs(crates) do
831		local state = GetState(crate)
832		-- Freeze crate if it wasn't already frozen
833		if band(state, gstFrozen) == 0 then
834			cratesFrozen = cratesFrozen + 1
835			SetState(crate, bor(GetState(crate), gstFrozen))
836			setGearValue(crate, "frozen", true)
837		end
838	end
839	-- Play sound if at least one new (!) crate was frozen
840	if cratesFrozen > 0 then
841		PlaySound(sndHogFreeze)
842	end
843
844end
845
846-- Unfreeze all crates
847function UnfreezeCrates()
848
849	for crate, isCrate in pairs(crates) do
850		SetState(crate, band(GetState(crate), bnot(gstFrozen)))
851		setGearValue(crate, "frozen", false)
852	end
853
854end
855
856function onCaseDrop()
857	local crates
858	if roundN == 100 then
859		allowCrate = crateGearsInGame < maxCrates
860		crates = CheckCrateConditions()
861	end
862	if type(crates) == "table" and #crates > 0 and CurrentHedgehog then
863		PlaySound(sndReinforce, CurrentHedgehog)
864	end
865end
866
867function CheckCrateConditions()
868
869	local crateSpawn = AreCratesUnlocked()
870	local crates = {}
871
872	if crateSpawn == true and crateSpawned == false then
873		UnfreezeCrates()
874		if allowCrate == true then
875			local cratesInGame = crateGearsInGame
876			local toSpawn = cratesPerTurn
877			if cratesInGame + toSpawn > maxCrates then
878				toSpawn = maxCrates - cratesInGame
879			end
880			for i=1,toSpawn do
881				table.insert(crates, SpawnSupplyCrate(0, 0, weapons[1+GetRandom(#weapons)]))
882			end
883			rPingTimer = 0
884			rAlpha = 0
885			if toSpawn > 0 then
886				PlaySound(sndWarp)
887			end
888		end
889	end
890
891	return crates
892end
893
894function onGearWaterSkip(gear)
895	if gear == CurrentHedgehog then
896		hasSurfed = true
897		AddCaption(loc("Surfer!"), capcolDefault, capgrpMessage2)
898	end
899end
900
901
902function WallHit(id, zXMin,zYMin, zWidth, zHeight)
903
904	if wTouched[id] == false then
905		AddVisualGear(GetX(CurrentHedgehog), GetY(CurrentHedgehog), vgtBigExplosion, 0, false)
906		PlaySound(sndExplosion)
907		wallsLeft = wallsLeft - 1
908
909		if wallsLeft == 0 then
910			AddCaption(loc("All walls touched!"))
911			allWallsHit = true
912			if (requireSurfer == true) and (hasSurfed == false) then
913				AddCaption(loc("Go surf!"), capcolDefault, capgrpMessage2)
914			end
915		else
916			AddCaption(string.format(loc("Walls left: %d"), wallsLeft))
917		end
918
919	end
920
921	wTouched[id] = true
922	if #wTouched > 0 then
923		AddVisualGear(GetX(CurrentHedgehog), GetY(CurrentHedgehog), vgtSmoke, 0, false)
924	end
925
926end
927
928function CheckForWallCollision()
929
930	for i = 1, #wTouched do
931		if gearIsInBox(CurrentHedgehog, wX[i],wY[i],wWidth[i],wHeight[i]) then
932			WallHit(i, wX[i],wY[i],wWidth[i],wHeight[i])
933		end
934	end
935
936end
937
938function BorderSpark(zXMin,zYMin, zWidth, zHeight, bCol)
939
940	local size = zWidth * zHeight
941	-- Add multiple sparks for very large walls
942	sparkRuns = math.min(10, math.max(1, div(size, 10240)))
943	for i=1, sparkRuns do
944		local eX = zXMin + GetRandom(zWidth+10)
945		local eY = zYMin + GetRandom(zHeight+10)
946
947		local tempE = AddVisualGear(eX, eY, vgtDust, 0, false)
948		SetVisualGearValues(tempE, eX, eY, nil, nil, nil, nil, nil, 1, nil, bCol )
949	end
950
951end
952
953
954function HandleBorderEffects()
955
956	if CurrentHedgehog == nil or band(GetState(CurrentHedgehog), gstHHDriven) == 0 then
957		return
958	end
959	effectTimer = effectTimer + 1
960	if effectTimer > 15 then --25
961
962		effectTimer = 1
963
964		for i = 1, #wTouched do
965			if wTouched[i] == false then
966				bCol = GetClanColor(GetHogClan(CurrentHedgehog))
967				BorderSpark(wX[i],wY[i],wWidth[i],wHeight[i], bCol)
968			end
969		end
970
971	end
972
973end
974
975function PlaceWarn()
976	PlaySound(sndDenied)
977	AddCaption(loc("Please place your hedgehog first!"), msgColorWarn, capgrpMessage2)
978end
979
980function AcceptConfiguration()
981	if roundN == 1 then
982		PlaySound(sndPlaced)
983		SetInputMask(0xFFFFFFFF)
984		AddCaption(loc("Configuration accepted."), msgColorTech, capgrpMessage)
985		if GetGameFlag(gfPlaceHog) then
986			SetTurnTimeLeft(PlacementTime)
987			AddAmmo(CurrentHedgehog, amTeleport, 100)
988			SetWeapon(amTeleport)
989			AddCaption(
990				string.format(loc("%s, place the first hedgehog!"), GetHogTeamName(CurrentHedgehog)),
991				0xFFFFFFFF,
992				capgrpMessage2
993			)
994			roundN = 2
995		else
996			SetTurnTimeLeft(TurnTime)
997			AddCaption(string.format(loc("Let's go, %s!"), GetHogTeamName(CurrentHedgehog)), capcolDefault, capgrpMessage2)
998			roundN = 100
999			wallsLeft = #wTouched
1000			allowCrate = true
1001		end
1002		PlaySound(sndYesSir, CurrentHedgehog)
1003		FinalizeMenu()
1004	end
1005end
1006
1007function onLJump()
1008	if roundN == 1 then
1009		AcceptConfiguration()
1010	elseif roundN == 2 then
1011		PlaceWarn()
1012	elseif roundN == 100 then
1013		if CBA and not crateCollected then
1014			if (GetCurAmmoType() ~= amRope) and
1015				(GetCurAmmoType() ~= amSkip) and
1016				(GetCurAmmoType() ~= amNothing) and
1017				(ropeG ~= nil)
1018			then
1019				AddCaption(loc("You must first collect a crate before you attack!"), msgColorWarn, capgrpMessage2)
1020				PlaySound(sndDenied)
1021			end
1022		end
1023	end
1024end
1025
1026function onAttack()
1027	if roundN == 1 then
1028		if menu[menuIndex].activate ~= nil then
1029			menu[menuIndex].activate()
1030		else
1031			menu[menuIndex].doNext()
1032		end
1033
1034		UpdateMenu()
1035		configureWeapons()
1036		HandleStartingStage()
1037
1038		PlaySound(sndSwitchHog)
1039
1040	elseif roundN == 2 then
1041		if GetCurAmmoType() ~= amSkip and GetCurAmmoType() ~= amNothing then
1042			PlaceWarn()
1043		end
1044
1045	elseif roundN == 100 then
1046		local weaponSelected = (GetCurAmmoType() ~= amRope) and
1047			(GetCurAmmoType() ~= amSkip) and
1048			(GetCurAmmoType() ~= amNothing) and
1049			(ropeG == nil)
1050
1051		if weaponSelected then
1052			if AFR and CBA and not crateCollected then
1053				AddCaption(loc("You must attack from a rope, after you collected a crate!"), msgColorWarn, capgrpMessage2)
1054				PlaySound(sndDenied)
1055			elseif AFR then
1056				AddCaption(loc("You may only attack from a rope!"), msgColorWarn, capgrpMessage2)
1057				PlaySound(sndDenied)
1058			elseif CBA and not crateCollected then
1059				AddCaption(loc("You must first collect a crate before you attack!"), msgColorWarn, capgrpMessage2)
1060				PlaySound(sndDenied)
1061			end
1062		end
1063	end
1064end
1065
1066function onSwitch()
1067	-- Must be in-game, hog must be controlled by player and hog must be on rope or have rope selected
1068	if roundN == 100 and CurrentHedgehog ~= nil and band(GetState(CurrentHedgehog), gstHHDriven) ~= 0 and (ropeG ~= nil or GetCurAmmoType() == amRope) then
1069		-- Toggle radar mode
1070		radarMode = radarMode + 1
1071		if radarMode > 2 then
1072			radarMode = 0
1073		end
1074		local message
1075		if radarMode == 0 then
1076			message = loc("Radar: On")
1077		elseif radarMode == 1 then
1078			message = loc("Radar: Show after crate drop")
1079		elseif radarMode == 2 then
1080			message = loc("Radar: Off")
1081		end
1082		AddCaption(message, GetClanColor(GetHogClan(CurrentHedgehog)), capgrpAmmostate)
1083		-- Remember the radar mode for this team to restore it on the team's next turn
1084		setTeamValue(GetHogTeamName(CurrentHedgehog), "radarMode", radarMode)
1085	end
1086end
1087
1088function onLeft()
1089	if roundN == 1 then
1090		if menu[menuIndex].doPrev ~= nil then
1091			menu[menuIndex].doPrev()
1092		else
1093			menu[menuIndex].activate()
1094		end
1095
1096		UpdateMenu()
1097		configureWeapons()
1098		HandleStartingStage()
1099
1100		PlaySound(sndSwitchHog)
1101	end
1102end
1103
1104function onRight()
1105	if roundN == 1 then
1106		if menu[menuIndex].doNext ~= nil then
1107			menu[menuIndex].doNext()
1108		else
1109			menu[menuIndex].activate()
1110		end
1111
1112		UpdateMenu()
1113		configureWeapons()
1114		HandleStartingStage()
1115
1116		PlaySound(sndSwitchHog)
1117	end
1118end
1119
1120function onDown()
1121	if roundN == 1 then
1122		PlaySound(sndSteps)
1123		menuIndex = menuIndex +1
1124		if menuIndex > #menu then
1125			menuIndex = 1
1126		end
1127		HandleStartingStage()
1128	end
1129end
1130
1131function onUp()
1132	if roundN == 1 then
1133		PlaySound(sndSteps)
1134		menuIndex = menuIndex -1
1135		if 	menuIndex == 0 then
1136			menuIndex = #menu
1137		end
1138		HandleStartingStage()
1139	end
1140end
1141
1142function parseBool(key, default)
1143	if params[key]=="true" then
1144		return true
1145	elseif params[key]=="false" then
1146		return false
1147	else
1148		return default
1149	end
1150end
1151
1152function parseInt(key, default, min, max)
1153	local num = tonumber(params[key])
1154	if type(num) ~= "number" then
1155		return default
1156	end
1157	if min ~= nil then
1158		num = math.max(min, num)
1159	end
1160	if max ~= nil then
1161		num = math.min(max, num)
1162	end
1163	return num
1164end
1165
1166function onParameters()
1167	parseParams()
1168	local tmpParam
1169	useMenu = parseBool("menu", useMenu)
1170	requireSurfer = parseBool("SBC", requireSurfer)
1171	AFR = parseBool("AFR", AFR)
1172	CBA = parseBool("CBA", CBA)
1173	if params["attackrule"] == "ABL" then
1174		attackRule = "ABL"
1175	elseif params["attackrule"] == "KTL" then
1176		attackRule = "KTL"
1177	end
1178	allowCrazyWeps = parseBool("SW", allowCrazyWeps)
1179	maxCrates = parseInt("maxcrates", maxCrates, 1, maxCratesHard)
1180	cratesPerTurn = parseInt("cratesperturn", cratesPerTurn, 1, maxCrates)
1181	local wallsParam = params["walls"]
1182	local wallsParamSelection = false
1183	if wallsParam ~= nil then
1184		if wallsParam == "all" then
1185			wallsParamSelection = true
1186			allowedWallSetTypes = {}
1187			for i=1,#allWallSetTypes do
1188				allowedWallSetTypes[allWallSetTypes[i]] = true
1189			end
1190		elseif wallsParam == "none" then
1191			WBC = false
1192			allowedWallSetTypes = {}
1193		else
1194			wallsParamSelection = true
1195			allowedWallSetTypes = {}
1196			local parsedWords = {}
1197			for k,v in string.gmatch(wallsParam, "(%w+)") do
1198				table.insert(parsedWords, k)
1199			end
1200			for i=1,#allWallSetTypes do
1201				for j=1,#parsedWords do
1202					if allWallSetTypes[i] == parsedWords[j] then
1203						allowedWallSetTypes[allWallSetTypes[i]] = true
1204					end
1205				end
1206			end
1207		end
1208	end
1209
1210	-- Upper and lower bounds
1211	local wallsNum = parseInt("wallsnum", nil, 0)
1212	if wallsNum == 0 then
1213		WBC = false
1214	end
1215	minWalls = wallsNum
1216	maxWalls = wallsNum
1217	-- minwalls and maxwalls take precedence over wallsnum
1218	minWalls = parseInt("minwalls", minWalls, 1)
1219	maxWalls = parseInt("maxwalls", maxWalls, 1)
1220end
1221
1222function onGameInit()
1223
1224	HealthCaseProb = 0
1225	CaseFreq = 0
1226	SetAmmoDescriptionAppendix(amRope, loc("Switch: Toggle crate radar"))
1227
1228end
1229
1230function configureWeapons()
1231
1232	-- reset wep array
1233	for i = 1, #weapons do
1234		weapons[i] = nil
1235	end
1236
1237	-- add rope weps
1238	for i, w in pairs(ropeWeps) do
1239        table.insert(weapons, w)
1240	end
1241
1242	-- add ground weps
1243	for i, w in pairs(groundWeps) do
1244        table.insert(weapons, w)
1245	end
1246
1247	-- remove ground weps if attacking from rope is mandatory
1248	if AFR == true then
1249		for i = 1, #weapons do
1250			for w = 1, #groundWeps do
1251				if groundWeps[w] == weapons[i] then
1252					table.remove(weapons, i)
1253				end
1254			end
1255		end
1256	end
1257
1258	-- remove crazy weps is crazy weps aren't allowed
1259	if allowCrazyWeps == false then
1260		for i = 1, #weapons do
1261			for w = 1, #crazyWeps do
1262				if crazyWeps[w] == weapons[i] then
1263					table.remove(weapons, i)
1264				end
1265			end
1266		end
1267	end
1268
1269end
1270
1271function onGameStart()
1272
1273	trackTeams()
1274
1275	MapsInit()
1276	LoadConfig(wallSetID)
1277	configureWeapons()
1278
1279	-- ABL or KTL only make sense with at least 3 teams, otherwise we disable it
1280	if TeamsCount < 3 or ClansCount < 3 then
1281		attackRule = nil
1282	end
1283
1284	if useMenu then
1285		ShowMission(loc("Wall to wall"), loc("Please wait …"), "", 2, 0)
1286		HideMission()
1287		UpdateMenu()
1288	else
1289		if GetGameFlag(gfPlaceHog) then
1290			roundN = 2
1291			FinalizeMenu()
1292		else
1293			allowCrate = false
1294			roundN = 100
1295			FinalizeMenu()
1296		end
1297	end
1298end
1299
1300function onEndTurn()
1301	crateSpawned = false
1302	crateCollected = false
1303	wallsLeft = #wTouched
1304	for i = 1, #wTouched do
1305		wTouched[i] = false
1306	end
1307	hasSurfed = false
1308	allWallsHit = false
1309end
1310
1311function onNewTurn()
1312	turnsCount = turnsCount + 1
1313
1314	if roundN == 0 then
1315		roundN = 1
1316	end
1317
1318	if GetGameFlag(gfPlaceHog) then
1319		if roundN < 2 then
1320			SetWeapon(amSkip)
1321			AddAmmo(CurrentHedgehog, amTeleport, 0)
1322			SetTurnTimeLeft(MAX_TURN_TIME)
1323			SetInputMask(0)
1324		end
1325		if roundN == 2 then
1326			if turnsCount > hogCount then
1327				roundN = 100
1328			end
1329		end
1330	end
1331
1332	if roundN == 100 then
1333
1334		local teamName = GetHogTeamName(CurrentHedgehog)
1335
1336		-- Restore team's radar mode
1337		radarMode = getTeamValue(teamName, "radarMode")
1338		if radarMode == nil then
1339			radarMode = 0
1340		end
1341
1342		if not AreCratesUnlocked() then
1343			FreezeCrates()
1344		end
1345
1346		-- Check the attack rule violation of the *previous* team and apply penalties
1347		-- This function will do nothiong in the first turn since previousTeam is still nil
1348		CheckAttackRuleViolation(previousTeam)
1349
1350		previousTeam = teamName
1351
1352		-- Update attack rule information for this turn
1353		UpdateLastAndLeaderTeams()
1354		teamsAttacked = {}
1355
1356		-- Was the team violating the attackRule the last time?
1357		if getTeamValue(teamName, "skipPenalty") then
1358			-- Then take away this turn
1359			AddCaption(string.format(loc("%s must skip this turn for rule violation."), teamName), msgColorWarn, capgrpMessage)
1360			EndTurn(true)
1361			setTeamValue(teamName, "skipPenalty", false)
1362		end
1363
1364	end
1365
1366	if roundN == 1 then
1367		SetTurnTimeLeft(MAX_TURN_TIME)
1368		SetInputMask(0)
1369		allowCrate = false
1370		UpdateMenu()
1371		AddCaption(string.format(loc("%s may choose the rules."), GetHogTeamName(CurrentHedgehog)), msgColorTech, capgrpGameState)
1372		HandleStartingStage()
1373		if GetHogLevel(CurrentHedgehog) ~= 0 then
1374			AcceptConfiguration()
1375		end
1376	end
1377
1378end
1379
1380function CanSurf()
1381	if mapID ~= nil then
1382		if GetGameFlag(gfBottomBorder) and WaterRise == 0 then
1383			return false
1384		else
1385			return MapList[mapID][2]
1386		end
1387	else
1388		return nil
1389	end
1390end
1391
1392function UpdateMenu()
1393	local teamInfo
1394	if roundN == 1 and CurrentHedgehog ~= nil then
1395		teamInfo = string.format(loc("%s, you may choose the rules."), GetHogTeamName(CurrentHedgehog))
1396	else
1397		teamInfo = ""
1398	end
1399	preMenuCfg =	teamInfo .. "|" ..
1400			loc("Press [Up] and [Down] to move between menu items.|Press [Attack], [Left], or [Right] to toggle.") .. "|"
1401	if GetGameFlag(gfPlaceHog) then
1402		postMenuCfg = loc("Press [Long jump] to accept this configuration and begin placing hedgehogs.")
1403	else
1404		postMenuCfg = loc("Press [Long jump] to accept this configuration and start the game.")
1405	end
1406
1407	-- This table contains the menu strings and functions to be called when the entry is activated.
1408	menu = {}
1409
1410	-- Walls required (hidden if the current settings don't allow for any walls)
1411	if #wallSets > 0 then
1412		local line
1413		if #wTouched > 0 then
1414			if wallSets[wallSetID].custom then
1415				line = string.format(loc("Wall set: %s (%d walls)"), wallSets[wallSetID].desc, #wTouched) .. "|"
1416			else
1417				line = string.format(loc("Wall set: %s"), wallSets[wallSetID].desc) .. "|"
1418			end
1419		else
1420			line = loc("Wall set: No walls") .. "|"
1421		end
1422		table.insert(menu, {
1423			line = line,
1424			doNext = function()
1425				wallSetID = wallSetID + 1
1426				if wallSetID > #wallSets then
1427					wallSetID = 0
1428				end
1429				LoadConfig(wallSetID)
1430			end,
1431			doPrev = function()
1432				wallSetID = wallSetID - 1
1433				if wallSetID < 0 then
1434					wallSetID = #wallSets
1435				end
1436				LoadConfig(wallSetID)
1437			end,
1438		})
1439	end
1440
1441	-- Surf Before Crate (hidden if map disabled it)
1442	if CanSurf() == true or CanSurf() == nil then
1443		local toggleSurf = function() requireSurfer = not(requireSurfer) end
1444		table.insert(menu, {
1445			line = string.format(loc("Surf Before Crate: %s"), BoolToCfgTxt(requireSurfer)) .. "|",
1446			activate = function() requireSurfer = not requireSurfer end,
1447		})
1448	end
1449
1450	-- Attack From Rope
1451	table.insert(menu, {
1452		line = string.format(loc("Attack From Rope: %s"), BoolToCfgTxt(AFR)) .. "|",
1453		activate = function() AFR = not AFR end,
1454	})
1455
1456	-- Crate Before Attack
1457	table.insert(menu, {
1458		line = string.format(loc("Crate Before Attack: %s"), BoolToCfgTxt(CBA)) .. "|",
1459		activate = function() CBA = not CBA end,
1460	})
1461
1462	if TeamsCount >= 3 then
1463		-- Attack rule (Disabled / All But Last / Kill The Leader)
1464		table.insert(menu, {
1465			line = string.format(loc("Attack rule: %s"), AttackRuleToCfgTxt(attackRule)) .. "|",
1466			doNext = function()
1467				if attackRule == nil then
1468					attackRule = "ABL"
1469				elseif attackRule == "ABL" then
1470					attackRule = "KTL"
1471				elseif attackRule == "KTL" then
1472					attackRule = nil
1473				end
1474			end,
1475			doPrev = function()
1476				if attackRule == nil then
1477					attackRule = "KTL"
1478				elseif attackRule == "ABL" then
1479					attackRule = nil
1480				elseif attackRule == "KTL" then
1481					attackRule = "ABL"
1482				end
1483			end,
1484		})
1485	end
1486
1487	-- Super weapons
1488	table.insert(menu, {
1489		line = string.format(loc("Super weapons: %s"), BoolToCfgTxt(allowCrazyWeps)) .. "|",
1490		activate = function() allowCrazyWeps = not allowCrazyWeps end,
1491	})
1492
1493	-- Number of crates which appear per turn
1494	if maxCrates > 1 then
1495		table.insert(menu, {
1496			line = string.format(loc("Crates per turn: %d"), cratesPerTurn) .. "|",
1497			doNext = function()
1498				cratesPerTurn = cratesPerTurn + 1
1499				if cratesPerTurn > maxCrates then
1500					cratesPerTurn = 1
1501				end
1502			end,
1503			doPrev = function()
1504				cratesPerTurn = cratesPerTurn - 1
1505				if cratesPerTurn < 1 then
1506					cratesPerTurn = maxCrates
1507				end
1508			end,
1509		})
1510	end
1511end
1512
1513function FinalizeMenu()
1514	local text = ""
1515	local showTime = 3000
1516	if #wTouched == 0 and not requireSurfer then
1517		text = text .. loc("Collect the crate and attack!") .. "|"
1518	else
1519		text = text .. loc("Spawn the crate and attack!") .. "|"
1520	end
1521
1522	-- Expose a few selected game flags
1523	if GetGameFlag(gfPlaceHog)  then
1524		text = text .. loc("Place hedgehogs: Place your hedgehogs at the start of the game.") .. "|"
1525		showTime = 6000
1526	end
1527	if GetGameFlag(gfResetWeps) then
1528		text = text .. loc("Weapons reset: The weapons are reset after each turn.") .. "|"
1529	end
1530
1531	-- Show the WxW rules
1532	if #wTouched == 1 then
1533		text = text .. loc("Wall Before Crate: You must touch the marked wall before you can get crates.") .. "|"
1534	elseif #wTouched > 0 then
1535		text = text .. string.format(loc("Walls Before Crate: You must touch the %d marked walls before you can get crates."), #wTouched) .. "|"
1536	end
1537
1538	if requireSurfer then
1539		text = text .. loc("Surf Before Crate: You must bounce off the water once before you can get crates.") .. "|"
1540	end
1541
1542	if AFR then
1543		text = text .. loc("Attack From Rope: You may only attack from a rope.") .. "|"
1544	end
1545
1546	if CBA then
1547		text = text .. loc("Crate Before Attack: You must collect a crate before you can attack.") .. "|"
1548	end
1549
1550	if attackRule == "ABL" then
1551		text = text .. loc("All But Last: You must not solely attack the team with the least health") .. "|"
1552	elseif attackRule == "KTL" then
1553		text = text .. loc("Kill The Leader: You must also hit the team with the most health.") .. "|"
1554	end
1555	if attackRule ~= nil then
1556		text = text .. loc("Penalty: If you violate above rule, you have to skip in the next turn.") .. "|"
1557	end
1558
1559	if allowCrazyWeps then
1560		text = text .. loc("Super weapons: A few crates contain very powerful weapons.") .. "|"
1561	end
1562
1563	ShowMission(loc("Wall to wall"), loc("A Shoppa minigame"), text, 1, showTime)
1564end
1565
1566function HandleStartingStage()
1567
1568	local renderedLines = {}
1569	for m = 1, #menu do
1570		local marker
1571		local line = menu[m].line
1572		if m == menuIndex then
1573			marker = "▶"
1574		else
1575			marker = "▷"
1576			line = string.gsub(line, ":", "\\:")
1577		end
1578		table.insert(renderedLines, marker .. " " .. line)
1579	end
1580
1581	missionComment = ""
1582	for l = 1, #renderedLines do
1583		missionComment = missionComment .. renderedLines[l]
1584	end
1585
1586	ShowMission	(
1587				loc("Wall to wall"),
1588				loc("Configuration phase"),
1589				preMenuCfg..
1590				missionComment ..
1591				postMenuCfg ..
1592				"", 3, 9999000, true
1593				)
1594
1595end
1596
1597function onGameTick()
1598
1599	if CurrentHedgehog ~= nil and roundN >= 0 then
1600
1601		gTimer = gTimer + 1
1602		if gTimer == 25 then
1603			gTimer = 1
1604
1605			if roundN == 100 then
1606				if band(GetState(CurrentHedgehog), gstHHDriven) ~= 0 then
1607					CheckForWallCollision()
1608					CheckCrateConditions()
1609				end
1610
1611				if (GetGearType(GetFollowGear()) == gtCase) then
1612					FollowGear(CurrentHedgehog)
1613				end
1614
1615				-- AFR and CBA handling
1616				local allowAttack = true
1617				local shootException
1618				shootException = (GetCurAmmoType() == amRope) or
1619					(GetCurAmmoType() == amSkip) or
1620					(GetCurAmmoType() == amNothing)
1621				-- If Attack From Rope is set, forbid firing unless using rope
1622				if AFR then
1623					if ropeG == nil then
1624						allowAttack = false
1625					end
1626				end
1627				-- If Crate Before Attack is set, forbid firing if crate is not collected
1628				if CBA then
1629					if not crateCollected then
1630						allowAttack = false
1631					end
1632				end
1633				if allowAttack or shootException then
1634					SetInputMask(bor(GetInputMask(), gmAttack))
1635					if CBA then
1636						SetInputMask(bor(GetInputMask(), gmLJump))
1637					end
1638				else
1639					if CBA then
1640						if ropeG == nil then
1641							SetInputMask(band(GetInputMask(), bnot(gmAttack)))
1642							SetInputMask(bor(GetInputMask(), gmLJump))
1643						else
1644							SetInputMask(bor(GetInputMask(), gmAttack))
1645							SetInputMask(band(GetInputMask(), bnot(gmLJump)))
1646						end
1647					else
1648						SetInputMask(band(GetInputMask(), bnot(gmAttack)))
1649					end
1650				end
1651			end
1652
1653		end
1654
1655
1656	end
1657
1658	HandleBorderEffects()
1659	HandleCircles()
1660
1661end
1662
1663local menuRepeatTimer = 0
1664function onGameTick20()
1665  -- Make sure the menu doesn't disappear while it is active
1666  if roundN == 1 then
1667    menuRepeatTimer = menuRepeatTimer + 20
1668    if menuRepeatTimer > 9990000 then
1669      HandleStartingStage()
1670      menuRepeatTimer = 0
1671    end
1672  end
1673end
1674
1675function onGearAdd(gear)
1676
1677	if GetGearType(gear) == gtRope then
1678		ropeG = gear
1679	elseif GetGearType(gear) == gtCase then
1680
1681		crates[gear] = true
1682		crateGearsInGame = crateGearsInGame + 1
1683
1684		trackGear(gear)
1685
1686		local vg = AddVisualGear(0, 0, vgtCircle, 0, true)
1687		if vg then
1688			table.insert(rCirc, vg)
1689			setGearValue(gear,"CIRC",vg)
1690			SetVisualGearValues(vg, 0, 0, 100, 255, 1, 10, 0, 40, 3, 0x0)
1691		end
1692		setGearValue(gear,"RX",0)
1693		setGearValue(gear,"RY",0)
1694
1695		allowCrate = false
1696		crateSpawned = true
1697
1698		rPingTimer = 0
1699		rAlpha = 0
1700
1701	elseif GetGearType(gear) == gtHedgehog then
1702		trackGear(gear)
1703		local teamName = GetHogTeamName(gear)
1704		-- Initialize radar mode to “on” and set other team values
1705		setTeamValue(teamName, "radarMode", 0)
1706		setTeamValue(teamName, "skipPenalty", false)
1707
1708		if getTeamValue(teamName, "hogs") == nil then
1709			setTeamValue(teamName, "hogs", 1)
1710		else
1711			increaseTeamValue(teamName, "hogs")
1712		end
1713		hogCount = hogCount + 1
1714		teamNames[GetHogTeamName(gear)] = true
1715	end
1716
1717end
1718
1719function onGearDelete(gear)
1720
1721	local gt = GetGearType(gear)
1722	if gt == gtRope then
1723		ropeG = nil
1724	elseif gt == gtCase then
1725
1726		crates[gear] = nil
1727		crateGearsInGame = crateGearsInGame - 1
1728
1729		for i = 1, #rCirc do
1730			local CIRC = getGearValue(gear,"CIRC")
1731			if CIRC ~= nil and rCirc[i] == CIRC then
1732				DeleteVisualGear(rCirc[i])
1733				table.remove(rCirc, i)
1734			end
1735		end
1736
1737		trackDeletion(gear)
1738
1739		-- Was crate collected?
1740		if band(GetGearMessage(gear), gmDestroy) ~= 0 then
1741			crateCollected = true
1742		end
1743
1744	elseif gt == gtHedgehog then
1745		teamsAttacked[GetHogTeamName(gear)] = true
1746		decreaseTeamValue(GetHogTeamName(gear), "hogs")
1747		trackDeletion(gear)
1748	end
1749
1750end
1751
1752function onGearDamage(gear)
1753
1754	if GetGearType(gear) == gtHedgehog then
1755		teamsAttacked[GetHogTeamName(gear)] = true
1756	end
1757
1758end
1759
1760-- Check which team is the last and which is the leader (used for ABL and KTL)
1761function UpdateLastAndLeaderTeams()
1762	local teamHealths = {}
1763
1764	for team, x in pairs(teamNames) do
1765		UpdateTeamHealth(team)
1766		local totalHealth = getTeamValue(team, "totalHealth")
1767		if totalHealth > 0 then
1768			table.insert(teamHealths, {name = team, health = totalHealth } )
1769		end
1770	end
1771
1772	-- Sort the table by health, lowest health comes first
1773	table.sort(teamHealths, function(team1, team2) return team1.health < team2.health end)
1774
1775	-- ABL and KTL rules are only active at 3 teams; when there are only 2 teams left, it's “everything goes”.
1776	if #teamHealths >= 3 then
1777		if teamHealths[1].health == teamHealths[2].health then
1778			-- ABL rule is disabled if it's a tie for “least health”
1779			lastTeam = nil
1780		else
1781			-- Normal assignment of ABL variable
1782			lastTeam = teamHealths[1].name
1783		end
1784		if teamHealths[#teamHealths].health == teamHealths[#teamHealths-1].health then
1785			-- KTL rule is disabled if it's a tie for “most health”
1786			leaderTeam = nil
1787			runnerUpTeam = nil
1788		else
1789			-- Normal assignment of KTL variables
1790			leaderTeam = teamHealths[#teamHealths].name
1791			runnerUpTeam = teamHealths[#teamHealths-1].name
1792		end
1793	else
1794		-- The KTL and ABL rules are disabled with only 2 teams left
1795		lastTeam = nil
1796		runnerUpTeam = nil
1797		leaderTeam = nil
1798	end
1799end
1800
1801function UpdateTeamHealth(team)
1802	setTeamValue(team, "totalHealth", 0)
1803	runOnHogsInTeam(function(hog)
1804		if(GetGearType(hog) ~= gtHedgehog) then return end
1805		local h = getTeamValue(GetHogTeamName(hog), "totalHealth")
1806		setTeamValue(GetHogTeamName(hog), "totalHealth", h + GetHealth(hog))
1807	end, team)
1808end
1809
1810-- Check if the ABL or KTL rule (if active) has been violated by teamToCheck
1811function CheckAttackRuleViolation(teamToCheck)
1812
1813	if teamToCheck == nil then return end
1814
1815	local violated = false
1816	if attackRule == "ABL" then
1817		-- We don't care if the last team hurts itself
1818		if lastTeam ~= nil and lastTeam ~= teamToCheck then
1819			local lastAttacked = false
1820			local attackNum = 0	-- count the attacked teams but we'll ignore the attacking team
1821			for team, wasAttacked in pairs(teamsAttacked) do
1822				-- Ignore the attacking team
1823				if team ~= teamToCheck then
1824					attackNum = attackNum + 1
1825					if team == lastTeam then
1826						lastAttacked = true
1827					end
1828				end
1829			end
1830			-- Rule is violated iff only the last team is attacked (damage to attacking team is ignored)
1831			if attackNum == 1 and lastAttacked then
1832				violated = true
1833			end
1834		end
1835		if violated then
1836			AddCaption(string.format(loc("%s violated the “All But Last” rule and will be penalized."), teamToCheck), msgColorWarn, capgrpGameState)
1837		end
1838	elseif attackRule == "KTL" then
1839		local leaderAttacked = false
1840		if leaderTeam ~= nil then
1841			local attackNum = 0
1842			local selfHarm = false
1843			for team, wasAttacked in pairs(teamsAttacked) do
1844				attackNum = attackNum + 1
1845				if team == teamToCheck then
1846					selfHarm = true
1847				end
1848				-- The leader must attack the runner-up, everyone else must attack the leader
1849				if (teamToCheck ~= leaderTeam and team == leaderTeam) or (teamToCheck == leaderTeam and team == runnerUpTeam) then
1850					leaderAttacked = true
1851					break
1852				end
1853			end
1854			-- If teams were attacked but not the leader, it is a violation,
1855			-- but we don't care if the team *only* harmed itself.
1856			if (attackNum >= 2 and not leaderAttacked) or (attackNum == 1 and not selfHarm and not leaderAttacked) then
1857				violated = true
1858			end
1859		end
1860		if violated then
1861			AddCaption(string.format(loc("%s violated the “Kill The Leader” rule and will be penalized."), teamToCheck), msgColorWarn, capgrpGameState)
1862		end
1863	end
1864	if violated then
1865		setTeamValue(teamToCheck, "skipPenalty", true)
1866	end
1867
1868end
1869
1870function onAmmoStoreInit()
1871
1872	for i, w in pairs(ropeWeps) do
1873        SetAmmo(w, 0, 0, 0, 1)
1874    end
1875
1876    for i, w in pairs(groundWeps) do
1877        SetAmmo(w, 0, 0, 0, 1)
1878    end
1879
1880    for i, w in pairs(crazyWeps) do
1881        SetAmmo(w, 0, 0, 0, 1)
1882    end
1883
1884	SetAmmo(amRope, 9, 0, 0, 0)
1885	SetAmmo(amSkip, 9, 0, 0, 0)
1886
1887end
1888