1 /**
2 	Dark Mine
3 	Dark caves with narrow connections set the stage for this battle. Every player
4 	only has a single clonk and no relaunches, so caution is needed.
5 
6 	@author Maikel
7 */
8 
9 
10 // Game modes.
11 static const GAMEMODE_Deathmatch = 0;
12 static const GAMEMODE_LastManStanding = 1;
13 static const GAMEMODE_KingOfTheHill = 2;
14 
Initialize()15 protected func Initialize()
16 {
17 	// Goals and rules.
18 	if (SCENPAR_GameMode == GAMEMODE_Deathmatch)
19 	{
20 		CreateObject(Goal_DeathMatch);
21 	}
22 	else if (SCENPAR_GameMode == GAMEMODE_LastManStanding)
23 	{
24 		CreateObject(Goal_LastManStanding);
25 		GetRelaunchRule()->SetDefaultRelaunchCount(Max(SCENPAR_NrRelaunchesKills, 0));
26 	}
27 
28 	else if (SCENPAR_GameMode == GAMEMODE_KingOfTheHill)
29 	{
30 		var goal = CreateObject(Goal_KingOfTheHill, LandscapeWidth() / 2, LandscapeHeight() / 2);
31 		goal->SetRadius(72);
32 		goal->SetPointLimit(Max(SCENPAR_NrRelaunchesKills, 1));
33 		GetRelaunchRule()->SetDefaultRelaunchCount(nil);
34 	}
35 	CreateObject(Rule_KillLogs);
36 	CreateObject(Rule_Gravestones);
37 
38 	GetRelaunchRule()
39 		->SetLastWeaponUse(false)
40 		->SetFreeCrew(true)
41 		->SetRespawnDelay(4);
42 
43 	// Rescale cave coordinates with map zoom and shuffle them.
44 	var mapzoom = GetScenarioVal("MapZoom", "Landscape");
45 	for (var cave in cave_list)
46 	{
47 		cave[0] *= mapzoom;
48 		cave[1] *= mapzoom;
49 	}
50 	ShuffleArray(cave_list);
51 	// Then add a nil entry at position one as a separator.
52 	PushFront(cave_list, nil);
53 
54 	// Initialize different parts of the scenario.
55 	// Amount of things depends on the map size.
56 	var plr_cnt = GetStartupPlayerCount();
57 	var map_size = BoundBy(120 + plr_cnt * 10, 140, 240);
58 	InitVegetation(map_size);
59 	InitMaterials(map_size);
60 	InitEnvironment(map_size);
61 	InitLorries();
62 	return;
63 }
64 
65 // Callback from the last man standing goal.
KillsToRelaunch()66 protected func KillsToRelaunch()
67 {
68 	// No relaunches awarded for kills.
69 	return 0;
70 }
71 
72 // Callback from the deathmatch goal.
WinKillCount()73 public func WinKillCount()
74 {
75 	return Max(SCENPAR_NrRelaunchesKills, 1);
76 }
77 
RelaunchPosition(int plr)78 public func RelaunchPosition(int plr)
79 {
80 	return FindStartCave(plr, GetRelaunchRule()->GetPlayerRelaunchCount(plr) != SCENPAR_NrRelaunchesKills);
81 }
82 
OnClonkLeftRelaunch(object clonk,int plr)83 public func OnClonkLeftRelaunch(object clonk, int plr)
84 {
85 	// Players start in a random small cave, the cave depends on whether it is a relaunch.
86 	var cave = [clonk->GetX(), clonk->GetY()];
87 	for (var i = 0; i < 4; ++i)
88 		BlastFree(cave[0], cave[1], 13);
89 
90 	// Players start with a shovel, a pickaxe and two firestones.
91 	clonk->CreateContents(Shovel);
92 	clonk->CreateContents(Pickaxe);
93 	// Better weapons after relaunching.
94 	if (GetRelaunchRule()->GetPlayerRelaunchCount(plr) != SCENPAR_NrRelaunchesKills)
95 	{
96 		clonk->CreateContents(Torch);
97 		clonk->CreateContents(Firestone, 2);
98 	}
99 	else
100 	{
101 		clonk->CreateContents(Lantern);
102 		clonk->CreateContents(Javelin);
103 		clonk->CreateContents(IronBomb);
104 	}
105 
106 	// Set the zoom range to be standard low, but allow for zooming out
107 	// such that light sources a bit further away can be spotted.
108 	SetPlayerZoomByViewRange(plr, 300, nil, PLRZOOM_Direct);
109 	SetPlayerZoomByViewRange(plr, 600, nil, PLRZOOM_LimitMax);
110 	SetPlayerViewLock(plr, true);
111 	return;
112 }
113 
114 // Finds a start cave which is furthest away from the center and from other already used start caves.
FindStartCave(int plr,bool is_relaunch)115 private func FindStartCave(int plr, bool is_relaunch)
116 {
117 	var wdt = LandscapeWidth() / 2;
118 	var hgt = LandscapeHeight() / 2;
119 	// Find the already used caves.
120 	var used_caves = [];
121 	var index_av;
122 	for (index_av = 0; index_av < GetLength(cave_list); index_av++)
123 	{
124 		if (cave_list[index_av] == nil)
125 			break;
126 		PushBack(used_caves, cave_list[index_av]);
127 	}
128 	// Then iterate over all still available caves and find the one furthest away from all other caves.
129 	var best_index;
130 	var max_dist = 0;
131 	for (var index = index_av + 1; index < GetLength(cave_list); index++)
132 	{
133 		var cave = cave_list[index];
134 		// Also furthest away from center cave.
135 		var dist = Distance(cave[0], cave[1], wdt, hgt);
136 		// For a normal cave take distance to used caves, for a relaunch to alive clonks.
137 		if (!is_relaunch)
138 		{
139 			for (var comp_cave in used_caves)
140 				dist = Min(dist, Distance(cave[0], cave[1], comp_cave[0], comp_cave[1]));
141 		}
142 		else
143 		{
144 			for (var clonk in FindObjects(Find_OCF(OCF_CrewMember), Find_Not(Find_Owner(plr))))
145 				dist = Min(dist, Distance(cave[0], cave[1], clonk->GetX(), clonk->GetY()));
146 		}
147 		if (dist > max_dist)
148 		{
149 			best_index = index;
150 			max_dist = dist;
151 		}
152 	}
153 	// If no cave has found, spawn in the large cave.
154 	if (best_index == nil)
155 		return [wdt, hgt];
156 	// Determine found cave and move it in front of the separator if it is not a relaunch.
157 	var found_cave = cave_list[best_index];
158 	if (!is_relaunch)
159 	{
160 		RemoveArrayIndex(cave_list, best_index);
161 		PushFront(cave_list, found_cave);
162 	}
163 	// Return the location of the found cave.
164 	return found_cave;
165 }
166 
167 
168 /*-- Scenario Initiliaztion --*/
169 
InitVegetation(int map_size)170 private func InitVegetation(int map_size)
171 {
172 	// Place some cave mushrooms for cover.
173 	LargeCaveMushroom->Place(map_size - 20, nil, { terraform = false });
174 
175 	// Some mushrooms to regain health.
176 	Mushroom->Place(map_size / 2);
177 	Fern->Place(map_size / 3);
178 
179 	// Place some branches and trunks around the map.
180 	Branch->Place(map_size / 2);
181 	Trunk->Place(map_size / 4, nil, { size = [60, 80] });
182 	return;
183 }
184 
InitMaterials(int map_size)185 private func InitMaterials(int map_size)
186 {
187 	// Some objects in the earth or rock material.
188 	PlaceObjects(Loam, map_size / 3, "Earth");
189 	PlaceObjects(Firestone, map_size / 3, "Earth");
190 	PlaceObjects(Dynamite, map_size / 5, "Earth");
191 	PlaceObjects(DynamiteBox, map_size / 6, "Rock");
192 	PlaceObjects(PowderKeg, map_size / 10, "Rock");
193 
194 	// Some pickaxes, shovels in the tunnels.
195 	for (var i = 0; i < map_size / 5; i++)
196 	{
197 		var loc = FindLocation(Loc_Tunnel(), Loc_Wall(CNAT_Bottom));
198 		if (!loc)
199 			continue;
200 		CreateObjectAbove([Shovel, Pickaxe, GoldBar][Random(3)], loc.x, loc.y)->SetR(Random(360));
201 	}
202 	return;
203 }
204 
InitEnvironment(int map_size)205 private func InitEnvironment(int map_size)
206 {
207 	var wdt = LandscapeWidth();
208 	var hgt = LandscapeHeight();
209 	// Some lights in the main cave as a strategic element.
210 	for (var side = -1; side <= 1; side += 2)
211 	{
212 		// Both sides of the cave are lighted for all players.
213 		var torch = CreateObjectAbove(Torch, wdt / 2 + side * 50, hgt / 2 + 32);
214 		torch->AttachToWall(true);
215 	}
216 	return;
217 }
218 
InitLorries()219 private func InitLorries()
220 {
221 	var wdt = LandscapeWidth();
222 	var hgt = LandscapeHeight();
223 	// Create lorries at random small caves, but only for 2/3 of them.
224 	for (var index = GetLength(cave_list) - 1; index >= 2 * GetLength(cave_list) / 3; index--)
225 	{
226 		var cave = cave_list[index];
227 		var lorry = CreateObjectAbove(Lorry, cave[0], cave[1]);
228 		// Basic objects which are in every lorry.
229 		lorry->CreateContents(Firestone, RandomX(4, 7));
230 		lorry->CreateContents(Dynamite, RandomX(2, 4));
231 		lorry->CreateContents(Loam, RandomX(2, 4));
232 		// Objects which are only in half of the lorries.
233 		if (!Random(2))
234 			lorry->CreateContents(DynamiteBox, RandomX(1, 2));
235 		if (!Random(2))
236 			lorry->CreateContents(Shield);
237 		if (!Random(2))
238 			lorry->CreateContents(Torch, 2);
239 		// Objects which are only in one third of the lorries.
240 		if (!Random(3))
241 			lorry->CreateContents(GrappleBow, RandomX(1, 2));
242 		if (!Random(3))
243 			lorry->CreateContents(Bread, RandomX(1, 2));
244 		if (!Random(3))
245 		{
246 			lorry->CreateContents(Bow);
247 			lorry->CreateContents([Arrow, FireArrow, FireArrow][Random(3)], 2);
248 		}
249 		// Objects which are only in one fifth of the lorries.
250 		if (!Random(5))
251 			lorry->CreateContents(Javelin, RandomX(1, 2));
252 		if (!Random(5))
253 			lorry->CreateContents(Club, RandomX(1, 2));
254 		if (!Random(5))
255 		{
256 			var barrel = lorry->CreateContents(Barrel);
257 			barrel->PutLiquid("Water", Barrel->GetLiquidContainerMaxFillLevel());
258 		}
259 		// Objects which are only in one eighth of the lorries.
260 		if (!Random(8))
261 			lorry->CreateContents(IronBomb, RandomX(1, 2));
262 		if (!Random(8))
263 			lorry->CreateContents(Lantern, RandomX(1, 2));
264 		if (!Random(8))
265 			lorry->CreateContents(SmokeBomb, RandomX(1, 2));
266 		if (!Random(8))
267 			lorry->CreateContents(WallKit, 1);
268 		if (!Random(8))
269 		{
270 			lorry->CreateContents(Blunderbuss);
271 			lorry->CreateContents(LeadBullet);
272 		}
273 	}
274 	// Create two lorries at the main cave.
275 	for (var side = -1; side <= 1; side += 2)
276 	{
277 		// Create lorry with useful tools and weapons.
278 		var lorry = CreateObjectAbove(Lorry, wdt / 2 + side * 50, hgt / 2 + 44);
279 		lorry->CreateContents(Bow, 2);
280 		lorry->CreateContents(BombArrow, 4);
281 		lorry->CreateContents(Boompack, 2);
282 		lorry->CreateContents(PowderKeg, 2);
283 		lorry->CreateContents(TeleGlove, 1);
284 		lorry->CreateContents(WindBag, 1);
285 		lorry->CreateContents(GrenadeLauncher);
286 		lorry->CreateContents(IronBomb, 4);
287 		lorry->CreateContents(Lantern, 2);
288 		lorry->CreateContents(SmokeBomb, 2);
289 	}
290 	return;
291 }
292