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