Beispiel #1
0
/**
 * @sa SV_RunGameFrame
 * @sa G_MatchEndTrigger
 * @sa AI_Run
 * @return true if game reaches its end - false otherwise
 */
static bool G_RunFrame (void)
{
	level.framenum++;
	/* server is running at 10 fps */
	level.time = level.framenum * SERVER_FRAME_SECONDS;

	/* this doesn't belong here, but it works */
	if (!level.routed) {
		level.routed = true;
		G_CompleteRecalcRouting();
	}

	/* still waiting for other players */
	if (!G_MatchIsRunning()) {
		if (sv_maxteams->modified) {
			/* inform the client */
			gi.ConfigString(CS_MAXTEAMS, "%i", sv_maxteams->integer);
			sv_maxteams->modified = false;
		}
	}

	if (G_IsMultiPlayer()) {
		if (sv_roundtimelimit->modified) {
			/* some played around here - restart the count down */
			level.roundstartTime = level.time;
			/* don't allow smaller values here */
			if (sv_roundtimelimit->integer < 30 && sv_roundtimelimit->integer > 0) {
				gi.DPrintf("The minimum value for sv_roundtimelimit is 30\n");
				gi.Cvar_Set("sv_roundtimelimit", "30");
			}
			sv_roundtimelimit->modified = false;
		}
		G_CheckForceEndRound();
	}

	/* end this game? */
	if (G_MatchDoEnd())
		return true;

	CheckNeedPass();

	/* run ai */
	AI_Run();

	/* not all teams are spawned or game has already ended */
	if (G_MatchIsRunning())
		G_EdictsThink();

	G_SendBoundingBoxes();

	return false;
}
Beispiel #2
0
/**
 * @brief Checks whether the actor is allowed to activate reaction fire and will informs the player about
 * the reason if this would not work.
 * @param[in] ent The actor to check
 * @return @c true if the actor is allowed to activate it, @c false otherwise
 */
static bool G_ReactionFireCanBeEnabled (const edict_t *ent)
{
	/* check ent is a suitable shooter */
	if (!ent->inuse || !G_IsLivingActor(ent))
		return false;

	if (G_MatchIsRunning() && ent->team != level.activeTeam)
		return false;

	/* actor may not carry weapons at all - so no further checking is needed */
	if (!ent->chr.teamDef->weapons)
		return false;

	if (!G_ActorHasReactionFireEnabledWeapon(ent)) {
		G_ClientPrintf(G_PLAYER_FROM_ENT(ent), PRINT_HUD, _("No reaction fire enabled weapon."));
		return false;
	}

	if (!G_ActorHasWorkingFireModeSet(ent)) {
		G_ClientPrintf(G_PLAYER_FROM_ENT(ent), PRINT_HUD, _("No fire mode selected for reaction fire."));
		return false;
	}

	if (!G_ActorHasEnoughTUsReactionFire(ent)) {
		G_ClientPrintf(G_PLAYER_FROM_ENT(ent), PRINT_HUD, _("Not enough TUs left for activating reaction fire."));
		return false;
	}

	return true;
}
Beispiel #3
0
/**
 * @brief called whenever the player updates a userinfo variable.
 * @note The game can override any of the settings in place (forcing skins or names, etc) before copying it off.
 */
