share/hedgewars/Data/Scripts/Multiplayer/WxW.lua
changeset 12065 7df7c58ea965
parent 11301 fa18940f290d
child 12067 18677a537d58
--- a/share/hedgewars/Data/Scripts/Multiplayer/WxW.lua	Thu Nov 24 06:33:00 2016 +0100
+++ b/share/hedgewars/Data/Scripts/Multiplayer/WxW.lua	Thu Nov 24 15:09:26 2016 +0100
@@ -1,6 +1,6 @@
 
 ----------------------
--- WALL TO WALL 0.4
+-- WALL TO WALL 0.7
 ----------------------
 -- a shoppa minigame
 -- by mikade
@@ -44,12 +44,154 @@
 -- added backwards compatibility with 0.9.17
 
 ----------------
---TO DO
+--0.5
+----------------
+-- Support for multiple sets of walls per map (instead of “all or nothing”)
+-- Ropes, ShoppaKing, ShoppaHell and ShoppaNeon can now be played with the classic left and right walls
+-- New wall sets for Ropes, ShoppaNeon, ShoppaDesert, ShoppaWild, ShoppaKing and ShoppaHell, and more.
+-- Basic support for a bunch of Shoppa maps
+-- Alternative configuration method with Script parameter
+-- Possible to set max. number of weapons in game (script parameter only)
+-- Possible to set number of crates per turn
+-- Menu can be disabled (with script parameter) for insant game start
+-- WxW is now fully functional even without a map border.
+-- WxW now allows for almost all game modifiers and game settings to be changed
+-- More sound effects
+-- No smoke when hog is near near a WxW wall but Walls Before Crate rule is not in place
+-- More readable mission display after configuration has been accepted
+-- Hide “Surf Before Crate” setting if surfing is disabled for this map, or the bottom is active and water never rises
+-- Hide walls setting if script does not provide walls for map yet
+-- Bugfix: Other player was able to change the menu config in the short period before the first "turn"
+-- Lots of refactoring
+
+----------------
+--0.6
+----------------
+-- Bugfix: 2 crates spawned at the 1st turn if script parameter was set to “menu=false, walls=none” or similar
+-- Bugfix: Annoying faulty error message appeared when hitting attack when on a rope with a weapon selected
+
+
+----------------
+--0.7
+----------------
+-- To enforce the rules more strictly, all crates will be frozen at turn start if WBC or SBC rule is in place.
+--	The crates are unfrozen if you met the crate criteria (that is, surfed and/or bounced off all walls).
+--      Frozen crates can't be collected and appear as small white dots in the radar.
+-- Add support for the “Crate Before Attack” rule
+-- Add support for the “All But Last” rule
+-- Add support for the “Kill The Leader” rule
+-- Allow toggling crate radar with “switch hog” key while roping
+-- The game continues now with the first team after the menu has been closed (rather than the second team)
+
+----------------
+--TODO
 ----------------
 -- achievements / try detect shoppa moves? :|
 -- maybe add ability for the user to place zones like in Racer?
 -- add more hard-coded values for specific maps
 
+
+--[[
+# CONFIGURATION
+
+By default, this script is easily configured via the in-game menu. The player of the first team can choose the rules and
+required walls (or none at all). After accepted, the game will start with the second team (!).
+
+= SCRIPT PARAMETER =
+
+Using the script parameter is optional, it mostly is just an alternative way for configuration and for convenience
+reasons, so often-used configurations can be saved and loaded.
+
+The script parameter is specified as a comma-sperated list of “key=value” pairs (see examples below).
+
+Truth values can be set true or false, and numeric values always use a whole number.
+
+== Basic parameters ==
+
+key		default	description
+----------------------------------------
+menu		true	Show configuration menu at the beginning. If no menu is used, a random wall set is used (see wall filters below)
+SBC		false	Surf Before Crate: Player must bounce off the water (“surfing”) before crates can be collected
+AFR		false	Attack From Rope: Players must attack from the rope. Weapons which can't be fired from rope are removed
+CBA		false	Crate Before Attack: Player must collect at least one crate before attacking
+attackrule	off	If present, enable one of the attack rules “ABL” or “KTL”:
+			ABL: All But Last: Players must not only attack the team with the lowest total health
+			KTL: Kill The Leader: If players hit some enemy hedgehog, at least one of them must be a hog from
+			the team with the highest total health.
+			The ABL and KTL rules exclude each other. If a player breaks the rule (if enabled), he must
+			skip in the next round.
+SW		false	Super Weapons: A few crates may contain very powerful weapons (melon, hellish grenade, RC plane, ballgun)
+maxcrates	12	Number of crates which can be at maximum in the game (limited to up to 100 to avoid lag)
+cratesperturn	1	Number of crates which appear each turn
+
+== Advanced parameters ==
+
+Wall filters: The following parameters allow you to filter out wall sets based on their type and number of walls.
+If this is used together with the menu, the filtered wall sets can't be selected. Without a menu, the wall set
+will be randomly selected among the wall sets that meet all criteria.
+
+If the criteria filter out all available wall sets of the map, the game is played without the Walls Before Crate rule.
+
+parameter	default	description
+----------------------------------------
+walls		N/A	
+
+Permitted values:
+- leftright:		The left and right part of the border. Traditional W2W-style.
+- roof:			Only the top part of the border
+- leftrightroof:	Combination of the two above
+- inside:		Map-specific wall set where all walls are part of the terrain
+- mixed:		Map-specific wall set where some walls are part of the terrain, and some are part of the map border
+- none:			No walls required.
+- all:			Shorthand: All wall sets are allowed.
+
+Combination of multiple types is possible by concatenating the names with plus signs (see examples below).
+
+
+Restrict wall numbers: With the following parameters you can restrict the pool of wall sets to only those with a certain
+number of walls. Note that 2 walls are the most common type of wall set, as this is often available by default.
+
+parameter	default	description
+----------------------------------------
+minwalls	N/A	Filter out wall sets with less than this
+maxwalls	N/A	Filter out wall sets with more than this
+
+wallsnum	N/A	Shorthand: Combintion of minwalls and maxwalls if they are the equal.
+
+
+== Examples ==
+
+
+SBC=true
+--> Keep the menu, enable Surf Before Crate by default (if available).
+
+SBC=true, menu=false
+--> Enable Surf Before Crate (if available) and use the defaul walls set.
+
+AFR=true, menu=false, wallsnum=2
+--> Attack From Rope rule active, and use a random wall set with 2 walls
+
+menu=false, walls=leftright
+--> Always use the classic left/right wall set automatically. Traditional W2W-style.
+
+walls=none, menu=false
+--> Like classic Shoppa
+
+walls=leftright+inside+mixed, menu=false
+--> Randomly use either the left/right wall set, an Inside or Mixed wall set.
+
+
+
+= MORE GAME SCHEME CONFIGURATION =
+You can almost set everything in the game scheme freely, and the script will work just fine together with it.
+Feel free to experiment a bit.
+The only exception are the crate frequencies. Setting them has no effect, crates are handled uniquiely in this game.
+
+At this stage, the script does not allow for custom weapon sets.
+]]
+
+
+
 -----------------------------
 -- GO PONIES, GO PONIES, GO!
 -----------------------------
@@ -57,37 +199,90 @@
 HedgewarsScriptLoad("/Scripts/Locale.lua")
 HedgewarsScriptLoad("/Scripts/Tracker.lua")
 HedgewarsScriptLoad("/Scripts/Utils.lua")
+HedgewarsScriptLoad("/Scripts/Params.lua")
 
--- experimental menu stuff
+-- HARDCODED values
+local ammoTypesNum = 58	-- number of weapon types (permanent TODO: Check this number for each Hedgewars version)
+local PlacementTime = 15000
+
+-- menu stuff
 local menuIndex = 1
 local menu = {}
 local preMenuCfg
 local postMenuCfg
+
+--[[ WxW preparation phase.
+0 = Game not started yet
+1 = Configuration phase
+2 = Hedgehog placement phase
+100 = Game phase
+]]
 local roundN = 0
 
+-- Used to select one of the wall sets
+-- 0: no walls
+-- 1 and above: ID of wall sets
+local wallSetID = 0
+
+-- Store the wall sets here
+local wallSets = {}
+
+-- Wall set types and wall number limits for filtering
+local allWallSetTypes = {"roof", "leftright", "leftrightroof", "mixed", "inside"}
+local allowedWallSetTypes = {roof=true, leftright=true, leftrightroof=true, mixed=true, inside=true}
+local minWalls, maxWalls = nil, nil
+
 -- config and wall variables
