1 /**
2  * @file
3  * @brief Mission related code - king of the hill and so on.
4  */
5 
6 /*
7 All original material Copyright (C) 2002-2013 UFO: Alien Invasion.
8 
9 Original file from Quake 2 v3.21: quake2-2.31/game/g_spawn.c
10 Copyright (C) 1997-2001 Id Software, Inc.
11 
12 This program is free software; you can redistribute it and/or
13 modify it under the terms of the GNU General Public License
14 as published by the Free Software Foundation; either version 2
15 of the License, or (at your option) any later version.
16 
17 This program is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
20 
21 See the GNU General Public License for more details.
22 
23 You should have received a copy of the GNU General Public License
24 along with this program; if not, write to the Free Software
25 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
26 
27 */
28 
29 #include "g_mission.h"
30 #include "g_actor.h"
31 #include "g_client.h"
32 #include "g_edicts.h"
33 #include "g_inventory.h"
34 #include "g_match.h"
35 #include "g_spawn.h"
36 #include "g_utils.h"
37 
G_MissionAddVictoryMessage(const char * message)38 void G_MissionAddVictoryMessage (const char* message)
39 {
40 	gi.ConfigString(CS_VICTORY_CONDITIONS, "%s", message);
41 }
42 
43 /**
44  * @brief Mission trigger
45  * @todo use level.nextmap to spawn another map when every living actor has touched the mission trigger
46  * @todo use level.actualRound to determine the 'King of the Hill' time
47  * @note Don't set a client action here - otherwise the movement event might
48  * be corrupted
49  */
G_MissionTouch(Edict * self,Edict * activator)50 bool G_MissionTouch (Edict* self, Edict* activator)
51 {
52 	if (!self->owner)
53 		return false;
54 
55 	switch (self->owner->team) {
56 	case TEAM_ALIEN:
57 		if (G_IsAlien(activator)) {
58 			if (!self->count) {
59 				self->count = level.actualRound;
60 				gi.BroadcastPrintf(PRINT_HUD, _("Aliens entered target zone!"));
61 			}
62 			return true;
63 		} else {
64 			/* reset king of the hill counter */
65 			self->count = 0;
66 		}
67 	/* general case that also works for multiplayer teams */
68 	default:
69 		if (activator->team != self->owner->team) {
70 			/* reset king of the hill counter */
71 			self->count = 0;
72 			return false;
73 		}
74 		if (self->owner->count)
75 			return false;
76 
77 		self->owner->count = level.actualRound;
78 		if (!self->owner->item) {
79 			gi.BroadcastPrintf(PRINT_HUD, _("Target zone is occupied!"));
80 			return true;
81 		}
82 
83 		/* search the item in the activator's inventory */
84 		/* ignore items linked from any temp container the actor must have this in his hands */
85 		const Container* cont = nullptr;
86 		while ((cont = activator->chr.inv.getNextCont(cont))) {
87 			Item* item = nullptr;
88 			while ((item = cont->getNextItem(item))) {
89 				const objDef_t* od = item->def();
90 				/* check whether we found the searched item in the actor's inventory */
91 				if (!Q_streq(od->id, self->owner->item))
92 					continue;
93 
94 				/* drop the weapon - even if out of TUs */
95 				G_ActorInvMove(activator, cont->def(), item, INVDEF(CID_FLOOR), NONE, NONE, false);
96 				gi.BroadcastPrintf(PRINT_HUD, _("Item was placed."));
97 				self->owner->count = level.actualRound;
98 				return true;
99 			}
100 		}
101 		break;
102 	}
103 	return true;
104 }
105 
106 /**
107  * @brief Mission trigger destroy function
108  */
G_MissionDestroy(Edict * self)109 bool G_MissionDestroy (Edict* self)
110 {
111 	return true;
112 }
113 
114 /**
115  * @brief Mission trigger use function
116  */
G_MissionUse(Edict * self,Edict * activator)117 bool G_MissionUse (Edict* self, Edict* activator)
118 {
119 	Edict* target = G_EdictsFindTargetEntity(self->target);
120 	if (!target) {
121 		gi.DPrintf("Target '%s' wasn't found for misc_mission\n", self->target);
122 		G_FreeEdict(self);
123 		return false;
124 	}
125 
126 	if (target->destroy) {
127 		/* set this to zero to determine that this is a triggered destroy call */
128 		target->HP = 0;
129 		target->destroy(target);
130 		/* freed when the level changes */
131 		self->target = nullptr;
132 		self->use = nullptr;
133 	} else if (target->use)
134 		target->use(target, activator);
135 
136 	return true;
137 }
138 
139 /**
140  * @note Think functions are only executed when the match is running
141  * or in other word, the game has started
142  */
G_MissionThink(Edict * self)143 void G_MissionThink (Edict* self)
144 {
145 	Edict* chain = self->groupMaster;
146 	Edict* ent;
147 	int team;
148 
149 	if (!G_MatchIsRunning())
150 		return;
151 
152 	/* when every player has joined the match - spawn the mission target
153 	 * particle (if given) to mark the trigger */
154 	if (self->particle) {
155 		self->link = G_SpawnParticle(self->origin, self->spawnflags, self->particle);
156 
157 		/* This is automatically freed on map shutdown */
158 		self->particle = nullptr;
159 	}
160 
161 	if (!chain)
162 		chain = self;
163 	while (chain) {
164 		if (chain->type == ET_MISSION) {
165 			if (chain->item) {
166 				const Item* ic;
167 				G_GetFloorItems(chain);
168 				ic = chain->getFloor();
169 				if (!ic) {
170 					/* reset the counter if there is no item */
171 					chain->count = 0;
172 					return;
173 				}
174 				for (; ic; ic = ic->getNext()) {
175 					const objDef_t* od = ic->def();
176 					assert(od);
177 					/* not the item we are looking for */
178 					if (Q_streq(od->id, chain->item))
179 						break;
180 				}
181 				if (!ic) {
182 					/* reset the counter if it's not the searched item */
183 					chain->count = 0;
184 					return;
185 				}
186 			}
187 			if (chain->time) {
188 				const int endTime = level.actualRound - chain->count;
189 				const int spawnIndex = (self->team + level.teamOfs) % MAX_TEAMS;
190 				const int currentIndex = (level.activeTeam + level.teamOfs) % MAX_TEAMS;
191 				/* not every edict in the group chain has
192 				 * been occupied long enough */
193 				if (!chain->count || endTime < chain->time ||
194 						(endTime == level.actualRound && spawnIndex <= currentIndex))
195 					return;
196 			}
197 			/* not destroyed yet */
198 			if ((chain->flags & FL_DESTROYABLE) && chain->HP)
199 				return;
200 		}
201 		chain = chain->groupChain;
202 	}
203 
204 	if (self->use)
205 		self->use(self, nullptr);
206 
207 	/* store team before the edict is released */
208 	team = self->team;
209 
210 	chain = self->groupMaster;
211 	if (!chain)
212 		chain = self;
213 	while (chain) {
214 		if (chain->item != nullptr) {
215 			Edict* item = G_GetEdictFromPos(chain->pos, ET_ITEM);
216 			if (item != nullptr) {
217 				if (!G_InventoryRemoveItemByID(chain->item, item, CID_FLOOR)) {
218 					Com_Printf("Could not remove item '%s' from floor edict %i\n",
219 							chain->item, item->number);
220 				} else {
221 					G_AppearPerishEvent(G_VisToPM(item->visflags), false, *item, nullptr);
222 				}
223 			}
224 		}
225 		if (chain->link != nullptr) {
226 			Edict* particle = G_GetEdictFromPos(chain->pos, ET_PARTICLE);
227 			if (particle != nullptr) {
228 				G_AppearPerishEvent(PM_ALL, false, *particle, nullptr);
229 				G_FreeEdict(particle);
230 			}
231 			chain->link = nullptr;
232 		}
233 
234 		ent = chain->groupChain;
235 		/* free the trigger */
236 		if (chain->child)
237 			G_FreeEdict(chain->child);
238 		/* free the group chain */
239 		G_FreeEdict(chain);
240 		chain = ent;
241 	}
242 	self = nullptr;
243 
244 	/* still active mission edicts left */
245 	ent = nullptr;
246 	while ((ent = G_EdictsGetNextInUse(ent)))
247 		if (ent->type == ET_MISSION && ent->team == team)
248 			return;
249 
250 	G_MatchEndTrigger(team, 10);
251 }
252