void G_ClientUserinfoChanged (player_t * player, const char *userinfo)
{
	const bool alreadyReady = player->isReady;
	const int oldTeamnum = Info_IntegerForKey(player->pers.userinfo, "cl_teamnum");

	/* check for malformed or illegal info strings */
	if (!Info_Validate(userinfo))
		userinfo = "\\cl_name\\badinfo";

	/* set name */
	Q_strncpyz(player->pers.netname, Info_ValueForKey(userinfo, "cl_name"), sizeof(player->pers.netname));
	Q_strncpyz(player->pers.userinfo, userinfo, sizeof(player->pers.userinfo));
	player->autostand = Info_IntegerForKey(userinfo, "cl_autostand");
	player->reactionLeftover = Info_IntegerForKey(userinfo, "cl_reactionleftover");
	player->isReady = Info_IntegerForKey(userinfo, "cl_ready");

	/* send the updated config string */
	gi.ConfigString(CS_PLAYERNAMES + player->num, "%s", player->pers.netname);

	/* try to update to the preferred team */
	if (!G_MatchIsRunning() && oldTeamnum != Info_IntegerForKey(userinfo, "cl_teamnum")) {
		/* if the player is marked as ready he can't change his team */
		if (!alreadyReady || !player->isReady) {
			player->pers.team = TEAM_NO_ACTIVE;
			G_GetTeam(player);
		} else {
			Com_DPrintf(DEBUG_GAME, "G_ClientUserinfoChanged: player %s is already marked as being ready\n",
					player->pers.netname);
		}
	}
}
Beispiel #4
0
/**
 * @brief Check whether a forced turn end should be executed
 */
void G_CheckForceEndRound (void)
{
	/* check for roundlimits in multiplayer only */
	if (!sv_roundtimelimit->integer || G_IsSinglePlayer())
		return;

	if (!G_MatchIsRunning())
		return;

	if (level.time != ceil(level.time))
		return;

	const int diff = level.roundstartTime + sv_roundtimelimit->integer - level.time;
	switch (diff) {
	case 240:
		gi.BroadcastPrintf(PRINT_HUD, _("4 minutes left until forced turn end."));
		return;
	case 180:
		gi.BroadcastPrintf(PRINT_HUD, _("3 minutes left until forced turn end."));
		return;
	case 120:
		gi.BroadcastPrintf(PRINT_HUD, _("2 minutes left until forced turn end."));
		return;
	case 60:
		gi.BroadcastPrintf(PRINT_HUD, _("1 minute left until forced turn end."));
		return;
	case 30:
		gi.BroadcastPrintf(PRINT_HUD, _("30 seconds left until forced turn end."));
		return;
	case 15:
		gi.BroadcastPrintf(PRINT_HUD, _("15 seconds left until forced turn end."));
		return;
	}

	/* active team still has time left */
	if (level.time < level.roundstartTime + sv_roundtimelimit->integer)
		return;

	gi.BroadcastPrintf(PRINT_HUD, _("Current active team hit the max round time."));

	/* store this in a local variable, as the global variable is changed in G_ClientEndRound */
	const int activeTeam = level.activeTeam;
	/* set all team members to ready (only human players) */
	Player* p = nullptr;
	while ((p = G_PlayerGetNextActiveHuman(p))) {
		if (p->getTeam() == activeTeam) {
			G_ClientEndRound(*p);
			level.nextEndRound = level.framenum;
		}
	}

	level.roundstartTime = level.time;
}
Beispiel #5
0
/**
 * @brief Chose a team that should start the match
 * @param[in] player In singleplayer mode the team of this player will get the first turn
 * @sa SVCmd_StartGame_f
 */
static void G_GetStartingTeam (const player_t* player)
{
	int teamCount;
	int playerCount;
	int knownTeams[MAX_TEAMS];
	player_t *p;

	/* return with no action if activeTeam already assigned or if are in single-player mode */
	if (G_MatchIsRunning())
		return;

	if (sv_maxclients->integer == 1) {
		level.activeTeam = player->pers.team;
		level.teamOfs = MAX_TEAMS - level.activeTeam;
		return;
	}

	/* count number of currently connected unique teams and players (human controlled players only) */
	p = NULL;
	teamCount = 0;
	playerCount = 0;
	while ((p = G_PlayerGetNextActiveHuman(p))) {
		int j;
		playerCount++;
		for (j = 0; j < teamCount; j++) {
			if (p->pers.team == knownTeams[j])
				break;
		}
		if (j == teamCount)
			knownTeams[teamCount++] = p->pers.team;
	}

	if (teamCount) {
		const int teamIndex = (int) (frand() * (teamCount - 1) + 0.5);
		G_PrintStats("Starting new game: %s with %i teams", level.mapname, teamCount);
		level.activeTeam = knownTeams[teamIndex];
		level.teamOfs = MAX_TEAMS - level.activeTeam;
		p = NULL;
		while ((p = G_PlayerGetNextActiveHuman(p)))
			if (p->pers.team != level.activeTeam)
				p->roundDone = true;
	}
}
Beispiel #6
0
/**
 * @brief Handles door and other objects
 * @sa G_RunFrame
 */