-local AFR = false
-local allowCrazyWeps = false
-local requireSurfer = true
+local useMenu = true
+local AFR = false		-- Attack From Rope
+local WBC = true		-- Wall(s) Before Crate, will later only be set again in script parameter
+local CBA = false		-- Crate Before Attack
+local attackRule = nil		-- Either nil, "KTL" (Kill The Leader) or "ABL" (All But Last)
+local allowCrazyWeps = false	-- Super weapons
+local requireSurfer = false	-- Surf Before Crate
+local crateSpawned = false	-- Has the crate (or crates) been spawned in this turn yet?
+local cratesPerTurn = 1		-- How many crates appear per turn (respects crate limit)
+local maxCrates = 12		-- default crate limit, can be configured with params
+local maxCratesHard = 100	-- "hard" crate limit, to avoid extreme lagging due to many crates
+local crateGearsInGame = 0
 local wX = {}
 local wY = {}
 local wWidth = {}
 local wHeight = {}
 local wTouched = {}
---local margin
 local wallsLeft = 0
 
 local hasSurfed = false
 local allWallsHit = false
+local crateCollected = false
+
+-- ABL and KTL stuff
+local teamNames = {}		-- List of all teams
+local teamsAttacked = {}	-- List of attacked teams (in this turn)
+local lastTeam = nil		-- Team with the least health. Determined only at start of turn. If it's a tie, use nil.
+local leaderTeam = nil		-- Team with the most health. Determined only at start of turn. If it's a tie, use nil.
+local runnerUpTeam = nil	-- Team with the second-most health
+local previousTeam = nil	-- Remember the name of the team in the previous turn
 
 local gTimer = 1
 local effectTimer = 1
 
 local ropeG = nil
-local crateG = nil
 local allowCrate = true
+local crates = {}
+
+-- Variables for place hedgehogs mode
+local hogCount = 0		-- Used to detect the end of the hog placement phase
+local turnsCount = 0
 
 -- crate radar vars
+
+-- Set the initial radar mode here
+-- 0: Radar is always active
+-- 1: Radar is only active shortly after crate spawn
+-- 2: Radar is disabled
+local radarMode = 0
+
 local rCirc = {}
 local rAlpha = 255
 local rPingTimer = 0
@@ -95,67 +290,179 @@
 
 local weapons = {}
 
---[[local unlisted = {amTardis, amLandGun,amExtraTime,amExtraDamage,
-				amVampiric, amSwitch, amInvulnerable, amGirder, amJetpack,
-				amPortalGun, amTeleport, amResurrector, amLaserSight, amLowGravity,
-				amAirAttack, amNapalm, amMineStrike, amDrillStrike,
-				amKamikaze, amSnowball, amSeduction}]]
-
 local crazyWeps = {amWatermelon, amHellishBomb, amBallgun, amRCPlane}
 
 local groundWeps = 	{amBee, amShotgun,amDEagle,amFirePunch, amWhip,
 				amPickHammer, amBaseballBat, amCake,amBallgun,
-				amRCPlane, amSniperRifle, amBirdy, amBlowTorch, amGasBomb,
-				amFlamethrower, amSMine, amMortar, amHammer}
+				amRCPlane, amSniperRifle, amBirdy, amBlowTorch,
+				amFlamethrower, amMortar, amHammer}
 
 local ropeWeps = {amGrenade, amClusterBomb, amBazooka, amMine, amDynamite,
-				amWatermelon, amHellishBomb, amDrill, amMolotov}
+				amWatermelon, amHellishBomb, amDrill, amMolotov,
+				amSMine, amGasBomb}
+
+local msgColorTech = 0xFFBA00FF
+local msgColorWarn = 0xFF4000FF
 
 -- 0.9.18+ extra custom data for preset maps
 local MapList =
 	{
-	--name,      						surfer, roof, 	LRwalls
-	{"Atlantis Shoppa", 			    true, 	false, true},
-	{"BambooPlinko", 				    true,	false, true},
-	{"BrickShoppa", 				    false, 	false, true},
-	{"BubbleFlow",   					true, 	false, true},
-	{"Cave",       						false, 	false, true},
-	{"Glass Shoppa",      				true, 	false, true},
-	{"HardIce",      					false, 	false, true},
-	{"Industrial",       				false,	false, true},
-	{"Islands",       					true, 	false, true},
-	{"Hedgelove",       				true, 	false, true},
-	{"NeonStyle",       				false, 	false, true},
-	{"Octorama",       					false, 	false, true},
+	--name,					surfer, roof, 	LRwalls
+	{"Alien",				true, 	true,  true},
+	{"Atlantis Shoppa",			true, 	true,  true},
+	{"BasketballField",			false,  false, false},
+	{"BattleCity_v1",			true,	true, true},
+	{"BIGshoppa",				true,	true, true},
+	{"BambooPlinko",			true,	false, true},
+	{"BoatWxW",				true,	true,  true},
+	{"BrickShoppa",				false, 	false, true},
+	{"BubbleFlow",				true, 	false, true},
+	{"Citrouille",				true, 	true,  true},
+	{"Cave",				false, 	false, true},
+	{"Cheese_Ropes", 			false, 	true,  true},
+	{"CookieShoppa", 			true, 	false, true},
+	{"CrossRopes",				false,	false, true},
+	{"FutuShoppa",				true,	false, true},
+	{"Garden",				false,	false, true},
+	{"Glass Shoppa",			true, 	false, true},
+	{"GlassShoppa2",			true, 	false, true},
+	{"HardIce",      			false, 	false, true},
+	{"Industrial",       			false,	false, true},
+	{"Islands",       			true, 	false, true},
+	{"IslandsFlipped",     			true, 	false, true},
+	{"IslandsRearranged",  			true, 	false, true},
+	{"Hedgelove",       			true, 	false, true},
+	{"HellishRopes",       			false, 	false, true},
+	{"Hedgeland_v1",			true,	false, true},
+	{"HeyLandShoppa",			false,	false, true},
+	{"NeonStyle",       			false, 	false, true},
+	{"MaskedRopes",       			false, 	false, true},
+	{"Octorama",       			false, 	false, true},
 	{"red vs blue - Castle",     		true, 	false, true},
 	{"red vs blue - castle2",     		true, 	false, true},
-	{"red vs blue - True Shoppa Sky",   true, 	false, true},
-	{"Ropes",       					false, 	false, true},
-	{"Ropes Rearranged",      			false, 	false, true},
+	{"red vs blue - True Shoppa Sky",	true,	false, true},
+	{"Ropes",       			false, 	false, true},
+	{"RopeLikeAKingInHellWithNeon",		false, 	true,  true},
+	{"Ropes Flipped",      			false, 	false, true},
+	{"Ropes Rearranged",      		false, 	false, true},
 	{"RopesRevenge Flipped",    		true, 	false, true},
-	{"Ropes Three",      				false, 	false, true},
-	{"RopesTwo",      					false, 	false, true},
-	{"ShapeShoppa1.0",     				true, 	false, true},
-	{"ShappeShoppa Darkhow",      		true, 	false, true},
-	{"ShoppaCave2",      				true, 	false, true},
-	{"ShoppaFun",      					true, 	false, true},
-	{"ShoppaGolf",      				false, 	false,  true},
-	{"ShoppaHell",      				false, 	true,  false},
-	{"ShoppaKing",       				false, 	false, false},
-	{"ShoppaNeon",       				false, 	false, true},
-	{"ShoppaSky",       				false, 	false, true},
-	{"Shoppawall",       				false, 	false, true},
-	{"SkatePark",       				false, 	false, true},
-	{"SloppyShoppa",      				false, 	false, true},
-	{"Sticks",       					true, 	false, true},
-	{"Symmetrical Ropes ",       		false, 	false, true},
-	{"Tetris",       					false, 	false, true},
-	{"TransRopes2",      				false, 	false, true},
-	{"Wildmap",      					false, 	false, true},
-	{"Winter Shoppa",      				false, 	false, true},
-	{"2Cshoppa",      					true, 	false, true}
+	{"RopesThree",      			false, 	false, true},
+	{"RopesTwo",      			false, 	false, true},
+	{"Ruler",	      			false, 	false, true},
+	{"SandShoppa",				false,	false, true},
+	{"ShapeShoppa1.0",     			true, 	false, true},
+	{"ShapeShoppa Darkhow",      		true, 	false, true},
+	{"SheepyShoppa_v2",      		true, 	false, true},
+	{"shopppa",				false,  true,  true},
+	{"ShoppaCave2",      			true, 	false, true},
+	{"ShoppaChallenge",    			false, 	true, true},
+	{"ShoppaDesert",    			false, 	false, true},
+	{"ShoppaEvoRope_v1",			true, 	false, true},
+	{"ShoppaFun",      			true, 	false, true},
+	{"ShoppaFun2",      			true, 	false, true},
+	{"ShoppaGolf",      			false, 	false, true},
+	{"ShoppaHalloween",    			false, 	false, true},
+	{"ShoppaHell",      			false,	true,  false},
+	{"ShoppaHellFlipped",  			true,	true,  false},
+	{"ShoppaHellRemake",			false,	true,  false},
+	{"ShoppaKing",       			false, 	true, false},
+	{"ShoppaKingFlipped",      		true, 	false, false},
+	{"ShoppaKingSideways",      		true, 	true,  false},
+	{"ShoppaMeme",				false,	true, false},
+	{"ShoppaNeon",       			false, 	false, true},
+	{"ShoppaNeonFlipped",			true, 	false, true},
+	{"ShoppaOnePiece2",			false, 	true, false},
+	{"ShoppaQuotes2",			false,  true,  true},
+	{"ShoppaRainbow",			false,  false, false},
+	{"ShoppaRadigme",			false,  true,  true},
+	{"ShoppaSilhouette",			false,  false, true},
+	{"ShoppaSpace",				true,   false, true},
+	{"ShoppaSea",				true,  false, false},
+	{"ShoppaShapex_v1",			false,  true, true},
+	{"ShoppaSparkle",			true,  true, true},
+	{"ShoppaSky",				false,  false, true},
+	{"ShoppaSky2",				true,  false, true},
+	{"ShoppaSsion",				false,  false, true},
+	{"ShoppaStyle2",			true,  false, true},
+	{"ShoppaThology",			false,  false, true},
+	{"ShoppaTournament2012",		false,  false, true},
+	{"ShoppaWild",				false,  false, true},
+	{"Shoppawall",				false,  false, false},
+	{"ShoppaWall2",				false,  false, false},
+	{"ShBall",				false,  true, false},
+	{"ShHell",				false,  true, false},
+	{"ShNeon",       			false, 	false, true},
+	{"ShoppaSky",       			false, 	false, true},
+	{"SloppyShoppa",       			false, 	true,  true},
+	{"SloppyShoppa2",      			false, 	true,  true},
+	{"SkatePark",       			false, 	true,  true},
+	{"Snow_Ropes",       			false, 	true, false},
+	{"Sticks",       			true, 	false, true},
+	{"Symmetrical Ropes",       		false, 	false, true},
+	{"SpartanShoppa",       		false, 	true,  true},
+	{"Tetris",       			false, 	false, true},
+	{"TransRopes2",      			false, 	false, true},
+	{"TRBShoppa",      			false, 	false, true},
+	{"TrickyShoppa",      			false, 	true, false},
+	{"Wildmap",      			false, 	false, true},
+	{"Winter Shoppa",      			false, 	false, true},
+	{"WarShoppa",      			false, 	true,  true},
+	{"2Cshoppa",      			true, 	false, true},
 	}
 