void G_PhysicsRun (void)
{
	edict_t *ent = NULL;

	/* not all teams are spawned or game has already ended */
	if (!G_MatchIsRunning())
		return;

#if 0 /* taken out - otherwise footstep sounds are too slow */
	/* don't run this too often to prevent overflows */
	if (level.framenum % 10)
		return;
#endif

	/* treat each object in turn */
	/* even the world gets a chance to think */
	while ((ent = G_EdictsGetNextInUse(ent))) {
		if (ent->think)
			G_PhysicsThink(ent);
	}
}
Beispiel #7
0
/**
 * @note Think functions are only executed when the match is running
 * or in other word, the game has started
 */
void G_MissionThink (Edict* self)
{
	if (!G_MatchIsRunning())
		return;

	/* when every player has joined the match - spawn the mission target
	 * particle (if given) to mark the trigger */
	if (self->particle) {
		self->link = G_SpawnParticle(self->origin, self->spawnflags, self->particle);

		/* This is automatically freed on map shutdown */
		self->particle = nullptr;
	}

	Edict* chain = self->groupMaster;
	if (!chain)
		chain = self;
	while (chain) {
		if (chain->type == ET_MISSION) {
			if (chain->item) {
				const Item* ic;
				G_GetFloorItems(chain);
				ic = chain->getFloor();
				if (!ic) {
					/* reset the counter if there is no item */
					chain->count = 0;
					return;
				}
				for (; ic; ic = ic->getNext()) {
					const objDef_t* od = ic->def();
					assert(od);
					/* not the item we are looking for */
					if (Q_streq(od->id, chain->item))
						break;
				}
				if (!ic) {
					/* reset the counter if it's not the searched item */
					chain->count = 0;
					return;
				}
			}
			if (chain->time) {
				/* Check that the target zone is still occupied (last defender might have died) */
				if (!chain->item && !G_MissionIsTouched(chain)) {
						chain->count = 0;
				}
				const int endTime = level.actualRound - chain->count;
				const int spawnIndex = (chain->getTeam() + level.teamOfs) % MAX_TEAMS;
				const int currentIndex = (level.activeTeam + level.teamOfs) % MAX_TEAMS;
				/* not every edict in the group chain has
				 * been occupied long enough */
				if (!chain->count || endTime < chain->time ||
						(endTime == chain->time && spawnIndex < currentIndex))
					return;
			}
			if (chain->target && !chain->time && !chain->item) {
				if (!G_MissionIsTouched(chain))
					return;
			}
		}
		chain = chain->groupChain;
	}

	const bool endMission = self->target == nullptr;

	/* store team before the edict is released */
	const int team = self->getTeam();
	chain = self->groupMaster;
	if (!chain)
		chain = self;
	while (chain) {
		if (chain->type == ET_MISSION) {
			G_UseEdict(chain, nullptr);
			if (chain->item != nullptr) {
				Edict* item = G_GetEdictFromPos(chain->pos, ET_ITEM);
				if (item != nullptr) {
					if (!G_InventoryRemoveItemByID(chain->item, item, CID_FLOOR)) {
						Com_Printf("Could not remove item '%s' from floor edict %i\n", chain->item, item->getIdNum());
					} else if (!item->getFloor()) {
						G_EventPerish(*item);
						G_FreeEdict(item);
					}
				}
			}
			if (chain->link != nullptr) {
				Edict* particle = G_GetEdictFromPos(chain->pos, ET_PARTICLE);
				if (particle != nullptr) {
					G_AppearPerishEvent(G_VisToPM(particle->visflags), false, *particle, nullptr);
					G_FreeEdict(particle);
				}
				chain->link = nullptr;
			}

			/* Display mission message */
			if (G_ValidMessage(chain)) {
				const char* msg = chain->message;
				if (msg[0] == '_')
					++msg;
				gi.BroadcastPrintf(PRINT_HUD, "%s", msg);
			}
		}

		Edict* ent = chain->groupChain;
		/* free the group chain */
		G_FreeEdict(chain);
		chain = ent;
	}

	if (endMission)
		G_MatchEndTrigger(team, level.activeTeam == TEAM_ALIEN ? 10 : 3);
}
Beispiel #8
0
/**
 * @brief Opens/closes a door
 * @note Use function for func_door
 * @todo Check if the door can be opened or closed - there should not be
 * anything in the way (e.g. an actor)
 */
static bool Door_Use (edict_t *door, edict_t *activator)
{
    if (door->doorState == STATE_CLOSED) {
        door->doorState = STATE_OPENED;

        /* change rotation/origin and relink */
        if (door->type == ET_DOOR) {
            if (door->dir & DOOR_OPEN_REVERSE)
                door->angles[door->dir & 3] -= DOOR_ROTATION_ANGLE;
            else
                door->angles[door->dir & 3] += DOOR_ROTATION_ANGLE;
        } else if (door->type == ET_DOOR_SLIDING) {
            Door_SlidingUse(door);
        }
        gi.LinkEdict(door);

        /* maybe the server called this because the door starts opened */
        if (G_MatchIsRunning()) {
            /* let everybody know, that the door opens */
            G_EventDoorOpen(door);
            if (door->noise[0] != '\0')
                G_EventSpawnSound(PM_ALL, false, door, door->origin, door->noise);
        }
    } else if (door->doorState == STATE_OPENED) {
        door->doorState = STATE_CLOSED;

        /* change rotation and relink */
        if (door->type == ET_DOOR) {
            if (door->dir & DOOR_OPEN_REVERSE)
                door->angles[door->dir & 3] += DOOR_ROTATION_ANGLE;
            else
                door->angles[door->dir & 3] -= DOOR_ROTATION_ANGLE;
        } else if (door->type == ET_DOOR_SLIDING) {
            Door_SlidingUse(door);
        }
        gi.LinkEdict(door);

        /* closed is the standard, opened is handled above - we need an active
         * team here already */
        if (G_MatchIsRunning()) {
            /* let everybody know, that the door closes */
            G_EventDoorClose(door);
            if (door->noise[0] != '\0')
                G_EventSpawnSound(PM_ALL, false, door, door->origin, door->noise);
        }
    } else
        return false;

    /* Update model orientation */
    gi.SetInlineModelOrientation(door->model, door->origin, door->angles);
    Com_DPrintf(DEBUG_GAME, "Server processed door movement.\n");

    /* Update path finding table */
    G_RecalcRouting(door->model);

    if (activator && G_IsLivingActor(activator)) {
        /* Check if the player appears/perishes, seen from other teams. */
        G_CheckVis(activator, true);

        /* Calc new vis for the activator. */
        G_CheckVisTeamAll(activator->team, false, activator);
    }

    return true;
}
Beispiel #9
0
/**
 * @sa G_PlayerSoldiersCount
 */