+local Ropes_WallSet = {
+	{ add="none", {299,932,20,856}, {4056,0,30,1788} },
+	{ add="none", {299,109,20,779}, {4056,0,30,1788} },
+	{ add="none", {299,109,20,779}, {299,932,20,856}, {4056,0,30,1788} },
+	{ add="default", {2253,326,20,574}, {3280,326,33,253}, needsborder=false },
+	{ add="roof", {2322,326,457,20} },
+	{ add="default", {1092,934,54,262}, {2822,323,33,137}, needsborder=false },
+	{ add="none", {203,1193,20,595}, {3280,326,20,253}, needsborder=false },
+}
+local Shoppawall_WallSet = {
+	{ add="none", {80+290,61+878,20,1018}, {3433+290,61+878,20,1018}, default=true, needsborder=false },
+}
+
+-- List of map with special wall settings
+local SpecialMapList = {
+	["Ropes"] = Ropes_WallSet,
+	["HellishRopes"] = Ropes_WallSet,
+	["MaskedRopes"] = Ropes_WallSet,
+	["TransRopes2"] = Ropes_WallSet,
+	["ShoppaKing"] = {
+		{ add="none", {3777,1520,50,196}, {1658,338,46,670}, needsborder=false },
+		{ add="none", {125,0,30,2048}, {4066,515,30,1528}, default=true},
+	},
+	["ShoppaHell"] = {
+		{ add="none", {3491,697,30,1150}, {0,0,30,1847}, default=true},
+		{ add="none", {3810,0,30,1616}, {0,0,30,1847}, },
+		{ add="none", {2045,832,20,260}, {2107,832,20,260}, needsborder=false },
+		{ add="default", {2035,831,30,263}, {3968,1668,31,383}, needsborder=false },
+	},
+	["ShoppaNeon"] = {
+		{ add="default", {980,400,20,300}, {1940,400,20,300}, {3088,565,26,284}, {187,270,28,266}, needsborder=false },
+	},
+	["Shoppawall"] = Shoppawall_WallSet,
+	["ShoppaWall2"] = Shoppawall_WallSet,
+	["ShoppaDesert"] = {
+		{ add="none", {2322,349,20,471}, {295,93,24,1479}, needsborder=false },
+		{ add="none", {3001,1535,20,232}, {2264,349,20,495},{716,696,20,119}, needsborder=false },
+		{ add="leftright", {209,656,20,367},{2810,838,20,96}, needsborder=false},
+		{ add="none", {2649,0,445,20}, {2322,349,947,20},{299,696,381,20}},
+	},
+	["ShoppaOnePiece2"] = {
+		{ add="default", {42,0,20,2048}, {4048,0,20,2048}, needsborder=false, },
+		{ add="default", {42,0,20,2048}, {3852,273,20,1637}, needsborder=false, default="noborder" },
+	},
+	["ShoppaWild"] = {
+		{ add="default", {2123,1365,20,293}, {3102,1365,20,293}, {1215,1391,20,291}, needsborder=false },
+		{ add="none", {144,167,1904,20}, {2350,167,753,20}, {3793,167,303,20}, needsborder=false},
+	},
+	["ShoppaRainbow"] = {
+		{ add="none", {67+602,61+80,20,1847}, {2779+602,61+80,20,1847}, needsborder=false },
+	},
+}
+
 function BoolToCfgTxt(p)
 	if p == false then
 		return loc("Disabled")
@@ -164,15 +471,41 @@
 	end
 end
 
-function LoadConfig(p)
+function AttackRuleToCfgTxt(attackRule)
+	if attackRule == nil then
+		return loc("Disabled")
+	elseif attackRule == "ABL" then
+		return loc("All But Last")
+	elseif attackRule == "KTL" then
+		return loc("Kill The Leader")
+	else
+		return "ERROR"
+	end
+end
 
+function NewWallSet(newWallSet, wType)
+	-- Filter out wall sets which are not in allowed categories or have too many or few walls
+	if allowedWallSetTypes[wType] == true then
+		local inBounds = true
+		if minWalls ~= nil and #newWallSet < minWalls then
+			inBounds = false
+		end
+		if maxWalls ~= nil and #newWallSet > maxWalls then
+			inBounds = false
+		end
+		if inBounds then
+			table.insert(wallSets, newWallSet)
+		end
+	end
+end
+
+function MapsInit()
+	mapID = nil
 	margin = 20
-	mapID = nil
 
 	-- 0.9.17
 	if Map == "CHANGE_ME" then
 		AddCaption(loc("For improved features/stability, play 0.9.18+"))
-		--AddWall(10,10,4085,margin)
 		AddWall(10,10,margin,2025)
 		AddWall(4085-margin,10,margin,2025)
 	end
@@ -181,52 +514,151 @@
 	for i = 1, #MapList do
 		if Map == MapList[i][1] then
 			mapID = i
-			--AddCaption(MapList[i][1] .. " found. reqSurf is " .. BoolToCfgTxt(MapList[i][2]))
 		end
 	end
 
-	if (p == 1) and (mapID ~= nil) then
-		requireSurfer = MapList[mapID][2]
-	end
+	local left, right, roof
+	left = {LeftX+10,TopY+10,margin,WaterLine}
+	right = {RightX-10-margin,TopY+10,margin,WaterLine}
+	roof = {LeftX+10,TopY+10,RightX-LeftX-20,margin}
+
+	local border = MapHasBorder()
 
 	if mapID ~= nil then