void G_ClientEndRound (Player& player)
{
	Player* p;

	const int lastTeamIndex = (G_GetActiveTeam() + level.teamOfs) % MAX_TEAMS;

	if (!G_IsAIPlayer(&player)) {
		/* inactive players can't end their inactive turn :) */
		if (level.activeTeam != player.getTeam())
			return;

		/* check for "team oszillation" */
		if (level.framenum < level.nextEndRound)
			return;

		level.nextEndRound = level.framenum + 20;
	}

	/* only use this for teamplay matches like coopX or fight2on2 and above
	 * also skip this for ai players, this is only called when all ai actors
	 * have finished their 'thinking' */
	if (!G_IsAIPlayer(&player) && sv_teamplay->integer) {
		/* check if all team members are ready */
		if (!player.roundDone) {
			player.roundDone = true;
			G_EventEndRoundAnnounce(player);
			G_EventEnd();
		}
		p = nullptr;
		while ((p = G_PlayerGetNextActiveHuman(p)))
			if (p->getTeam() == level.activeTeam && !p->roundDone && G_PlayerSoldiersCount(*p) > 0)
				return;
		p = nullptr;
		while ((p = G_PlayerGetNextActiveAI(p)))
			if (p->getTeam() == level.activeTeam && !p->roundDone && G_PlayerSoldiersCount(*p) > 0)
				return;
	} else {
		player.roundDone = true;
	}

	/* clear any remaining reaction fire */
	G_ReactionFireOnEndTurn();

	if (!G_IsAIPlayer(&player)) {
		if (g_lastseen->integer > 0) {
			Edict* ent = nullptr;
			while ((ent = G_EdictsGetNextActor(ent))) {
				if (G_IsAI(ent) && G_IsVisibleForTeam(ent, level.activeTeam)) {
					player.lastSeen = level.actualRound;
					break;
				}
			}
			if (level.actualRound - player.lastSeen > g_lastseen->integer) {
				Com_Printf("round end triggered by g_lastseen (player %i (team %i) last seen in round %i of %i rounds)\n",
						player.getNum(), level.activeTeam, player.lastSeen, level.actualRound);
				G_MatchEndTrigger(-1, 0);
			}
		}
	}

	/* let all the invisible players perish now */
	G_CheckVisTeamAll(level.activeTeam, VIS_APPEAR, nullptr);

	G_GetNextActiveTeam();

	AI_CheckRespawn(TEAM_ALIEN);

	/* no other team left? */
	if (!G_MatchIsRunning())
		return;

	if (lastTeamIndex > (level.activeTeam + level.teamOfs) % MAX_TEAMS)
		level.actualRound++;

	/* communicate next player in row to clients */
	G_EventEndRound();

	/* store the round start time to be able to abort the round after a give time */
	level.roundstartTime = level.time;

	/* Wounded team members bleed */
	G_BleedWounds(level.activeTeam);

	/* Update the state of stuned team-members. The actual statistics are sent below! */
	G_UpdateStunState(level.activeTeam);

	/* Give the actors of the now active team their TUs. */
	G_GiveTimeUnits(level.activeTeam);

	/* apply morale behaviour, reset reaction fire */
	G_ReactionFireReset(level.activeTeam);
	if (mor_panic->integer)
		G_MoraleBehaviour(level.activeTeam);

	G_UpdateCarriedWeight(level.activeTeam);

	/* start ai - there is only one player for ai teams, and the last pointer must only
	 * be updated for ai players */
	p = G_GetPlayerForTeam(level.activeTeam);
	if (p == nullptr)
		gi.Error("Could not find player for team %i", level.activeTeam);

	/* finish off events */
	G_EventEnd();

	/* reset ready flag for every player on the current team (even ai players) */
	p = nullptr;
	while ((p = G_PlayerGetNextActiveHuman(p))) {
		if (p->getTeam() == level.activeTeam) {
			p->roundDone = false;
		}
	}

	p = nullptr;
	while ((p = G_PlayerGetNextActiveAI(p))) {
		if (p->getTeam() == level.activeTeam) {
			p->roundDone = false;
		}
	}
}
Beispiel #10
0
/**
 * @note Think functions are only executed when the match is running
 * or in other word, the game has started
 */
void G_MissionThink (Edict* self)
{
	if (!G_MatchIsRunning())
		return;

	/* when every player has joined the match - spawn the mission target
	 * particle (if given) to mark the trigger */
	if (self->particle) {
		self->link = G_SpawnParticle(self->origin, self->spawnflags, self->particle);

		/* This is automatically freed on map shutdown */
		self->particle = nullptr;
	}

	Edict* chain = self->groupMaster;
	if (!chain)
		chain = self;
	while (chain) {
		if (chain->type == ET_MISSION) {
			if (chain->item) {
				const Item* ic;
				G_GetFloorItems(chain);
				ic = chain->getFloor();
				if (!ic) {
					/* reset the counter if there is no item */
					chain->count = 0;
					return;
				}
				for (; ic; ic = ic->getNext()) {
					const objDef_t* od = ic->def();
					assert(od);
					/* not the item we are looking for */
					if (Q_streq(od->id, chain->item))
						break;
				}
				if (!ic) {
					/* reset the counter if it's not the searched item */
					chain->count = 0;
					return;
				}
			}
			if (chain->time) {
				const int endTime = level.actualRound - chain->count;
				const int spawnIndex = (self->getTeam() + level.teamOfs) % MAX_TEAMS;
				const int currentIndex = (level.activeTeam + level.teamOfs) % MAX_TEAMS;
				/* not every edict in the group chain has
				 * been occupied long enough */
				if (!chain->count || endTime < chain->time ||
						(endTime == level.actualRound && spawnIndex <= currentIndex))
					return;
			}
			/* not destroyed yet */
			if ((chain->flags & FL_DESTROYABLE) && chain->HP)
				return;
		}
		chain = chain->groupChain;
	}

	if (self->use)
		self->use(self, nullptr);

	/* store team before the edict is released */
	const int team = self->getTeam();
	chain = self->groupMaster;
	if (!chain)
		chain = self;
	while (chain) {
		if (chain->item != nullptr) {
			Edict* item = G_GetEdictFromPos(chain->pos, ET_ITEM);
			if (item != nullptr) {
				if (!G_InventoryRemoveItemByID(chain->item, item, CID_FLOOR)) {
					Com_Printf("Could not remove item '%s' from floor edict %i\n", chain->item, item->getIdNum());
				} else if (!item->getFloor()) {
					G_EventPerish(*item);
					G_FreeEdict(item);
				}
			}
		}
		if (chain->link != nullptr) {
			Edict* particle = G_GetEdictFromPos(chain->pos, ET_PARTICLE);
			if (particle != nullptr) {
				G_AppearPerishEvent(PM_ALL, false, *particle, nullptr);
				G_FreeEdict(particle);
			}
			chain->link = nullptr;
		}

		Edict* ent = chain->groupChain;
		/* free the trigger */
		if (chain->child())
			G_FreeEdict(chain->child());
		/* free the group chain */
		G_FreeEdict(chain);
		chain = ent;
	}
	self = nullptr;

	/* still active mission edicts left */
	Edict* ent = nullptr;
	while ((ent = G_EdictsGetNextInUse(ent)))
		if (ent->type == ET_MISSION && ent->getTeam() == team)
			return;

	G_MatchEndTrigger(team, 10);
}
Beispiel #11
0
/**
 * @brief Opens/closes a door
 * @note Use function for func_door
 * @todo Check if the door can be opened or closed - there should not be
 * anything in the way (e.g. an actor)
 */
static bool Door_Use (edict_t *door, edict_t *activator)
{
	int opening = 1;

	if (door->doorState == STATE_CLOSED) {
		door->doorState = STATE_OPENED;
		opening = 1;
	} else if (door->doorState == STATE_OPENED) {
		door->doorState = STATE_CLOSED;
		opening = -1;
	} else
		return false;

	/* remember the old location */
	AABB oldAABB;
	gi.GetInlineModelAABB(door->model, oldAABB);
	GridBox rerouteOldBox(oldAABB);

	/* change rotation and relink */
	if (door->type == ET_DOOR) {
		if (door->dir & DOOR_OPEN_REVERSE)
			opening *= -1;
		door->angles[door->dir & 3] += DOOR_ROTATION_ANGLE * opening;
	} else if (door->type == ET_DOOR_SLIDING) {
		Door_SlidingUse(door);
	}
	gi.LinkEdict(door);

	/* maybe the server called this because the door starts opened */
	if (G_MatchIsRunning()) {
		/* let everybody know, that the door moves */
		if (door->doorState == STATE_OPENED)
			G_EventDoorOpen(door);
		else
			G_EventDoorClose(door);
		if (door->noise[0] != '\0') {
			const playermask_t playerMask = G_GetClosePlayerMask(door->origin, UNIT_SIZE * 10);
			G_EventSpawnSound(playerMask, false, door, door->origin, door->noise);
		}
	}

	/* Update model orientation */
	gi.SetInlineModelOrientation(door->model, door->origin, door->angles);
	AABB newAabb;
	gi.GetInlineModelAABB(door->model, newAabb);
	GridBox rerouteNewBox(newAabb);
	Com_DPrintf(DEBUG_GAME, "Server processed door movement.\n");

	/* Update path finding table for the new location of the model */
	G_RecalcRouting(door->model, rerouteOldBox);							/* Update path finding table */
	G_RecalcRouting(door->model, rerouteNewBox);

	if (activator && G_IsLivingActor(activator)) {
		/* Check if the player appears/perishes, seen from other teams. */
		G_CheckVis(activator);

		/* Calc new vis for the activator. */
		G_CheckVisTeamAll(activator->team, 0, activator);
	}

	return true;
}
Beispiel #12
0
/**
 * @note Think functions are only executed when the match is running
 * or in other word, the game has started
 */