-
-		-- add a wall to the roof
-		if MapList[mapID][3] == true then
-			AddWall(LeftX+10,TopY+10,RightX-LeftX-20,margin)
-		end
-
-		-- add walls on the left and right border
-		if MapList[mapID][4] == true then
-			AddWall(LeftX+10,TopY+10,margin,WaterLine)
-			AddWall(RightX-10-margin,TopY+10,margin,WaterLine)
+		if border then
+			if MapList[mapID][3] == true then
+				NewWallSet({roof, desc=loc("Roof")}, "roof")
+				wallSetID = #wallSets
+			end
+			if MapList[mapID][4] == true then
+				NewWallSet({left, right, desc=loc("Left and right")}, "leftright")
+				wallSetID = #wallSets
+			end
+			if MapList[mapID][3] == true and MapList[mapID][4] == true then
+				NewWallSet({left, right, roof, desc=loc("Left, right and roof")}, "leftrightroof")
+			end
 		end
 
 		-- add map specific walls
-		if Map == "Ropes" then
-			AddWall(1092,934,54,262)
-			AddWall(2822,323,33,137)
-		elseif Map == "ShoppaKing" then
-			AddWall(3777,1520,50,196)
-			AddWall(1658,338,46,670)
-		elseif Map == "ShoppaHell" then
-			AddWall(2035,831,30,263)
-			AddWall(3968,1668,31,383)
-		elseif Map == "ShoppaNeon" then
-			AddWall(980,400,20,300)
-			AddWall(1940,400,20,300)
-			AddWall(3088,565,26,284)
-			AddWall(187,270,28,266)
+		if SpecialMapList[Map] ~= nil then
+			local insideID = 1
+			local previousInside = nil
+			local mixedID = 1
+			local previousMixed = nil
+
+			-- Helper function to build the wall set name.
+			-- Basically just to ensure that names like "Inside 1" are only used when there are at least 2 "Insides"
+			local function newInsideOrMixed(ws, previous_ws, id, string, stringD)
+				if id == 1 then
+					ws.desc = string
+				else
+					ws.desc = string.format(stringD, id)
+				end
+				if id == 2 then
+					previous_ws.desc = string.format(stringD, id-1)
+				end
+				id = id + 1
+				previous_ws = ws
+				return id, previous_ws
+			end
+			for ws=1,#SpecialMapList[Map] do
+				local walls = SpecialMapList[Map][ws]
+				if walls.needsborder == false then
+					local newwallset2 = {}
+					for w=1,#walls do
+						table.insert(newwallset2, walls[w])
+					end
+					insideID, previousInside = newInsideOrMixed(newwallset2, previousInside, insideID, loc("Inside"), loc("Inside %d"))
+					newwallset2.custom = true
+					NewWallSet(newwallset2, "inside")
+					if SpecialMapList[Map][ws].default == "noborder" then
+						wallSetID = #wallSets
+					end
+				end
+				local newwallset = {}
+				if border then
+					if walls.add == "all" then
+						table.insert(newwallset, roof)
+						table.insert(newwallset, left)
+						table.insert(newwallset, right)
+					elseif walls.add == "default" then
+						if MapList[mapID][3] == true then
+							table.insert(newwallset, roof)
+						end
+						if MapList[mapID][4] == true then
+							table.insert(newwallset, left)
+							table.insert(newwallset, right)
+						end
+					elseif walls.add == "roof" then
+						table.insert(newwallset, roof)
+					elseif walls.add == "leftright" then
+						table.insert(newwallset, left)
+						table.insert(newwallset, right)
+					end
+				end
+				for w=1,#walls do
+					table.insert(newwallset, walls[w])
+				end
+				if border and ((walls.add ~= "none" and walls.add ~= nil) or walls.needsborder ~= false) then
+					mixedID, previousMixed = newInsideOrMixed(newwallset, previousMixed, mixedID, loc("Mixed"), loc("Mixed %d"))
+					newwallset.custom = true
+					NewWallSet(newwallset, "mixed")
+				end
+				if SpecialMapList[Map][ws].default == true then
+					wallSetID = #wallSets
+				end
+			end
+		end
+
+	else
+		if border then
+			NewWallSet({roof, desc=loc("Roof")}, "roof")
+			NewWallSet({left, right, desc=loc("Left and right")}, "leftright")
+			NewWallSet({left, right, roof, desc=loc("Left, right and roof")}, "leftrightroof")
+			wallSetID = 2
+		end
+	end
+
+	-- Choose random map when without without menu
+	if useMenu == false and #wallSets > 0 then
+		wallSetID = GetRandom(#wallSets)+1
+	end
+	-- Select first wall set by default if we still haven't selected anything for some reason
+	if wallSetID == 0 and #wallSets > 0 then
+		wallSetID = 1	
+	end
+	-- But disabled walls from script parameter have higher priority
+	if WBC == false then
+		wallSetID = 0
+	end
+
+	if CanSurf() == false then
+		requireSurfer = false
+	end
+end
+
+function LoadConfig(p)
+	ClearWalls()
+	if mapID ~= nil then
+		if p > 0 then
+			local walls = wallSets[p]
+			for i=1,#walls do
+				AddWall(walls[i][1], walls[i][2], walls[i][3], walls[i][4])
+			end
 		end
 
 	-- if map is unrecognized, add two walls on the side borders
 	-- also, if version of hw is not 0.9.17 or lower
 	elseif Map ~= "CHANGE_ME" then
-		AddWall(LeftX+10,TopY+10,margin,WaterLine)
-		AddWall(RightX-10-margin,TopY+10,margin,WaterLine)
+		if p == 1 or p == 3 then
+			AddWall(LeftX+10,TopY+10,RightX-LeftX-20,margin)
+		end
+		if p == 2 or p == 3 then
+			AddWall(LeftX+10,TopY+10,margin,WaterLine)
+			AddWall(RightX-10-margin,TopY+10,margin,WaterLine)
+		end
 	end
 
-
 end
 
 function AddWall(zXMin,zYMin, zWidth, zHeight)
@@ -239,11 +671,39 @@
 
 end
 
+function ClearWalls()
+
+	wX = {}
+	wY = {}
+	wWidth = {}
+	wHeight = {}
+	wTouched = {}
+
+end
+
+-- Draw a single point for the crate radar
 function DrawBlip(gear)
-	SetVisualGearValues(getGearValue(gear,"CIRC"), getGearValue(gear,"RX"), getGearValue(gear,"RY"), 100, 255, 1, 10, 0, 40, 3, GetClanColor(GetHogClan(CurrentHedgehog))-rAlpha)
+	if GetGearType(gear) ~= gtCase then
+		return
+	end
+
+	local baseColor, radius, alpha
+	if getGearValue(gear, "frozen") then
+		radius = 25
+		baseColor = 0xFFFFFFFF
+		alpha = math.min(255, rAlpha+127)
+	else
+		radius = 40
+		baseColor = GetClanColor(GetHogClan(CurrentHedgehog))
+		alpha = rAlpha
+	end
+	SetVisualGearValues(getGearValue(gear,"CIRC"), getGearValue(gear,"RX"), getGearValue(gear,"RY"), 100, 255, 1, 10, 0, radius, 3, baseColor-alpha)
 end
 
 function TrackRadarBlip(gear)
+	if GetGearType(gear) ~= gtCase then
+		return
+	end
 
 	-- work out the distance to the target
 	g1X, g1Y = GetGearPosition(CurrentHedgehog)
@@ -294,21 +754,24 @@
 
 function HandleCircles()
 
-	-- enable this if you want the radar to only show for a few seconds
-	-- after you spawn the crate
-	--[[if rAlpha ~= 255 then
-
-		rPingTimer = rPingTimer + 1
-		if rPingTimer == 100 then
-			rPingTimer = 0
-
-			rAlpha = rAlpha + 5
-			if rAlpha >= 255 then
-				rAlpha = 255
+	if radarMode == 0 then
+		rAlpha = 0
+	elseif radarMode == 1 then
+		-- Only show radar for a short time after a crate spawn
+		if rAlpha ~= 255 then
+			rPingTimer = rPingTimer + 1
+			if rPingTimer == 100 then
+				rPingTimer = 0
+	
+				rAlpha = rAlpha + 5
+				if rAlpha >= 255 then
+					rAlpha = 255
+				end
 			end
 		end
-
-	end]]
+	elseif radarMode == 2 then
+		rAlpha = 255
+	end
 
 	runOnGears(DrawBlip)
 
@@ -324,10 +787,10 @@
 
 end
 
+-- Returns true if crates are allowed to be accessed right now (used for unfreezing and spawning)
+function AreCratesUnlocked()
 
-function CheckCrateConditions()
-
-	crateSpawn = true
+	local crateSpawn = true
 
 	if requireSurfer == true then
 		if hasSurfed == false then
@@ -341,14 +804,60 @@
 		end
 	end
 
-	if crateSpawn == true then
+	return crateSpawn
+
+end
+
+-- Freeze all crates,
+function FreezeCrates()
+
+	local cratesFrozen = 0
+	for crate, isCrate in pairs(crates) do
+		local state = GetState(crate)
+		-- Freeze crate if it wasn't already frozen
+		if band(state, gstFrozen) == 0 then
+			cratesFrozen = cratesFrozen + 1
+			SetState(crate, bor(GetState(crate), gstFrozen))
+			setGearValue(crate, "frozen", true)
+		end
+	end
+	-- Play sound if at least one new (!) crate was frozen
+	if cratesFrozen > 0 then
+		PlaySound(sndHogFreeze)
+	end
+
+end
+
+-- Unfreeze all crates
+function UnfreezeCrates()
+
+	for crate, isCrate in pairs(crates) do
+		SetState(crate, band(GetState(crate), bnot(gstFrozen)))
+		setGearValue(crate, "frozen", false)
+	end
+
+end
+
+function CheckCrateConditions()
+
+	local crateSpawn = AreCratesUnlocked()
+
+	if crateSpawn == true and crateSpawned == false then
+		UnfreezeCrates()
 		if allowCrate == true then
-		--if (crateG == nil) and (allowCrate == true) then
-			--AddCaption("")
-			SpawnAmmoCrate(0, 0, weapons[1+GetRandom(#weapons)] )
+			local cratesInGame = crateGearsInGame
+			local toSpawn = cratesPerTurn
+			if cratesInGame + toSpawn > maxCrates then
+				toSpawn = maxCrates - cratesInGame
+			end
+			for i=1,toSpawn do
+				SpawnAmmoCrate(0, 0, weapons[1+GetRandom(#weapons)] )
+			end
 			rPingTimer = 0
 			rAlpha = 0
-			PlaySound(sndWarp)
+			if toSpawn > 0 then
+				PlaySound(sndWarp)
+			end
 		end
 	end
 
@@ -357,7 +866,7 @@
 function onGearWaterSkip(gear)
 	if gear == CurrentHedgehog then
 		hasSurfed = true
-		AddCaption(loc("Surfer!"),0xffba00ff,capgrpMessage2)
+		AddCaption(loc("Surfer!"), 0xFFFFFFFF, capgrpMessage2)
 	end
 end
 
@@ -373,17 +882,18 @@
 			AddCaption(loc("All walls touched!"))
 			allWallsHit = true
 			if (requireSurfer == true) and (hasSurfed == false) then
-				AddCaption(loc("Go surf!"),0xffba00ff,capgrpMessage2)
+				AddCaption(loc("Go surf!"), 0xFFFFFFFF, capgrpMessage2)
 			end
 		else
-			AddCaption(loc("Walls Left") .. ": " .. wallsLeft)
+			AddCaption(string.format(loc("Walls left: %d"), wallsLeft))
 		end
 
 	end
 
 	wTouched[id] = true
-	tempE = AddVisualGear(GetX(CurrentHedgehog), GetY(CurrentHedgehog), vgtSmoke, 0, false)
-	--PlaySound(sndVaporize) -- yeah, this is just annoying as shit
+	if #wTouched > 0 then
+		tempE = AddVisualGear(GetX(CurrentHedgehog), GetY(CurrentHedgehog), vgtSmoke, 0, false)
+	end
 
 end
 
@@ -419,75 +929,158 @@
 		effectTimer = 1
 
 		for i = 1, #wTouched do
-			if wTouched[i] == true then
-				--bCol = GetClanColor(GetHogClan(CurrentHedgehog))
-			else
-				--bCol = 0xFFFFFFFF
+			if wTouched[i] == false then
 				bCol = GetClanColor(GetHogClan(CurrentHedgehog))
 				BorderSpark(wX[i],wY[i],wWidth[i],wHeight[i], bCol)
 			end
-			--BorderSpark(wX[i],wY[i],wWidth[i],wHeight[i], bCol)
 		end
 
 	end
 
 end
 
+function PlaceWarn()
+	PlaySound(sndDenied)
+	AddCaption(loc("Please place your hedgehog first!"), msgColorWarn, capgrpMessage2)
+end
+
 function onLJump()
-	if roundN < 2 then
-		roundN = 100
+	if roundN == 1 then
+		PlaySound(sndPlaced)
 		SetInputMask(0xFFFFFFFF)
-		TurnTimeLeft = 1
-		AddCaption(loc("Configuration accepted."),0xffba00ff,capgrpMessage)
-		HideMission()
+		AddCaption(loc("Configuration accepted."), msgColorTech, capgrpMessage)
+		if GetGameFlag(gfPlaceHog) then
+			TurnTimeLeft = PlacementTime
+			AddAmmo(CurrentHedgehog, amTeleport, 100)
+			SetWeapon(amTeleport)
+			AddCaption(
+				string.format(loc("%s, place the first hedgehog!"), GetHogTeamName(CurrentHedgehog)),
+				0xFFFFFFFF,
+				capgrpMessage2
+			)
+			roundN = 2
+		else
+			TurnTimeLeft = TurnTime
+			AddCaption(string.format(loc("Let's go, %s!"), GetHogTeamName(CurrentHedgehog)), 0xFFFFFFFF, capgrpMessage2)
+			roundN = 100
+			wallsLeft = #wTouched
+			allowCrate = true
+		end
+		PlaySound(sndYesSir, CurrentHedgehog)
+		FinalizeMenu()
+	elseif roundN == 2 then
+		PlaceWarn()
+	elseif roundN == 100 then
+		if CBA and not crateCollected then
+			if (GetCurAmmoType() ~= amRope) and
+				(GetCurAmmoType() ~= amSkip) and
+				(GetCurAmmoType() ~= amNothing) and
+				(ropeG ~= nil)
+			then
+				AddCaption(loc("You must first collect a crate before you attack!"), msgColorWarn, capgrpMessage2)
+				PlaySound(sndDenied)
+			end
+		end
 	end
 end
 
 function onAttack()
-
-	if roundN < 2 then
-
-		if menuIndex == 1 then
-
-			if #wTouched > 0 then
-				for i = 1, #wTouched do
-					wTouched[i] = nil
-					wX[i] = nil
-					wY[i] = nil
-					wWidth[i] = nil
-					wHeight[i] = nil
-				end
-			else
-				LoadConfig(2)
-			end
-
-		elseif menuIndex == 2 then
-			requireSurfer = not(requireSurfer)
-		elseif menuIndex == 3 then
-			AFR = not(AFR)
-		elseif menuIndex == 4 then
-			allowCrazyWeps = not(allowCrazyWeps)
+	if roundN == 1 then
+		if menu[menuIndex].activate ~= nil then
+			menu[menuIndex].activate()
+		else
+			menu[menuIndex].doNext()
 		end
 
 		UpdateMenu()
 		configureWeapons()
 		HandleStartingStage()
 
-	elseif (AFR == true) then
+		PlaySound(sndSwitchHog)
 
-		if (GetCurAmmoType() ~= amRope) and
-			(GetCurAmmoType() ~= amSkip) and
-			(GetCurAmmoType() ~= amNothing)
-		then
-			AddCaption(loc("You may only attack from a rope!"),0xffba00ff,capgrpMessage2)
+	elseif roundN == 2 then
+		if GetCurAmmoType() ~= amSkip and GetCurAmmoType() ~= amNothing then
+			PlaceWarn()
 		end
 
+	elseif roundN == 100 then
+		local weaponSelected = (GetCurAmmoType() ~= amRope) and
+			(GetCurAmmoType() ~= amSkip) and
+			(GetCurAmmoType() ~= amNothing) and
+			(ropeG == nil)
+
+		if weaponSelected then
+			if AFR and CBA and not crateCollected then
+				AddCaption(loc("You must attack from a rope, after you collected a crate!"), msgColorWarn, capgrpMessage2)
+				PlaySound(sndDenied)
+			elseif AFR then
+				AddCaption(loc("You may only attack from a rope!"), msgColorWarn, capgrpMessage2)
+				PlaySound(sndDenied)
+			elseif CBA and not crateCollected then
+				AddCaption(loc("You must first collect a crate before you attack!"), msgColorWarn, capgrpMessage2)
+				PlaySound(sndDenied)
+			end
+		end
 	end
+end
 
+function onSwitch()
+	-- Must be in-game, hog must be controlled by player and hog must be on rope or have rope selected
+	if roundN == 100 and CurrentHedgehog ~= nil and band(GetState(CurrentHedgehog), gstHHDriven) ~= 0 and (ropeG ~= nil or GetCurAmmoType() == amRope) then
+		-- Toggle radar mode
+		radarMode = radarMode + 1
+		if radarMode > 2 then
+			radarMode = 0
+		end
+		local message
+		if radarMode == 0 then
+			message = loc("Radar: On")
+		elseif radarMode == 1 then
+			message = loc("Radar: Show after crate drop")
+		elseif radarMode == 2 then
+			message = loc("Radar: Off")
+		end
+		AddCaption(message, GetClanColor(GetHogClan(CurrentHedgehog)), capgrpAmmostate)
+		-- Remember the radar mode for this team to restore it on the team's next turn
+		setTeamValue(GetHogTeamName(CurrentHedgehog), "radarMode", radarMode)
+	end
+end
+
+function onLeft()
+	if roundN == 1 then
+		if menu[menuIndex].doPrev ~= nil then
+			menu[menuIndex].doPrev()
+		else
+			menu[menuIndex].activate()
+		end
+
+		UpdateMenu()
+		configureWeapons()
+		HandleStartingStage()
+
+		PlaySound(sndSwitchHog)
+	end
+end
+
+function onRight()
+	if roundN == 1 then
+		if menu[menuIndex].doNext ~= nil then
+			menu[menuIndex].doNext()
+		else
+			menu[menuIndex].activate()
+		end
+
+		UpdateMenu()
+		configureWeapons()
+		HandleStartingStage()
+
+		PlaySound(sndSwitchHog)
+	end
 end
 
 function onDown()
-	if roundN < 2 then
+	if roundN == 1 then
+		PlaySound(sndSteps)
 		menuIndex = menuIndex +1
 		if menuIndex > #menu then
 			menuIndex = 1
@@ -497,7 +1090,8 @@
 end
 
 function onUp()
-	if roundN < 2 then
+	if roundN == 1 then
+		PlaySound(sndSteps)
 		menuIndex = menuIndex -1
 		if 	menuIndex == 0 then
 			menuIndex = #menu
@@ -506,10 +1100,88 @@
 	end
 end
 
+function parseBool(key, default)
+	if params[key]=="true" then
+		return true
+	elseif params[key]=="false" then
+		return false
+	else
+		return default
+	end
+end
+
+function parseInt(key, default, min, max)
+	local num = tonumber(params[key])
+	if type(num) ~= "number" then
+		return default
+	end
+	if min ~= nil then
+		num = math.max(min, num)
+	end
+	if max ~= nil then
+		num = math.min(max, num)
+	end
+	return num
+end
+
+function onParameters()
+	parseParams()
+	local tmpParam
+	useMenu = parseBool("menu", useMenu)
+	requireSurfer = parseBool("SBC", requireSurfer)
+	AFR = parseBool("AFR", AFR)
+	CBA = parseBool("CBA", CBA)
+	if params["attackrule"] == "ABL" then
+		attackRule = "ABL"
+	elseif params["attackrule"] == "KTL" then
+		attackRule = "KTL"
+	end
+	allowCrazyWeps = parseBool("SW", allowCrazyWeps)
+	maxCrates = parseInt("maxcrates", maxCrates, 1, maxCratesHard)
+	cratesPerTurn = parseInt("cratesperturn", cratesPerTurn, 1, maxCrates)
+	local wallsParam = params["walls"]
+	local wallsParamSelection = false
+	if wallsParam ~= nil then
+		if wallsParam == "all" then
+			wallsParamSelection = true
+			allowedWallSetTypes = {}
+			for i=1,#allWallSetTypes do
+				allowedWallSetTypes[allWallSetTypes[i]] = true
+			end
+		elseif wallsParam == "none" then
+			WBC = false
+			allowedWallSetTypes = {}
+		else
+			wallsParamSelection = true
+			allowedWallSetTypes = {}
+			local parsedWords = {}
+			for k,v in string.gmatch(wallsParam, "(%w+)") do
+				table.insert(parsedWords, k)
+			end
+			for i=1,#allWallSetTypes do
+				for j=1,#parsedWords do
+					if allWallSetTypes[i] == parsedWords[j] then
+						allowedWallSetTypes[allWallSetTypes[i]] = true
+					end
+				end
+			end
+		end
+	end
+
+	-- Upper and lower bounds
+	local wallsNum = parseInt("wallsnum", nil, 0)
+	if wallsNum == 0 then
+		WBC = false
+	end
+	minWalls = wallsNum
+	maxWalls = wallsNum
+	-- minwalls and maxwalls take precedence over wallsnum
+	minWalls = parseInt("minwalls", minWalls, 1)
+	maxWalls = parseInt("maxwalls", maxWalls, 1)
+end
+
 function onGameInit()
 
-	ClearGameFlags()
-	EnableGameFlags(gfRandomOrder, gfBorder, gfSolidLand) --, gfInfAttack
 	HealthCaseProb = 0
 	CaseFreq = 0
 
@@ -558,14 +1230,52 @@
 
 function onGameStart()
 
-	LoadConfig(1)
+	trackTeams()
+
+	MapsInit()
+	LoadConfig(wallSetID)
 	configureWeapons()
-	UpdateMenu()
-	HandleStartingStage()
+
+	-- ABL or KTL only make sense with at least 3 teams, otherwise we disable it
+	if TeamsCount < 3 or ClansCount < 3 then
+		attackRule = nil
+	end
 
+	if useMenu then
+		ShowMission(loc("Wall to wall"), loc("Please wait …"), "", 2, 300000)
+		UpdateMenu()
+	else
+		if GetGameFlag(gfPlaceHog) then
+			roundN = 2
+			FinalizeMenu()
+		else
+			allowCrate = false
+			roundN = 100
+			FinalizeMenu()
+		end
+	end
 end
 
 function onNewTurn()
+	turnsCount = turnsCount + 1
+
+	if roundN == 0 then
+		roundN = 1
+	end
+
+	if GetGameFlag(gfPlaceHog) then
+		if roundN < 2 then
+			SetWeapon(amSkip)
+			AddAmmo(CurrentHedgehog, amTeleport, 0)
+			TurnTimeLeft = -1
+			SetInputMask(0)
+		end
+		if roundN == 2 then
+			if turnsCount > hogCount then
+				roundN = 100
+			end
+		end
+	end
 
 	wallsLeft = #wTouched
 
@@ -573,88 +1283,317 @@
 		wTouched[i] = false
 	end
 
-	allowCrate = true
-
 	hasSurfed = false
 	allWallsHit = false
+	crateCollected = false
 
-	crateG = nil
+	crateSpawned = false
+
+	if roundN == 100 then
+		allowCrate = crateGearsInGame < maxCrates
+
+		local teamName = GetHogTeamName(CurrentHedgehog)
+
+		-- Restore team's radar mode
+		radarMode = getTeamValue(teamName, "radarMode")
+
+		if not AreCratesUnlocked() then
+			FreezeCrates()
+		end
+
+		-- Check the attack rule violation of the *previous* team and apply penalties
+		-- This function will do nothiong in the first turn since previousTeam is still nil
+		CheckAttackRuleViolation(previousTeam)
 
-	-- new config stuff
-	roundN = roundN + 1
-	if roundN < 2 then
+		previousTeam = teamName
+
+		-- Update attack rule information for this turn
+		UpdateLastAndLeaderTeams()
+		teamsAttacked = {}
+
+		-- Was the team violating the attackRule the last time?
+		if getTeamValue(teamName, "skipPenalty") then
+			-- Then take away this turn
+			AddCaption(string.format(loc("%s must skip this turn for rule violation ."), teamName), msgColorWarn, capgrpMessage)
+			TurnTimeLeft = 0
+			setTeamValue(teamName, "skipPenalty", false)
+		end
+
+	else
+		allowCrate = false
+	end
+
+	if roundN == 1 then
 		TurnTimeLeft = -1
 		SetInputMask(0)
 		allowCrate = false
-		HandleStartingStage() -- new
+		UpdateMenu()
+		AddCaption(string.format(loc("%s may choose the rules."), GetHogTeamName(CurrentHedgehog)), msgColorTech, capgrpGameState)
+		HandleStartingStage()
 	end
 
 end
 
+function CanSurf()
+	if mapID ~= nil then
+		if GetGameFlag(gfBottomBorder) and WaterRise == 0 then
+			return false
+		else
+			return MapList[mapID][2]
+		end
+	else
+		return nil
+	end
+end
+
 function UpdateMenu()
+	local teamInfo
+	if roundN == 1 and CurrentHedgehog ~= nil then
+		teamInfo = string.format(loc("%s, you may choose the rules."), GetHogTeamName(CurrentHedgehog)) 
+	else
+		teamInfo = ""
+	end
+	preMenuCfg =	teamInfo .. "|" ..
+			loc("Press [Up] and [Down] to move between menu items.|Press [Attack], [Left], or [Right] to toggle.") .. "|"
+	if GetGameFlag(gfPlaceHog) then
+		postMenuCfg = loc("Press [Long jump] to accept this configuration and begin placing hedgehogs.")
+	else
+		postMenuCfg = loc("Press [Long jump] to accept this configuration and start the game.")
+	end
+
+	-- This table contains the menu strings and functions to be called when the entry is activated.
+	menu = {}
 
-	preMenuCfg = loc("Spawn the crate, and attack!") .. "|"
-	postMenuCfg = loc("Press [Enter] to accept this configuration.")
+	-- Walls required (hidden if the current settings don't allow for any walls)
+	if #wallSets > 0 then
+		local line
+		if #wTouched > 0 then
+			if wallSets[wallSetID].custom then
+				line = string.format(loc("Wall set: %s (%d walls)"), wallSets[wallSetID].desc, #wTouched) .. "|"
+			else
+				line = string.format(loc("Wall set: %s"), wallSets[wallSetID].desc) .. "|"
+			end
+		else
+			line = loc("Wall set: No walls") .. "|"
+		end
+		table.insert(menu, {
+			line = line,
+			doNext = function()
+				wallSetID = wallSetID + 1
+				if wallSetID > #wallSets then
+					wallSetID = 0
+				end
+				LoadConfig(wallSetID)
+			end,
+			doPrev = function()
+				wallSetID = wallSetID - 1
+				if wallSetID < 0 then
+					wallSetID = #wallSets
+				end
+				LoadConfig(wallSetID)
+			end,
+		})
+	end
+
+	-- Surf Before Crate (hidden if map disabled it)
+	if CanSurf() == true or CanSurf() == nil then
+		local toggleSurf = function() requireSurfer = not(requireSurfer) end
+		table.insert(menu, {
+			line = string.format(loc("Surf Before Crate: %s"), BoolToCfgTxt(requireSurfer)) .. "|",
+			activate = function() requireSurfer = not requireSurfer end,
+		})
+	end
+
+	-- Attack From Rope
+	table.insert(menu, {
+		line = string.format(loc("Attack From Rope: %s"), BoolToCfgTxt(AFR)) .. "|",
+		activate = function() AFR = not AFR end,
+	})
+
+	-- Crate Before Attack
+	table.insert(menu, {
+		line = string.format(loc("Crate Before Attack: %s"), BoolToCfgTxt(CBA)) .. "|",
+		activate = function() CBA = not CBA end,
+	})
 
-	menu = 	{
-			loc("Walls Required") .. ": " .. #wTouched .. "|",
-			loc("Surf Before Crate") .. ": " .. BoolToCfgTxt(requireSurfer) .. "|",
-			loc("Attack From Rope") .. ": " .. BoolToCfgTxt(AFR) .. "|",
-			loc("Super Weapons") .. ": " .. BoolToCfgTxt(allowCrazyWeps) .. "|"
-			}
+	if TeamsCount >= 3 then
+		-- Attack rule (Disabled / All But Last / Kill The Leader)
+		table.insert(menu, {
+			line = string.format(loc("Attack rule: %s"), AttackRuleToCfgTxt(attackRule)) .. "|",
+			doNext = function()
+				if attackRule == nil then
+					attackRule = "ABL"
+				elseif attackRule == "ABL" then
+					attackRule = "KTL"
+				elseif attackRule == "KTL" then
+					attackRule = nil
+				end
+			end,
+			doPrev = function()
+				if attackRule == nil then
+					attackRule = "KTL"
+				elseif attackRule == "ABL" then
+					attackRule = nil 
+				elseif attackRule == "KTL" then
+					attackRule = "ABL"
+				end
+			end,
+		})
+	end
+
+	-- Super weapons
+	table.insert(menu, {
+		line = string.format(loc("Super weapons: %s"), BoolToCfgTxt(allowCrazyWeps)) .. "|",
+		activate = function() allowCrazyWeps = not allowCrazyWeps end,
+	})
+
+	-- Number of crates which appear per turn
+	if maxCrates > 1 then
+		table.insert(menu, {
+			line = string.format(loc("Crates per turn: %d"), cratesPerTurn) .. "|",
+			doNext = function()
+				cratesPerTurn = cratesPerTurn + 1
+				if cratesPerTurn > maxCrates then
+					cratesPerTurn = 1
+				end
+			end,
+			doPrev = function()
+				cratesPerTurn = cratesPerTurn - 1
+				if cratesPerTurn < 1 then
+					cratesPerTurn = maxCrates
+				end
+			end,
+		})
+	end
+end
+
+function FinalizeMenu()
+	local text = ""
+	local showTime = 3000
+	if #wTouched == 0 and not requireSurfer then
+		text = text .. loc("Collect the crate and attack!") .. "|"
+	else
+		text = text .. loc("Spawn the crate and attack!") .. "|"
+	end
+
+	-- Expose a few selected game flags
+	if GetGameFlag(gfPlaceHog)  then
+		text = text .. loc("Place hedgehogs: Place your hedgehogs at the start of the game.") .. "|"
+		showTime = 6000
+	end
+	if GetGameFlag(gfResetWeps) then
+		text = text .. loc("Weapons reset: The weapons are reset after each turn.") .. "|"
+	end
+
+	-- Show the WxW rules
+	if #wTouched == 1 then
+		text = text .. loc("Wall Before Crate: You must touch the marked wall before you can get crates.") .. "|"
+	elseif #wTouched > 0 then
+		text = text .. string.format(loc("Walls Before Crate: You must touch the %d marked walls before you can get crates."), #wTouched) .. "|"
+	end
+
+	if requireSurfer then
+		text = text .. loc("Surf Before Crate: You must bounce off the water once before you can get crates.") .. "|"
+	end
+
+	if AFR then
+		text = text .. loc("Attack From Rope: You may only attack from a rope.") .. "|"
+	end
+
+	if CBA then
+		text = text .. loc("Crate Before Attack: You must collect a crate before you can attack.") .. "|"
+	end
+
+	if attackRule == "ABL" then
+		text = text .. loc("All But Last: You must not solely attack the team with the least health") .. "|"
+	elseif attackRule == "KTL" then
+		text = text .. loc("Kill The Leader: You must also hit the team with the most health.") .. "|"
+	end
+	if attackRule ~= nil then
+		text = text .. loc("Penalty: If you violate above rule, you have to skip in the next turn.") .. "|"
+	end
+
+	if allowCrazyWeps then
+		text = text .. loc("Super weapons: A few crates contain very powerful weapons.") .. "|"
+	end
+
+	ShowMission(loc("Wall to wall"), loc("A Shoppa minigame"), text, 1, showTime)
 end
 
 function HandleStartingStage()
 
-	temp = menu[menuIndex]
-	menu[menuIndex] = "--> " .. menu[menuIndex]
+	temp = menu[menuIndex].line
+	menu[menuIndex].line = "--> " .. menu[menuIndex].line
 
 	missionComment = ""
 	for i = 1, #menu do
-		missionComment = missionComment .. menu[i]
+		missionComment = missionComment .. menu[i].line
 	end
 
 	ShowMission	(
-				loc("WALL TO WALL") .. " 0.4",
-				loc("a shoppa minigame"),
+				loc("Wall to wall"),
+				loc("Configuration phase"),
 				preMenuCfg..
 				missionComment ..
 				postMenuCfg ..
-				--" " .. "|" ..
-				"", 4, 300000
+				"", 2, 300000
 				)
 
-	menu[menuIndex] = temp
+	menu[menuIndex].line = temp
 
 end
 
 function onGameTick()
 
-	if CurrentHedgehog ~= nil then
-
-		--AddCaption(Map)
-		--AddCaption(RightX ..";" .. GetX(CurrentHedgehog))
+	if CurrentHedgehog ~= nil and roundN >= 0 then
 
 		gTimer = gTimer + 1
 		if gTimer == 25 then
 			gTimer = 1
 
-			CheckForWallCollision()
-			CheckCrateConditions()
+			if roundN == 100 then
+				CheckForWallCollision()
+				CheckCrateConditions()
 
-			if (crateG == GetFollowGear()) and (crateG ~= nil) then
-				FollowGear(CurrentHedgehog)
-			end
-
-			-- if attackfromrope is set, forbid firing unless using rope
-			if (AFR == true) and (roundN >= 2) then
-				if (GetCurAmmoType() == amRope) or
+				if (GetGearType(GetFollowGear()) == gtCase) then
+					FollowGear(CurrentHedgehog)
+				end
+				
+				-- AFR and CBA handling
+				local allowAttack = true
+				local shootException
+				shootException = (GetCurAmmoType() == amRope) or
 					(GetCurAmmoType() == amSkip) or
 					(GetCurAmmoType() == amNothing)
-				then
-					SetInputMask(0xFFFFFFFF)
-				elseif ropeG == nil then
-					SetInputMask(bnot(gmAttack))
+				-- If Attack From Rope is set, forbid firing unless using rope
+				if AFR then
+					if ropeG == nil then
+						allowAttack = false
+					end
+				end
+				-- If Crate Before Attack is set, forbid firing if crate is not collected
+				if CBA then
+					if not crateCollected then
+						allowAttack = false
+					end
+				end
+				if allowAttack or shootException then
+					SetInputMask(bor(GetInputMask(), gmAttack))
+					if CBA then
+						SetInputMask(bor(GetInputMask(), gmLJump))
+					end
+				else
+					if CBA then
+						if ropeG == nil then
+							SetInputMask(band(GetInputMask(), bnot(gmAttack)))
+							SetInputMask(bor(GetInputMask(), gmLJump))
+						else
+							SetInputMask(bor(GetInputMask(), gmAttack))
+							SetInputMask(band(GetInputMask(), bnot(gmLJump)))
+						end
+					else
+						SetInputMask(band(GetInputMask(), bnot(gmAttack)))
+					end
 				end
 			end
 
@@ -673,7 +1612,9 @@
 		ropeG = gear
 	elseif GetGearType(gear) == gtCase then
 
-		crateG = gear
+		crates[gear] = true
+		crateGearsInGame = crateGearsInGame + 1
+
 		trackGear(gear)
 
 		table.insert(rCirc, AddVisualGear(0,0,vgtCircle,0,true) )
@@ -683,24 +1624,38 @@
 		SetVisualGearValues(rCirc[#rCirc], 0, 0, 100, 255, 1, 10, 0, 40, 3, 0xff00ffff)
 
 		allowCrate = false
+		crateSpawned = true
 
 		rPingTimer = 0
 		rAlpha = 0
 
+	elseif GetGearType(gear) == gtHedgehog then
+		trackGear(gear)
+		local teamName = GetHogTeamName(gear)
+		-- Initialize radar mode to “on” and set other team values
+		setTeamValue(teamName, "radarMode", 0)
+		setTeamValue(teamName, "skipPenalty", false)
+
+		if getTeamValue(teamName, "hogs") == nil then
+			setTeamValue(teamName, "hogs", 1)
+		else
+			increaseTeamValue(teamName, "hogs")
+		end
+		hogCount = hogCount + 1
+		teamNames[GetHogTeamName(gear)] = true
 	end
 
 end
 
 function onGearDelete(gear)
 
-	if gear == ropeG then
+	local gt = GetGearType(gear)
+	if gt == gtRope then
 		ropeG = nil
-	elseif GetGearType(gear) == gtCase then
+	elseif gt == gtCase then
 
-		if gear == crateG then
-			crateG = nil
-		--	rAlpha = 255
-		end
+		crates[gear] = nil
+		crateGearsInGame = crateGearsInGame - 1
 
 		for i = 1, #rCirc do
 			if rCirc[i] == getGearValue(gear,"CIRC") then
@@ -711,6 +1666,133 @@
 
 		trackDeletion(gear)
 
+		-- Was crate collected?
+		if band(GetGearMessage(gear), gmDestroy) ~= 0 then
+			crateCollected = true
+		end
+
+	elseif gt == gtHedgehog then
+		teamsAttacked[GetHogTeamName(gear)] = true
+		decreaseTeamValue(GetHogTeamName(gear), "hogs")
+		trackDeletion(gear)
+	end
+
+end
+
+function onGearDamage(gear)
+
+	if GetGearType(gear) == gtHedgehog then
+		teamsAttacked[GetHogTeamName(gear)] = true
+	end
+
+end
+
+-- Check which team is the last and which is the leader (used for ABL and KTL)
+function UpdateLastAndLeaderTeams()
+	local teamHealths = {}
+
+	for team, x in pairs(teamNames) do
+		UpdateTeamHealth(team)
+		local totalHealth = getTeamValue(team, "totalHealth")
+		if totalHealth > 0 then
+			table.insert(teamHealths, {name = team, health = totalHealth } )
+		end
+	end
+
+	-- Sort the table by health, lowest health comes first
+	table.sort(teamHealths, function(team1, team2) return team1.health < team2.health end)
+
+	-- ABL and KTL rules are only active at 3 teams; when there are only 2 teams left, it's “everything goes”.
+	if #teamHealths >= 3 then
+		if teamHealths[1].health == teamHealths[2].health then
+			-- ABL rule is disabled if it's a tie for “least health”
+			lastTeam = nil
+		else
+			-- Normal assignment of ABL variable
+			lastTeam = teamHealths[1].name
+		end
+		if teamHealths[#teamHealths].health == teamHealths[#teamHealths-1].health then
+			-- KTL rule is disabled if it's a tie for “most health”
+			leaderTeam = nil
+			runnerUpTeam = nil
+		else
+			-- Normal assignment of KTL variables
+			leaderTeam = teamHealths[#teamHealths].name
+			runnerUpTeam = teamHealths[#teamHealths-1].name
+		end
+	else
+		-- The KTL and ABL rules are disabled with only 2 teams left
+		lastTeam = nil
+		runnerUpTeam = nil
+		leaderTeam = nil
+	end
+end
+
+function UpdateTeamHealth(team)
+	setTeamValue(team, "totalHealth", 0)
+	runOnHogsInTeam(function(hog)
+		if(GetGearType(hog) ~= gtHedgehog) then return end
+		local h = getTeamValue(GetHogTeamName(hog), "totalHealth")
+		setTeamValue(GetHogTeamName(hog), "totalHealth", h + GetHealth(hog))
+	end, team)
+end
+
+-- Check if the ABL or KTL rule (if active) has been violated by teamToCheck
+function CheckAttackRuleViolation(teamToCheck)
+
+	if teamToCheck == nil then return end
+
+	local violated = false
+	if attackRule == "ABL" then
+		-- We don't care if the last team hurts itself
+		if lastTeam ~= nil and lastTeam ~= teamToCheck then
+			local lastAttacked = false
+			local attackNum = 0	-- count the attacked teams but we'll ignore the attacking team
+			for team, wasAttacked in pairs(teamsAttacked) do
+				-- Ignore the attacking team
+				if team ~= teamToCheck then
+					attackNum = attackNum + 1
+					if team == lastTeam then
+						lastAttacked = true
+					end
+				end
+			end
+			-- Rule is violated iff only the last team is attacked (damage to attacking team is ignored)
+			if attackNum == 1 and lastAttacked then
+				violated = true
+			end
+		end
+		if violated then
+			AddCaption(string.format(loc("%s violated the “All But Last” rule and will be penalized."), teamToCheck), msgColorWarn, capgrpGameState)
+		end
+	elseif attackRule == "KTL" then
+		local leaderAttacked = false
+		if leaderTeam ~= nil then
+			local attackNum = 0
+			local selfHarm = false
+			for team, wasAttacked in pairs(teamsAttacked) do
+				attackNum = attackNum + 1
+				if team == teamToCheck then
+					selfHarm = true
+				end
+				-- The leader must attack the runner-up, everyone else must attack the leader
+				if (teamToCheck ~= leaderTeam and team == leaderTeam) or (teamToCheck == leaderTeam and team == runnerUpTeam) then
+					leaderAttacked = true
+					break
+				end
+			end
+			-- If teams were attacked but not the leader, it is a violation,
+			-- but we don't care if the team *only* harmed itself.
+			if (attackNum >= 2 and not leaderAttacked) or (attackNum == 1 and not selfHarm and not leaderAttacked) then
+				violated = true
+			end
+		end
+		if violated then
+			AddCaption(string.format(loc("%s violated the “Kill The Leader” rule and will be penalized."), teamToCheck), msgColorWarn, capgrpGameState)
+		end
+	end
+	if violated then
+		setTeamValue(teamToCheck, "skipPenalty", true)
 	end
 
 end