void G_MissionThink (edict_t *self)
{
	edict_t *chain = self->groupMaster;
	edict_t *ent;
	int team;

	if (!G_MatchIsRunning())
		return;

	/* when every player has joined the match - spawn the mission target
	 * particle (if given) to mark the trigger */
	if (self->particle) {
		G_SpawnParticle(self->origin, self->spawnflags, self->particle);

		/* This is automatically freed on map shutdown */
		self->particle = NULL;
	}

	if (!chain)
		chain = self;
	while (chain) {
		if (chain->type == ET_MISSION) {
			if (chain->item) {
				const invList_t *ic;
				G_GetFloorItems(chain);
				ic = FLOOR(chain);
				if (!ic) {
					/* reset the counter if there is no item */
					chain->count = 0;
					return;
				}
				for (; ic; ic = ic->next) {
					const objDef_t *od = ic->item.t;
					assert(od);
					/* not the item we are looking for */
					if (Q_streq(od->id, chain->item))
						break;
				}
				if (!ic) {
					/* reset the counter if it's not the searched item */
					chain->count = 0;
					return;
				}
			}
			if (chain->time) {
				/* not every edict in the group chain has
				 * been occupied long enough */
				if (!chain->count || level.actualRound - chain->count < chain->time)
					return;
			}
			/* not destroyed yet */
			if ((chain->flags & FL_DESTROYABLE) && chain->HP)
				return;
		}
		chain = chain->groupChain;
	}

	if (self->use)
		self->use(self, NULL);

	/* store team before the edict is released */
	team = self->team;

	chain = self->groupMaster;
	if (!chain)
		chain = self;
	while (chain) {
		if (chain->item != NULL) {
			edict_t *item = G_GetEdictFromPos(chain->pos, ET_ITEM);
			if (item != NULL) {
				if (!G_InventoryRemoveItemByID(chain->item, item, gi.csi->idFloor)) {
					Com_Printf("Could not remove item '%s' from floor edict %i\n",
							chain->item, item->number);
				} else {
					G_AppearPerishEvent(G_VisToPM(item->visflags), false, item, NULL);
				}
			}
		}
		if (chain->particle != NULL) {
			/** @todo not yet working - particle stays active */
			edict_t *particle = G_GetEdictFromPos(chain->pos, ET_PARTICLE);
			if (particle != NULL) {
				G_AppearPerishEvent(PM_ALL, false, particle, NULL);
				G_FreeEdict(particle);
			}
		}

		ent = chain->groupChain;
		/* free the trigger */
		if (chain->child)
			G_FreeEdict(chain->child);
		/* free the group chain */
		G_FreeEdict(chain);
		chain = ent;
	}
	self = NULL;

	/* still active mission edicts left */
	ent = NULL;
	while ((ent = G_EdictsGetNextInUse(ent)))
		if (ent->type == ET_MISSION && ent->team == team)
			return;

	G_MatchEndTrigger(team, 10);
}