Ejemplo n.º 1
0
static void DoDamageCharacter(
	const Vec2i hitVector,
	const int power,
	const int flags,
	const int playerUID,
	const int uid,
	TActor *actor,
	const special_damage_e special)
{
	// Create events: hit, damage, score
	CASSERT(actor->isInUse, "Cannot damage nonexistent player");
	CASSERT(CanHitCharacter(flags, uid, actor), "damaging undamageable actor");

	if (ConfigGetBool(&gConfig, "Game.ShotsPushback"))
	{
		GameEvent ei = GameEventNew(GAME_EVENT_ACTOR_IMPULSE);
		ei.u.ActorImpulse.UID = actor->uid;
		ei.u.ActorImpulse.Vel = Vec2i2Net(Vec2iScaleDiv(
			Vec2iScale(hitVector, power), SHOT_IMPULSE_DIVISOR));
		ei.u.ActorImpulse.Pos = Vec2i2Net(actor->Pos);
		GameEventsEnqueue(&gGameEvents, ei);
	}

	const bool canDamage =
		CanDamageCharacter(flags, playerUID, uid, actor, special);

	GameEvent e = GameEventNew(GAME_EVENT_ACTOR_HIT);
	e.u.ActorHit.UID = actor->uid;
	e.u.ActorHit.PlayerUID = actor->PlayerUID;
	e.u.ActorHit.HitterPlayerUID = playerUID;
	e.u.ActorHit.Special = special;
	e.u.ActorHit.Power = canDamage ? power : 0;
	e.u.ActorHit.Vel = Vec2i2Net(hitVector);
	GameEventsEnqueue(&gGameEvents, e);

	if (canDamage)
	{
		// Don't score for friendly or player hits
		const bool isFriendly =
			(actor->flags & FLAGS_GOOD_GUY) ||
			(!IsPVP(gCampaign.Entry.Mode) && actor->PlayerUID >= 0);
		if (playerUID >= 0 && power != 0 && !isFriendly)
		{
			// Calculate score based on
			// if they hit a penalty character
			e = GameEventNew(GAME_EVENT_SCORE);
			e.u.Score.PlayerUID = playerUID;
			if (actor->flags & FLAGS_PENALTY)
			{
				e.u.Score.Score = PENALTY_MULTIPLIER * power;
			}
			else
			{
				e.u.Score.Score = power;
			}
			GameEventsEnqueue(&gGameEvents, e);
		}
	}
}
Ejemplo n.º 2
0
static void DestroyObject(
	TObject *o, const int flags, const int playerUID, const int uid)
{
	o->Health = 0;

	const Vec2i realPos = Vec2iNew(o->tileItem.x, o->tileItem.y);

	if (!gCampaign.IsClient)
	{
		// Update objective
		UpdateMissionObjective(&gMission, o->tileItem.flags, OBJECTIVE_DESTROY);
		// Extra score if objective
		if ((o->tileItem.flags & TILEITEM_OBJECTIVE) && playerUID >= 0)
		{
			GameEvent e = GameEventNew(GAME_EVENT_SCORE);
			e.u.Score.PlayerUID = playerUID;
			e.u.Score.Score = OBJECT_SCORE;
			GameEventsEnqueue(&gGameEvents, e);
		}

		// Weapons that go off when this object is destroyed
		const Vec2i fullPos = Vec2iReal2Full(realPos);
		for (int i = 0; i < (int)o->Class->DestroyGuns.size; i++)
		{
			const GunDescription **g = CArrayGet(&o->Class->DestroyGuns, i);
			GunFire(*g, fullPos, 0, 0, flags, playerUID, uid, true, false);
		}

		// A wreck left after the destruction of this object
		// TODO: doesn't need to be network event
		GameEvent e = GameEventNew(GAME_EVENT_ADD_BULLET);
		e.u.AddBullet.UID = MobObjsObjsGetNextUID();
		strcpy(e.u.AddBullet.BulletClass, "fireball_wreck");
		e.u.AddBullet.MuzzlePos = Vec2i2Net(fullPos);
		e.u.AddBullet.MuzzleHeight = 0;
		e.u.AddBullet.Angle = 0;
		e.u.AddBullet.Elevation = 0;
		e.u.AddBullet.Flags = 0;
		e.u.AddBullet.PlayerUID = -1;
		e.u.AddBullet.ActorUID = -1;
		GameEventsEnqueue(&gGameEvents, e);
	}

	SoundPlayAt(&gSoundDevice, gSoundDevice.wreckSound, realPos);

	// Turn the object into a wreck, if available
	if (o->Class->Wreck.Pic)
	{
		o->tileItem.flags = TILEITEM_IS_WRECK;
	}
	else
	{
		ObjDestroy(o->tileItem.id);
	}

	// Update pathfinding cache since this object could have blocked a path
	// before
	PathCacheClear(&gPathCache);
}
Ejemplo n.º 3
0
void GunAddBullets(
	const GunDescription *g, const Vec2i fullPos, const int z,
	const double radians,
	const int flags, const int player, const int uid,
	const bool playSound)
{
	// Add bullets
	if (g->Bullet)
	{
		// Find the starting angle of the spread (clockwise)
		// Keep in mind the fencepost problem, i.e. spread of 3 means a
		// total spread angle of 2x width
		const double spreadStartAngle =
			g->AngleOffset - (g->Spread.Count - 1) * g->Spread.Width / 2;
		for (int i = 0; i < g->Spread.Count; i++)
		{
			const double recoil =
				((double)rand() / RAND_MAX * g->Recoil) - g->Recoil / 2;
			const double finalAngle =
				radians + spreadStartAngle + i * g->Spread.Width + recoil;
			GameEvent e = GameEventNew(GAME_EVENT_ADD_BULLET);
			strcpy(e.u.AddBullet.BulletClass, g->Bullet->Name);
			e.u.AddBullet.MuzzlePos = Vec2i2Net(fullPos);
			e.u.AddBullet.MuzzleHeight = z;
			e.u.AddBullet.Angle = (float)finalAngle;
			e.u.AddBullet.Elevation = RAND_INT(g->ElevationLow, g->ElevationHigh);
			e.u.AddBullet.Flags = flags;
			e.u.AddBullet.PlayerIndex = player;
			e.u.AddBullet.UID = uid;
			GameEventsEnqueue(&gGameEvents, e);
		}
	}
	// Add muzzle flash
	if (GunHasMuzzle(g))
	{
		GameEvent e = GameEventNew(GAME_EVENT_ADD_PARTICLE);
		e.u.AddParticle.Class = g->MuzzleFlash;
		e.u.AddParticle.FullPos = fullPos;
		e.u.AddParticle.Z = z;
		e.u.AddParticle.Angle = radians;
		GameEventsEnqueue(&gGameEvents, e);
	}
	// Sound
	if (playSound && g->Sound)
	{
		SoundPlayAt(&gSoundDevice, g->Sound, Vec2iFull2Real(fullPos));
	}
	// Screen shake
	if (g->ShakeAmount > 0)
	{
		GameEvent shake = GameEventNew(GAME_EVENT_SCREEN_SHAKE);
		shake.u.ShakeAmount = g->ShakeAmount;
		GameEventsEnqueue(&gGameEvents, shake);
	}
}
Ejemplo n.º 4
0
void CreateEnemies(void)
{
    if (gCampaign.Setting.characters.baddieIds.size == 0)
    {
        return;
    }

    for (int i = 0;
            i < MAX(1, (gMission.missionData->EnemyDensity * ConfigGetInt(&gConfig, "Game.EnemyDensity")) / 100);
            i++)
    {
        NActorAdd aa = NActorAdd_init_default;
        aa.UID = ActorsGetNextUID();
        aa.CharId = CharacterStoreGetRandomBaddieId(
                        &gCampaign.Setting.characters);
        aa.FullPos = PlaceAwayFromPlayers(&gMap);
        aa.Direction = rand() % DIRECTION_COUNT;
        const Character *c =
            CArrayGet(&gCampaign.Setting.characters.OtherChars, aa.CharId);
        aa.Health = CharacterGetStartingHealth(c, true);
        GameEvent e = GameEventNew(GAME_EVENT_ACTOR_ADD);
        e.u.ActorAdd = aa;
        GameEventsEnqueue(&gGameEvents, e);
        gBaddieCount++;

        // Process the events that actually place the actors
        HandleGameEvents(&gGameEvents, NULL, NULL, NULL);
    }
}
Ejemplo n.º 5
0
void Damage(
	const Vec2i hitVector,
	const int power,
	const int flags,
	const int playerUID,
	const int uid,
	const TileItemKind targetKind, const int targetUID,
	const special_damage_e special)
{
	switch (targetKind)
	{
	case KIND_CHARACTER:
		DoDamageCharacter(
			hitVector,
			power, flags, playerUID, uid,
			ActorGetByUID(targetUID), special);
		break;
	case KIND_OBJECT:
		{
			GameEvent e = GameEventNew(GAME_EVENT_MAP_OBJECT_DAMAGE);
			e.u.MapObjectDamage.UID = targetUID;
			e.u.MapObjectDamage.Power = power;
			e.u.MapObjectDamage.ActorUID = uid;
			e.u.MapObjectDamage.PlayerUID = playerUID;
			e.u.MapObjectDamage.Flags = flags;
			GameEventsEnqueue(&gGameEvents, e);
		}
		break;
	default:
		CASSERT(false, "cannot damage tile item kind");
		break;
	}
}
Ejemplo n.º 6
0
bool RunGame(const CampaignOptions *co, struct MissionOptions *m, Map *map)
{
	// Clear the background
	DrawRectangle(
		&gGraphicsDevice, Vec2iZero(), gGraphicsDevice.cachedConfig.Res,
		colorBlack, 0);
	SDL_UpdateTexture(
		gGraphicsDevice.bkg, NULL, gGraphicsDevice.buf,
		gGraphicsDevice.cachedConfig.Res.x * sizeof(Uint32));

	MapLoad(map, m, co);

	// Seed random if PVP mode (otherwise players will always spawn in same
	// position)
	if (IsPVP(co->Entry.Mode))
	{
		srand((unsigned int)time(NULL));
	}

	if (!co->IsClient)
	{
		MapLoadDynamic(map, m, &co->Setting.characters);

		// For PVP modes, mark all map as explored
		if (IsPVP(co->Entry.Mode))
		{
			MapMarkAllAsVisited(map);
		}

		// Reset players for the mission
		CA_FOREACH(const PlayerData, p, gPlayerDatas)
			// Only reset for local players; for remote ones wait for the
			// client ready message
			if (!p->IsLocal) continue;
			GameEvent e = GameEventNew(GAME_EVENT_PLAYER_DATA);
			e.u.PlayerData = PlayerDataMissionReset(p);
			GameEventsEnqueue(&gGameEvents, e);
		CA_FOREACH_END()
		// Process the events to force add the players
		HandleGameEvents(&gGameEvents, NULL, NULL, NULL);

		// Note: place players first,
		// as bad guys are placed away from players
		Vec2i firstPos = Vec2iZero();
		CA_FOREACH(const PlayerData, p, gPlayerDatas)
			if (!p->Ready) continue;
			firstPos = PlacePlayer(&gMap, p, firstPos, true);
		CA_FOREACH_END()
		if (!IsPVP(co->Entry.Mode))
		{
			InitializeBadGuys();
			CreateEnemies();
		}
	}
Ejemplo n.º 7
0
bool NumPlayersSelection(GraphicsDevice *graphics, EventHandlers *handlers)
{
	MenuSystem ms;
	MenuSystemInit(
		&ms, handlers, graphics,
		Vec2iZero(),
		graphics->cachedConfig.Res);
	ms.allowAborts = true;
	ms.root = ms.current = MenuCreateNormal(
		"",
		"Select number of players",
		MENU_TYPE_NORMAL,
		0);
	MenuAddSubmenu(ms.current, MenuCreateReturn("(No local players)", 0));
	for (int i = 0; i < MAX_LOCAL_PLAYERS; i++)
	{
		char buf[2];
		sprintf(buf, "%d", i + 1);
		MenuAddSubmenu(ms.current, MenuCreateReturn(buf, i + 1));
	}
	MenuAddExitType(&ms, MENU_TYPE_RETURN);
	// Select 1 player default
	ms.current->u.normal.index = 1;

	MenuLoop(&ms);
	const bool ok = !ms.hasAbort;
	if (ok)
	{
		const int numPlayers = ms.current->u.returnCode;
		CA_FOREACH(const PlayerData, p, gPlayerDatas)
			CASSERT(!p->IsLocal, "unexpected local player");
		CA_FOREACH_END()
		// Add the players
		for (int i = 0; i < numPlayers; i++)
		{
			GameEvent e = GameEventNew(GAME_EVENT_PLAYER_DATA);
			e.u.PlayerData = PlayerDataDefault(i);
			e.u.PlayerData.UID = gNetClient.FirstPlayerUID + i;
			GameEventsEnqueue(&gGameEvents, e);
		}
		// Process the events to force add the players
		HandleGameEvents(&gGameEvents, NULL, NULL, NULL);
		// This also causes the client to send player data to the server
	}
	MenuSystemTerminate(&ms);
	return ok;
}
Ejemplo n.º 8
0
void EmitterStart(
	Emitter *em, const Vec2i fullPos, const int z, const Vec2i vel)
{
	const Vec2i p = Vec2iAdd(fullPos, Vec2iReal2Full(em->offset));

	// TODO: single event multiple particles
	GameEvent e = GameEventNew(GAME_EVENT_ADD_PARTICLE);
	e.u.AddParticle.FullPos = p;
	e.u.AddParticle.Z = z * Z_FACTOR;
	e.u.AddParticle.Class = em->p;
	const int speed = RAND_INT(em->minSpeed, em->maxSpeed);
	const Vec2i baseVel = Vec2iFromPolar(speed, RAND_DOUBLE(0, PI * 2));
	e.u.AddParticle.Vel = Vec2iAdd(vel, baseVel);
	e.u.AddParticle.Angle = RAND_DOUBLE(0, PI * 2);
	e.u.AddParticle.DZ = RAND_INT(em->minDZ, em->maxDZ);
	e.u.AddParticle.Spin = RAND_DOUBLE(em->minRotation, em->maxRotation);
	GameEventsEnqueue(&gGameEvents, e);
}
Ejemplo n.º 9
0
Vec2i PlacePlayer(
	Map *map, const PlayerData *p, const Vec2i firstPos, const bool pumpEvents)
{
	NetMsgActorAdd aa = NetMsgActorAdd_init_default;
	aa.Id = ActorsGetFreeIndex();
	aa.Health = p->Char.maxHealth;
	aa.PlayerId = p->playerIndex;

	if (IsPVP(gCampaign.Entry.Mode))
	{
		// In a PVP mode, always place players apart
		aa.FullPos = PlaceActor(&gMap);
	}
	else if (gMission.missionData->Type == MAPTYPE_STATIC &&
		!Vec2iIsZero(gMission.missionData->u.Static.Start))
	{
		// place players near the start point
		Vec2i startPoint = Vec2iReal2Full(Vec2iCenterOfTile(
			gMission.missionData->u.Static.Start));
		aa.FullPos = PlaceActorNear(map, startPoint, true);
	}
	else if (gConfig.Interface.Splitscreen == SPLITSCREEN_NEVER &&
		!Vec2iIsZero(firstPos))
	{
		// If never split screen, try to place players near the first player
		aa.FullPos = PlaceActorNear(map, firstPos, true);
	}
	else
	{
		aa.FullPos = PlaceActor(map);
	}

	GameEvent e = GameEventNew(GAME_EVENT_ACTOR_ADD);
	e.u.ActorAdd = aa;
	GameEventsEnqueue(&gGameEvents, e);

	if (pumpEvents)
	{
		// Process the events that actually place the players
		HandleGameEvents(&gGameEvents, NULL, NULL, NULL, &gEventHandlers);
	}

	return Vec2iNew(aa.FullPos.x, aa.FullPos.y);
}
Ejemplo n.º 10
0
void UpdateObjects(const int ticks)
{
	for (int i = 0; i < (int)gObjs.size; i++)
	{
		TObject *obj = CArrayGet(&gObjs, i);
		if (!obj->isInUse)
		{
			continue;
		}
		switch (obj->Class->Type)
		{
		case MAP_OBJECT_TYPE_PICKUP_SPAWNER:
			if (gCampaign.IsClient) break;
			// If counter -1, it is inactive i.e. already spawned pickup
			if (obj->counter == -1)
			{
				break;
			}
			obj->counter -= ticks;
			if (obj->counter <= 0)
			{
				// Deactivate spawner by setting counter to -1
				// Spawner reactivated only when ammo taken
				obj->counter = -1;
				GameEvent e = GameEventNew(GAME_EVENT_ADD_PICKUP);
				e.u.AddPickup.UID = PickupsGetNextUID();
				strcpy(
					e.u.AddPickup.PickupClass,
					obj->Class->u.PickupClass->Name);
				e.u.AddPickup.IsRandomSpawned = false;
				e.u.AddPickup.SpawnerUID = obj->uid;
				e.u.AddPickup.TileItemFlags = 0;
				e.u.AddPickup.Pos =
					Vec2i2Net(Vec2iNew(obj->tileItem.x, obj->tileItem.y));
				GameEventsEnqueue(&gGameEvents, e);
			}
			break;
		default:
			// Do nothing
			break;
		}
	}
}
Ejemplo n.º 11
0
void UpdateMobileObjects(int ticks)
{
	for (int i = 0; i < (int)gMobObjs.size; i++)
	{
		TMobileObject *obj = CArrayGet(&gMobObjs, i);
		if (!obj->isInUse)
		{
			continue;
		}
		if (!obj->updateFunc(obj, ticks) && !gCampaign.IsClient)
		{
			GameEvent e = GameEventNew(GAME_EVENT_REMOVE_BULLET);
			e.u.RemoveBullet.UID = obj->UID;
			GameEventsEnqueue(&gGameEvents, e);
			continue;
		}
		CPicUpdate(&obj->tileItem.CPic, ticks);
	}
}
Ejemplo n.º 12
0
static void AddPickupAtObject(const TObject *o, const PickupType type)
{
	GameEvent e = GameEventNew(GAME_EVENT_ADD_PICKUP);
	switch (type)
	{
	case PICKUP_JEWEL: CASSERT(false, "unexpected pickup type"); break;
	case PICKUP_HEALTH:
		if (!ConfigGetBool(&gConfig, "Game.HealthPickups"))
		{
			return;
		}
		strcpy(e.u.AddPickup.PickupClass, "health");
		break;
	case PICKUP_AMMO:
		if (!ConfigGetBool(&gConfig, "Game.Ammo"))
		{
			return;
		}
		// Pick a random ammo type and spawn it
		{
			const int ammoId = rand() % AmmoGetNumClasses(&gAmmo);
			const Ammo *a = AmmoGetById(&gAmmo, ammoId);
			sprintf(e.u.AddPickup.PickupClass, "ammo_%s", a->Name);
		}
		break;
	case PICKUP_KEYCARD: CASSERT(false, "unexpected pickup type"); break;
	case PICKUP_GUN:
		// Pick a random mission gun type and spawn it
		{
			const int gunId = (int)(rand() % gMission.Weapons.size);
			const WeaponClass **wc = CArrayGet(&gMission.Weapons, gunId);
			sprintf(e.u.AddPickup.PickupClass, "gun_%s", (*wc)->name);
		}
		break;
	default: CASSERT(false, "unexpected pickup type"); break;
	}
	e.u.AddPickup.UID = PickupsGetNextUID();
	e.u.AddPickup.Pos = Vec2ToNet(o->thing.Pos);
	e.u.AddPickup.IsRandomSpawned = true;
	e.u.AddPickup.SpawnerUID = -1;
	e.u.AddPickup.ThingFlags = 0;
	GameEventsEnqueue(&gGameEvents, e);
}
Ejemplo n.º 13
0
void DamageObject(const NThingDamage d)
{
	TObject *o = ObjGetByUID(d.UID);
	// Don't bother if object already destroyed
	if (o->Health <= 0)
	{
		return;
	}

	o->Health -= d.Power;

	// Destroying objects and all the wonderful things that happen
	if (o->Health <= 0 && !gCampaign.IsClient)
	{
		GameEvent e = GameEventNew(GAME_EVENT_MAP_OBJECT_REMOVE);
		e.u.MapObjectRemove.UID = o->uid;
		e.u.MapObjectRemove.ActorUID = d.SourceActorUID;
		e.u.MapObjectRemove.Flags = d.Flags;
		GameEventsEnqueue(&gGameEvents, e);
	}
}
Ejemplo n.º 14
0
void ObjRemove(const NMapObjectRemove mor)
{
	TObject *o = ObjGetByUID(mor.UID);
	o->Health = 0;

	if (!gCampaign.IsClient)
	{
		// Update objective
		UpdateMissionObjective(
			&gMission, o->thing.flags, OBJECTIVE_DESTROY, 1);
		const TActor *a = ActorGetByUID(mor.ActorUID);
		const int playerUID = a != NULL ? a->PlayerUID : -1;
		// Extra score if objective
		if ((o->thing.flags & THING_OBJECTIVE) && playerUID >= 0)
		{
			GameEvent e = GameEventNew(GAME_EVENT_SCORE);
			e.u.Score.PlayerUID = playerUID;
			e.u.Score.Score = OBJECT_SCORE;
			GameEventsEnqueue(&gGameEvents, e);
		}

		// Weapons that go off when this object is destroyed
		CA_FOREACH(const WeaponClass *, wc, o->Class->DestroyGuns)
			WeaponClassFire(
				*wc, o->thing.Pos, 0, 0, mor.Flags, mor.ActorUID,
				true, false);
		CA_FOREACH_END()

		// Random chance to add pickups in single player modes
		if (!IsPVP(gCampaign.Entry.Mode))
		{
			CA_FOREACH(
				const MapObjectDestroySpawn, mods,  o->Class->DestroySpawn)
				const double chance = (double) rand() / RAND_MAX;
				if (chance < mods->SpawnChance)
				{
					AddPickupAtObject(o, mods->Type);
				}
			CA_FOREACH_END()
		}
Ejemplo n.º 15
0
static void PlayerSpecialCommands(TActor *actor, const int cmd)
{
	if ((cmd & CMD_BUTTON2) && CMD_HAS_DIRECTION(cmd))
	{
		if (ConfigGetEnum(&gConfig, "Game.SwitchMoveStyle") == SWITCHMOVE_SLIDE)
		{
			SlideActor(actor, cmd);
		}
	}
	else if (
		(actor->lastCmd & CMD_BUTTON2) &&
		!(cmd & CMD_BUTTON2) &&
		!actor->specialCmdDir &&
		!actor->CanPickupSpecial &&
		!(ConfigGetEnum(&gConfig, "Game.SwitchMoveStyle") == SWITCHMOVE_SLIDE && CMD_HAS_DIRECTION(cmd)) &&
		ActorCanSwitchGun(actor))
	{
		GameEvent e = GameEventNew(GAME_EVENT_ACTOR_SWITCH_GUN);
		e.u.ActorSwitchGun.UID = actor->uid;
		e.u.ActorSwitchGun.GunIdx = (actor->gunIndex + 1) % actor->guns.size;
		GameEventsEnqueue(&gGameEvents, e);
	}
}
Ejemplo n.º 16
0
static void AddBrass(
	const GunDescription *g, const direction_e d, const Vec2i pos)
{
	CASSERT(g->Brass, "Cannot create brass for no-brass weapon");
	GameEvent e = GameEventNew(GAME_EVENT_ADD_PARTICLE);
	e.u.AddParticle.Class = g->Brass;
	double x, y;
	const double radians = dir2radians[d];
	GetVectorsForRadians(radians, &x, &y);
	const Vec2i ejectionPortOffset = Vec2iReal2Full(Vec2iScale(Vec2iNew(
		(int)round(x), (int)round(y)), 7));
	const Vec2i muzzleOffset = GunGetMuzzleOffset(g, d);
	const Vec2i muzzlePosition = Vec2iAdd(pos, muzzleOffset);
	e.u.AddParticle.FullPos = Vec2iMinus(muzzlePosition, ejectionPortOffset);
	e.u.AddParticle.Z = g->MuzzleHeight;
	e.u.AddParticle.Vel = Vec2iScaleDiv(
		GetFullVectorsForRadians(radians + PI / 2), 3);
	e.u.AddParticle.Vel.x += (rand() % 128) - 64;
	e.u.AddParticle.Vel.y += (rand() % 128) - 64;
	e.u.AddParticle.Angle = RAND_DOUBLE(0, PI * 2);
	e.u.AddParticle.DZ = (rand() % 6) + 6;
	e.u.AddParticle.Spin = RAND_DOUBLE(-0.1, 0.1);
	GameEventsEnqueue(&gGameEvents, e);
}
Ejemplo n.º 17
0
		// Random chance to add pickups in single player modes
		if (!IsPVP(gCampaign.Entry.Mode))
		{
			CA_FOREACH(
				const MapObjectDestroySpawn, mods,  o->Class->DestroySpawn)
				const double chance = (double) rand() / RAND_MAX;
				if (chance < mods->SpawnChance)
				{
					AddPickupAtObject(o, mods->Type);
				}
			CA_FOREACH_END()
		}

		// A wreck left after the destruction of this object
		// TODO: doesn't need to be network event
		GameEvent e = GameEventNew(GAME_EVENT_ADD_BULLET);
		e.u.AddBullet.UID = MobObjsObjsGetNextUID();
		strcpy(e.u.AddBullet.BulletClass, "fireball_wreck");
		e.u.AddBullet.MuzzlePos = Vec2ToNet(o->thing.Pos);
		e.u.AddBullet.MuzzleHeight = 0;
		e.u.AddBullet.Angle = 0;
		e.u.AddBullet.Elevation = 0;
		e.u.AddBullet.Flags = 0;
		e.u.AddBullet.ActorUID = -1;
		GameEventsEnqueue(&gGameEvents, e);
	}

	SoundPlayAt(&gSoundDevice, StrSound("bang"), o->thing.Pos);

	// If wreck is available spawn it in the exact same position
	PlaceWreck(o->Class->Wreck, &o->thing);
Ejemplo n.º 18
0
void InitializeBadGuys(void)
{
    for (int i = 0; i < (int)gMission.missionData->Objectives.size; i++)
    {
        MissionObjective *mobj =
            CArrayGet(&gMission.missionData->Objectives, i);
        ObjectiveDef *obj = CArrayGet(&gMission.Objectives, i);
        if (mobj->Type == OBJECTIVE_KILL &&
                gMission.missionData->SpecialChars.size > 0)
        {
            for (; obj->placed < mobj->Count; obj->placed++)
            {
                NActorAdd aa = NActorAdd_init_default;
                aa.UID = ActorsGetNextUID();
                aa.CharId = CharacterStoreGetRandomSpecialId(
                                &gCampaign.Setting.characters);
                aa.TileItemFlags = ObjectiveToTileItem(i);
                aa.Direction = rand() % DIRECTION_COUNT;
                const Character *c =
                    CArrayGet(&gCampaign.Setting.characters.OtherChars, aa.CharId);
                aa.Health = CharacterGetStartingHealth(c, true);
                aa.FullPos = PlaceAwayFromPlayers(&gMap);
                GameEvent e = GameEventNew(GAME_EVENT_ACTOR_ADD);
                e.u.ActorAdd = aa;
                GameEventsEnqueue(&gGameEvents, e);

                // Process the events that actually place the actors
                HandleGameEvents(&gGameEvents, NULL, NULL, NULL);
            }
        }
        else if (mobj->Type == OBJECTIVE_RESCUE)
        {
            for (; obj->placed < mobj->Count; obj->placed++)
            {
                NActorAdd aa = NActorAdd_init_default;
                aa.UID = ActorsGetNextUID();
                aa.CharId = CharacterStoreGetPrisonerId(
                                &gCampaign.Setting.characters, 0);
                aa.TileItemFlags = ObjectiveToTileItem(i);
                aa.Direction = rand() % DIRECTION_COUNT;
                const Character *c =
                    CArrayGet(&gCampaign.Setting.characters.OtherChars, aa.CharId);
                aa.Health = CharacterGetStartingHealth(c, true);
                if (MapHasLockedRooms(&gMap))
                {
                    aa.FullPos = PlacePrisoner(&gMap);
                }
                else
                {
                    aa.FullPos = PlaceAwayFromPlayers(&gMap);
                }
                GameEvent e = GameEventNew(GAME_EVENT_ACTOR_ADD);
                e.u.ActorAdd = aa;
                GameEventsEnqueue(&gGameEvents, e);

                // Process the events that actually place the actors
                HandleGameEvents(&gGameEvents, NULL, NULL, NULL);
            }
        }
    }

    gBaddieCount = gMission.index * 4;
    gAreGoodGuysPresent = 0;
}
Ejemplo n.º 19
0
void CommandBadGuys(int ticks)
{
    int count = 0;
    int delayModifier;
    int rollLimit;

    switch (ConfigGetEnum(&gConfig, "Game.Difficulty"))
    {
    case DIFFICULTY_VERYEASY:
        delayModifier = 4;
        rollLimit = 300;
        break;
    case DIFFICULTY_EASY:
        delayModifier = 2;
        rollLimit = 200;
        break;
    case DIFFICULTY_HARD:
        delayModifier = 1;
        rollLimit = 75;
        break;
    case DIFFICULTY_VERYHARD:
        delayModifier = 1;
        rollLimit = 50;
        break;
    default:
        delayModifier = 1;
        rollLimit = 100;
        break;
    }

    CA_FOREACH(TActor, actor, gActors)
    if (!actor->isInUse)
    {
        continue;
    }
    const CharBot *bot = ActorGetCharacter(actor)->bot;
    if (!(actor->PlayerUID >= 0 || (actor->flags & FLAGS_PRISONER)))
    {
        if ((actor->flags & (FLAGS_VICTIM | FLAGS_GOOD_GUY)) != 0)
        {
            gAreGoodGuysPresent = 1;
        }

        count++;
        int cmd = 0;

        // Wake up if it can see a player
        if ((actor->flags & FLAGS_SLEEPING) &&
                actor->aiContext->Delay == 0)
        {
            if (CanSeeAPlayer(actor))
            {
                actor->flags &= ~FLAGS_SLEEPING;
                ActorSetAIState(actor, AI_STATE_NONE);
            }
            actor->aiContext->Delay = bot->actionDelay * delayModifier;
            // Randomly change direction
            int newDir = (int)actor->direction + ((rand() % 2) * 2 - 1);
            if (newDir < (int)DIRECTION_UP)
            {
                newDir = (int)DIRECTION_UPLEFT;
            }
            if (newDir == (int)DIRECTION_COUNT)
            {
                newDir = (int)DIRECTION_UP;
            }
            cmd = DirectionToCmd((int)newDir);
        }
        // Go to sleep if the player's too far away
        if (!(actor->flags & FLAGS_SLEEPING) &&
                actor->aiContext->Delay == 0 &&
                !(actor->flags & FLAGS_AWAKEALWAYS))
        {
            if (!IsCloseToPlayer(actor->Pos, (40 * 16) << 8))
            {
                actor->flags |= FLAGS_SLEEPING;
                ActorSetAIState(actor, AI_STATE_IDLE);
            }
        }

        if (!actor->dead && !(actor->flags & FLAGS_SLEEPING))
        {
            bool bypass = false;
            const int roll = rand() % rollLimit;
            if (actor->flags & FLAGS_FOLLOWER)
            {
                if (IsCloseToPlayer(actor->Pos, 32 << 8))
                {
                    cmd = 0;
                    ActorSetAIState(actor, AI_STATE_IDLE);
                }
                else
                {
                    cmd = AIGoto(
                              actor, AIGetClosestPlayerPos(actor->Pos), true);
                    ActorSetAIState(actor, AI_STATE_FOLLOW);
                }
            }
            else if (!!(actor->flags & FLAGS_SNEAKY) &&
                     !!(actor->flags & FLAGS_VISIBLE) &&
                     DidPlayerShoot())
            {
                cmd = AIHuntClosest(actor) | CMD_BUTTON1;
                if (actor->flags & FLAGS_RUNS_AWAY)
                {
                    // Turn back and shoot for running away characters
                    cmd = AIReverseDirection(cmd);
                }
                bypass = true;
                ActorSetAIState(actor, AI_STATE_HUNT);
            }
            else if (actor->flags & FLAGS_DETOURING)
            {
                cmd = BrightWalk(actor, roll);
                ActorSetAIState(actor, AI_STATE_TRACK);
            }
            else if (actor->aiContext->Delay > 0)
            {
                cmd = actor->lastCmd & ~CMD_BUTTON1;
            }
            else
            {
                if (roll < bot->probabilityToTrack)
                {
                    cmd = AIHuntClosest(actor);
                    ActorSetAIState(actor, AI_STATE_HUNT);
                }
                else if (roll < bot->probabilityToMove)
                {
                    cmd = DirectionToCmd(rand() & 7);
                    ActorSetAIState(actor, AI_STATE_TRACK);
                }
                else
                {
                    cmd = 0;
                }
                actor->aiContext->Delay = bot->actionDelay * delayModifier;
            }
            if (!bypass)
            {
                if (WillFire(actor, roll))
                {
                    cmd |= CMD_BUTTON1;
                    if (!!(actor->flags & FLAGS_FOLLOWER) &&
                            (actor->flags & FLAGS_GOOD_GUY))
                    {
                        // Shoot in a random direction away
                        for (int j = 0; j < 10; j++)
                        {
                            direction_e d =
                                (direction_e)(rand() % DIRECTION_COUNT);
                            if (!IsFacingPlayer(actor, d))
                            {
                                cmd = DirectionToCmd(d) | CMD_BUTTON1;
                                break;
                            }
                        }
                    }
                    if (actor->flags & FLAGS_RUNS_AWAY)
                    {
                        // Turn back and shoot for running away characters
                        cmd |= AIReverseDirection(AIHuntClosest(actor));
                    }
                    ActorSetAIState(actor, AI_STATE_HUNT);
                }
                else
                {
                    if ((actor->flags & FLAGS_VISIBLE) == 0)
                    {
                        // I think this is some hack to make sure invisible enemies don't fire so much
                        ActorGetGun(actor)->lock = 40;
                    }
                    if (cmd && !IsDirectionOK(actor, CmdToDirection(cmd)) &&
                            (actor->flags & FLAGS_DETOURING) == 0)
                    {
                        Detour(actor);
                        cmd = 0;
                        ActorSetAIState(actor, AI_STATE_TRACK);
                    }
                }
            }
        }
        actor->aiContext->Delay =
            MAX(0, actor->aiContext->Delay - ticks);
        CommandActor(actor, cmd, ticks);
    }
    else if ((actor->flags & FLAGS_PRISONER) != 0)
    {
        CommandActor(actor, 0, ticks);
    }
    CA_FOREACH_END()
    if (gMission.missionData->Enemies.size > 0 &&
            gMission.missionData->EnemyDensity > 0 &&
            count < MAX(1, (gMission.missionData->EnemyDensity * ConfigGetInt(&gConfig, "Game.EnemyDensity")) / 100))
    {
        NActorAdd aa = NActorAdd_init_default;
        aa.UID = ActorsGetNextUID();
        aa.CharId = CharacterStoreGetRandomBaddieId(
                        &gCampaign.Setting.characters);
        aa.Direction = rand() % DIRECTION_COUNT;
        const Character *c =
            CArrayGet(&gCampaign.Setting.characters.OtherChars, aa.CharId);
        aa.Health = CharacterGetStartingHealth(c, true);
        aa.FullPos = PlaceAwayFromPlayers(&gMap);
        GameEvent e = GameEventNew(GAME_EVENT_ACTOR_ADD);
        e.u.ActorAdd = aa;
        GameEventsEnqueue(&gGameEvents, e);
        gBaddieCount++;
    }
}
Ejemplo n.º 20
0
static void OnReceive(NetClient *n, ENetEvent event)
{
	const NetMsg msgType = (NetMsg)*(uint32_t *)event.packet->data;
	LOG(LM_NET, LL_TRACE, "recv msg(%u)", msgType);
	const NetMsgEntry nme = NetMsgGet(msgType);
	if (nme.Event != GAME_EVENT_NONE)
	{
		// Game event message; decode and add to event queue
		LOG(LM_NET, LL_DEBUG, "recv gameEvent(%d)", (int)nme.Event);
		GameEvent e = GameEventNew(nme.Event);
		if (nme.Fields != NULL)
		{
			NetDecode(event.packet, &e.u, nme.Fields);
		}

		// For actor events, check if UID is not for local player
		int actorUID = -1;
		bool actorIsLocal = false;
		switch (nme.Event)
		{
		case GAME_EVENT_ACTOR_ADD:
			// Note: ignore checking this event
			break;
		case GAME_EVENT_ACTOR_MOVE: actorUID = e.u.ActorMove.UID; break;
		case GAME_EVENT_ACTOR_STATE: actorUID = e.u.ActorState.UID; break;
		case GAME_EVENT_ACTOR_DIR: actorUID = e.u.ActorDir.UID; break;
		case GAME_EVENT_ADD_BULLET:
			actorIsLocal = PlayerIsLocal(e.u.AddBullet.PlayerIndex);
			break;
		default: break;
		}
		if (actorUID >= 0)
		{
			actorIsLocal = ActorIsLocalPlayer(actorUID);
		}
		if (actorIsLocal)
		{
			LOG(LM_NET, LL_TRACE, "game event is for local player, ignoring");
		}
		else
		{
			GameEventsEnqueue(&gGameEvents, e);
		}
	}
	else
	{
		switch (msgType)
		{
		case MSG_CLIENT_ID:
			{
				CASSERT(
					n->ClientId == -1,
					"unexpected client ID message, already set");
				NetMsgClientId cid;
				NetDecode(event.packet, &cid, NetMsgClientId_fields);
				LOG(LM_NET, LL_DEBUG, "NetClient: received client ID %d", cid.Id);
				n->ClientId = cid.Id;
			}
			break;
		case MSG_CAMPAIGN_DEF:
			if (gCampaign.IsLoaded)
			{
				LOG(LM_NET, LL_INFO, "WARNING: unexpected campaign def msg received");
			}
			else
			{
				LOG(LM_NET, LL_DEBUG, "NetClient: received campaign def, loading...");
				NetMsgCampaignDef def;
				NetDecode(event.packet, &def, NetMsgCampaignDef_fields);
				char campaignPath[CDOGS_PATH_MAX];
				GameMode mode;
				NetMsgCampaignDefConvert(&def, campaignPath, &mode);
				CampaignEntry entry;
				if (CampaignEntryTryLoad(&entry, campaignPath, mode) &&
					CampaignLoad(&gCampaign, &entry))
				{
					gCampaign.IsClient = true;
				}
				else
				{
					printf("Error: failed to load campaign def\n");
					gCampaign.IsError = true;
				}
			}
			break;
		case MSG_PLAYER_DATA:
			{
				NetMsgPlayerData pd;
				NetDecode(event.packet, &pd, NetMsgPlayerData_fields);
				AddMissingPlayers(pd.PlayerIndex);
				LOG(LM_NET, LL_DEBUG, "recv player data name(%s) id(%d) total(%d)",
					pd.Name, pd.PlayerIndex, (int)gPlayerDatas.size);
				NetMsgPlayerDataUpdate(&pd);
			}
			break;
		case MSG_ADD_PLAYERS:
			{
				NetMsgAddPlayers ap;
				NetDecode(event.packet, &ap, NetMsgAddPlayers_fields);
				LOG(LM_NET, LL_DEBUG,
					"NetClient: received new players %d",
					(int)ap.PlayerIds_count);
				// Add new players
				// If they are local players, set them up with defaults
				const bool isLocal = ap.ClientId == n->ClientId;
				for (int i = 0; i < ap.PlayerIds_count; i++)
				{
					const int playerId = (int)ap.PlayerIds[i];
					AddMissingPlayers(playerId);
					PlayerData *p = CArrayGet(&gPlayerDatas, playerId);
					p->IsLocal = isLocal;
					if (isLocal)
					{
						PlayerDataSetLocalDefaults(p, i);
					}
				}
			}
			break;
		case MSG_GAME_START:
			LOG(LM_NET, LL_DEBUG, "NetClient: received game start");
			gMission.HasStarted = true;
			break;
		case MSG_GAME_END:
			LOG(LM_NET, LL_DEBUG, "NetClient: received game end");
			gMission.isDone = true;
			break;
		default:
			CASSERT(false, "unexpected message type");
			break;
		}
	}
	enet_packet_destroy(event.packet);
}
Ejemplo n.º 21
0
bool UpdateBullet(TMobileObject *obj, const int ticks)
{
	obj->count += ticks;
	obj->soundLock = MAX(0, obj->soundLock - ticks);
	obj->specialLock = MAX(0, obj->specialLock - ticks);
	if (obj->count < obj->bulletClass->Delay)
	{
		return true;
	}

	if (obj->range >= 0 && obj->count > obj->range)
	{
		if (!gCampaign.IsClient)
		{
			FireGuns(obj, &obj->bulletClass->OutOfRangeGuns);
		}
		return false;
	}

	const Vec2i objPos = Vec2iNew(obj->x, obj->y);

	if (obj->bulletClass->SeekFactor > 0)
	{
		// Find the closest target to this bullet and steer towards it
		const TActor *owner = ActorGetByUID(obj->ActorUID);
		if (owner == NULL)
		{
			return false;
		}
		const TActor *target = AIGetClosestEnemy(objPos, owner, obj->flags);
		if (target && !target->dead)
		{
			for (int i = 0; i < ticks; i++)
			{
				obj->vel = SeekTowards(
					objPos, obj->vel,
					obj->bulletClass->SpeedLow, target->Pos,
					obj->bulletClass->SeekFactor);
			}
		}
	}

	Vec2i pos = Vec2iScale(Vec2iAdd(objPos, obj->vel), ticks);
	HitType hitItem = HIT_NONE;
	if (!gCampaign.IsClient)
	{
		hitItem = HitItem(obj, pos, obj->bulletClass->Persists);
	}
	const Vec2i realPos = Vec2iFull2Real(pos);

	// Falling (grenades)
	if (obj->bulletClass->Falling.GravityFactor != 0)
	{
		bool hasDropped = obj->z <= 0;
		for (int i = 0; i < ticks; i++)
		{
			obj->z += obj->dz;
			if (obj->z <= 0)
			{
				obj->z = 0;
				if (obj->bulletClass->Falling.Bounces)
				{
					obj->dz = -obj->dz / 2;
				}
				else
				{
					obj->dz = 0;
				}
				if (!hasDropped)
				{
					if (!gCampaign.IsClient)
					{
						FireGuns(obj, &obj->bulletClass->Falling.DropGuns);
					}
				}
				hasDropped = true;
				if (obj->bulletClass->Falling.DestroyOnDrop)
				{
					return false;
				}
				SoundPlayAt(
					&gSoundDevice,
					StrSound(obj->bulletClass->HitSound.Wall), realPos);
			}
			else
			{
				obj->dz -= obj->bulletClass->Falling.GravityFactor;
			}
			if (!obj->bulletClass->Falling.FallsDown)
			{
				obj->dz = MAX(0, obj->dz);
			}
		}
	}
	
	// Friction
	const bool isDiagonal = obj->vel.x != 0 && obj->vel.y != 0;
	int frictionComponent = isDiagonal ?
		(int)round(obj->bulletClass->Friction / sqrt(2)) :
		obj->bulletClass->Friction;
	for (int i = 0; i < ticks; i++)
	{
		if (obj->vel.x > 0)
		{
			obj->vel.x -= frictionComponent;
		}
		else if (obj->vel.x < 0)
		{
			obj->vel.x += frictionComponent;
		}

		if (obj->vel.y > 0)
		{
			obj->vel.y -= frictionComponent;
		}
		else if (obj->vel.y < 0)
		{
			obj->vel.y += frictionComponent;
		}
	}

	bool hitWall = false;
	if (!gCampaign.IsClient && hitItem == HIT_NONE)
	{
		hitWall =
			MapIsRealPosIn(&gMap, realPos) && ShootWall(realPos.x, realPos.y);
	}
	if (hitWall || hitItem != HIT_NONE)
	{
		GameEvent b = GameEventNew(GAME_EVENT_BULLET_BOUNCE);
		b.u.BulletBounce.UID = obj->UID;
		if (hitWall && !Vec2iIsZero(obj->vel))
		{
			b.u.BulletBounce.HitType = (int)HIT_WALL;
		}
		else
		{
			b.u.BulletBounce.HitType = (int)hitItem;
		}
		bool alive = true;
		if ((hitWall && !obj->bulletClass->WallBounces) ||
			((hitItem != HIT_NONE) && obj->bulletClass->HitsObjects))
		{
			b.u.BulletBounce.Spark = true;
			CASSERT(!gCampaign.IsClient, "Cannot process bounces as client");
			FireGuns(obj, &obj->bulletClass->HitGuns);
			if (hitWall || !obj->bulletClass->Persists)
			{
				alive = false;
			}
		}
		b.u.BulletBounce.BouncePos = Vec2i2Net(pos);
		b.u.BulletBounce.BounceVel = Vec2i2Net(obj->vel);
		if (hitWall && !Vec2iIsZero(obj->vel))
		{
			// Bouncing
			Vec2i bounceVel = obj->vel;
			pos = GetWallBounceFullPos(objPos, pos, &bounceVel);
			b.u.BulletBounce.BouncePos = Vec2i2Net(pos);
			b.u.BulletBounce.BounceVel = Vec2i2Net(bounceVel);
			obj->vel = bounceVel;
		}
		GameEventsEnqueue(&gGameEvents, b);
		if (!alive)
		{
			return false;
		}
	}
	if (!MapTryMoveTileItem(&gMap, &obj->tileItem, realPos))
	{
		obj->count = obj->range;
		return false;
	}
	obj->x = pos.x;
	obj->y = pos.y;

	if (obj->bulletClass->Erratic)
	{
		for (int i = 0; i < ticks; i++)
		{
			obj->vel.x += ((rand() % 3) - 1) * 128;
			obj->vel.y += ((rand() % 3) - 1) * 128;
		}
	}

	// Proximity function, destroy
	// Only check proximity every now and then
	if (obj->bulletClass->ProximityGuns.size > 0 && !(obj->count & 3))
	{
		if (!gCampaign.IsClient)
		{
			// Detonate the mine if there are characters in the tiles around it
			const Vec2i tv =
				Vec2iToTile(Vec2iFull2Real(pos));
			Vec2i dv;
			for (dv.y = -1; dv.y <= 1; dv.y++)
			{
				for (dv.x = -1; dv.x <= 1; dv.x++)
				{
					const Vec2i dtv = Vec2iAdd(tv, dv);
					if (!MapIsTileIn(&gMap, dtv))
					{
						continue;
					}
					if (TileHasCharacter(MapGetTile(&gMap, dtv)))
					{
						FireGuns(obj, &obj->bulletClass->ProximityGuns);
						return false;
					}
				}
			}
		}
	}

	return true;
}
Ejemplo n.º 22
0
static void HandleGameEvent(
	const GameEvent e,
	Camera *camera,
	PowerupSpawner *healthSpawner,
	CArray *ammoSpawners)
{
	switch (e.Type)
	{
	case GAME_EVENT_PLAYER_DATA:
		PlayerDataAddOrUpdate(e.u.PlayerData);
		break;
	case GAME_EVENT_TILE_SET:
		{
			Tile *t = MapGetTile(&gMap, Net2Vec2i(e.u.TileSet.Pos));
			t->flags = e.u.TileSet.Flags;
			t->pic = PicManagerGetNamedPic(
				&gPicManager, e.u.TileSet.PicName);
			t->picAlt = PicManagerGetNamedPic(
				&gPicManager, e.u.TileSet.PicAltName);
		}
		break;
	case GAME_EVENT_MAP_OBJECT_ADD:
		ObjAdd(e.u.MapObjectAdd);
		break;
	case GAME_EVENT_MAP_OBJECT_DAMAGE:
		DamageObject(e.u.MapObjectDamage);
		break;
	case GAME_EVENT_SCORE:
		{
			PlayerData *p = PlayerDataGetByUID(e.u.Score.PlayerUID);
			PlayerScore(p, e.u.Score.Score);
			HUDAddUpdate(
				&camera->HUD,
				NUMBER_UPDATE_SCORE, e.u.Score.PlayerUID, e.u.Score.Score);
		}
		break;
	case GAME_EVENT_SOUND_AT:
		if (!e.u.SoundAt.IsHit || ConfigGetBool(&gConfig, "Sound.Hits"))
		{
			SoundPlayAt(
				&gSoundDevice,
				StrSound(e.u.SoundAt.Sound), Net2Vec2i(e.u.SoundAt.Pos));
		}
		break;
	case GAME_EVENT_SCREEN_SHAKE:
		camera->shake = ScreenShakeAdd(
			camera->shake, e.u.ShakeAmount,
			ConfigGetInt(&gConfig, "Graphics.ShakeMultiplier"));
		break;
	case GAME_EVENT_SET_MESSAGE:
		HUDDisplayMessage(
			&camera->HUD, e.u.SetMessage.Message, e.u.SetMessage.Ticks);
		break;
	case GAME_EVENT_GAME_START:
		gMission.HasStarted = true;
		break;
	case GAME_EVENT_ACTOR_ADD:
		ActorAdd(e.u.ActorAdd);
		break;
	case GAME_EVENT_ACTOR_MOVE:
		ActorMove(e.u.ActorMove);
		break;
	case GAME_EVENT_ACTOR_STATE:
		{
			TActor *a = ActorGetByUID(e.u.ActorState.UID);
			if (!a->isInUse) break;
			ActorSetState(a, (ActorAnimation)e.u.ActorState.State);
		}
		break;
	case GAME_EVENT_ACTOR_DIR:
		{
			TActor *a = ActorGetByUID(e.u.ActorDir.UID);
			if (!a->isInUse) break;
			a->direction = (direction_e)e.u.ActorDir.Dir;
		}
		break;
	case GAME_EVENT_ACTOR_SLIDE:
		{
			TActor *a = ActorGetByUID(e.u.ActorSlide.UID);
			if (!a->isInUse) break;
			a->Vel = Net2Vec2i(e.u.ActorSlide.Vel);
			// Slide sound
			if (ConfigGetBool(&gConfig, "Sound.Footsteps"))
			{
				SoundPlayAt(
					&gSoundDevice,
					gSoundDevice.slideSound,
					Vec2iNew(a->tileItem.x, a->tileItem.y));
			}
		}
		break;
	case GAME_EVENT_ACTOR_IMPULSE:
		{
			TActor *a = ActorGetByUID(e.u.ActorImpulse.UID);
			if (!a->isInUse) break;
			a->Vel = Vec2iAdd(a->Vel, Net2Vec2i(e.u.ActorImpulse.Vel));
			const Vec2i pos = Net2Vec2i(e.u.ActorImpulse.Pos);
			if (!Vec2iIsZero(pos))
			{
				a->Pos = pos;
			}
		}
		break;
	case GAME_EVENT_ACTOR_SWITCH_GUN:
		ActorSwitchGun(e.u.ActorSwitchGun);
		break;
	case GAME_EVENT_ACTOR_PICKUP_ALL:
		{
			TActor *a = ActorGetByUID(e.u.ActorPickupAll.UID);
			if (!a->isInUse) break;
			a->PickupAll = e.u.ActorPickupAll.PickupAll;
		}
		break;
	case GAME_EVENT_ACTOR_REPLACE_GUN:
		ActorReplaceGun(e.u.ActorReplaceGun);
		break;
	case GAME_EVENT_ACTOR_HEAL:
		{
			TActor *a = ActorGetByUID(e.u.Heal.UID);
			if (!a->isInUse || a->dead) break;
			ActorHeal(a, e.u.Heal.Amount);
			// Sound of healing
			SoundPlayAt(
				&gSoundDevice,
				gSoundDevice.healthSound, Vec2iFull2Real(a->Pos));
			// Tell the spawner that we took a health so we can
			// spawn more (but only if we're the server)
			if (e.u.Heal.IsRandomSpawned && !gCampaign.IsClient)
			{
				PowerupSpawnerRemoveOne(healthSpawner);
			}
			if (e.u.Heal.PlayerUID >= 0)
			{
				HUDAddUpdate(
					&camera->HUD, NUMBER_UPDATE_HEALTH,
					e.u.Heal.PlayerUID, e.u.Heal.Amount);
			}
		}
		break;
	case GAME_EVENT_ACTOR_ADD_AMMO:
		{
			TActor *a = ActorGetByUID(e.u.AddAmmo.UID);
			if (!a->isInUse || a->dead) break;
			ActorAddAmmo(a, e.u.AddAmmo.AmmoId, e.u.AddAmmo.Amount);
			// Tell the spawner that we took ammo so we can
			// spawn more (but only if we're the server)
			if (e.u.AddAmmo.IsRandomSpawned && !gCampaign.IsClient)
			{
				PowerupSpawnerRemoveOne(
					CArrayGet(ammoSpawners, e.u.AddAmmo.AmmoId));
			}
			if (e.u.AddAmmo.PlayerUID >= 0)
			{
				HUDAddUpdate(
					&camera->HUD, NUMBER_UPDATE_AMMO,
					e.u.AddAmmo.PlayerUID, e.u.AddAmmo.Amount);
			}
		}
		break;
	case GAME_EVENT_ACTOR_USE_AMMO:
		{
			TActor *a = ActorGetByUID(e.u.UseAmmo.UID);
			if (!a->isInUse || a->dead) break;
			ActorAddAmmo(a, e.u.UseAmmo.AmmoId, -(int)e.u.UseAmmo.Amount);
			if (e.u.UseAmmo.PlayerUID >= 0)
			{
				HUDAddUpdate(
					&camera->HUD, NUMBER_UPDATE_AMMO,
					e.u.UseAmmo.PlayerUID, -(int)e.u.UseAmmo.Amount);
			}
		}
		break;
	case GAME_EVENT_ACTOR_DIE:
		{
			TActor *a = ActorGetByUID(e.u.ActorDie.UID);

			// Check if the player has lives to revive
			PlayerData *p = PlayerDataGetByUID(a->PlayerUID);
			if (p != NULL)
			{
				p->Lives--;
				CASSERT(p->Lives >= 0, "Player has died too many times");
				if (p->Lives > 0 && !gCampaign.IsClient)
				{
					// Find the closest player alive; try to spawn next to that position
					// if no other suitable position exists
					Vec2i defaultSpawnPosition = Vec2iZero();
					const TActor *closestActor = AIGetClosestPlayer(a->Pos);
					if (closestActor != NULL) defaultSpawnPosition = closestActor->Pos;
					PlacePlayer(&gMap, p, defaultSpawnPosition, false);
				}
			}

			ActorDestroy(a);
		}
		break;
	case GAME_EVENT_ACTOR_MELEE:
		{
			const TActor *a = ActorGetByUID(e.u.Melee.UID);
			if (!a->isInUse) break;
			const BulletClass *b = StrBulletClass(e.u.Melee.BulletClass);
			if ((HitType)e.u.Melee.HitType != HIT_NONE &&
				HasHitSound(b->Power, a->flags, a->PlayerUID,
				(TileItemKind)e.u.Melee.TargetKind, e.u.Melee.TargetUID,
				SPECIAL_NONE, false))
			{
				PlayHitSound(
					&b->HitSound, (HitType)e.u.Melee.HitType,
					Vec2iFull2Real(a->Pos));
			}
			if (!gCampaign.IsClient)
			{
				Damage(
					Vec2iZero(),
					b->Power,
					a->flags, a->PlayerUID, a->uid,
					(TileItemKind)e.u.Melee.TargetKind, e.u.Melee.TargetUID,
					SPECIAL_NONE);
			}
		}
		break;
	case GAME_EVENT_ADD_PICKUP:
		PickupAdd(e.u.AddPickup);
		// Play a spawn sound
		SoundPlayAt(
			&gSoundDevice,
			StrSound("spawn_item"), Net2Vec2i(e.u.AddPickup.Pos));
		break;
	case GAME_EVENT_REMOVE_PICKUP:
		PickupDestroy(e.u.RemovePickup.UID);
		if (e.u.RemovePickup.SpawnerUID >= 0)
		{
			TObject *o = ObjGetByUID(e.u.RemovePickup.SpawnerUID);
			o->counter = AMMO_SPAWNER_RESPAWN_TICKS;
		}
		break;
	case GAME_EVENT_BULLET_BOUNCE:
		{
			TMobileObject *o = MobObjGetByUID(e.u.BulletBounce.UID);
			if (o == NULL || !o->isInUse) break;
			const Vec2i pos = Net2Vec2i(e.u.BulletBounce.BouncePos);
			PlayHitSound(
				&o->bulletClass->HitSound, (HitType)e.u.BulletBounce.HitType,
				Vec2iFull2Real(pos));
			if (e.u.BulletBounce.Spark && o->bulletClass->Spark != NULL)
			{
				GameEvent s = GameEventNew(GAME_EVENT_ADD_PARTICLE);
				s.u.AddParticle.Class = o->bulletClass->Spark;
				s.u.AddParticle.FullPos = pos;
				s.u.AddParticle.Z = o->z;
				GameEventsEnqueue(&gGameEvents, s);
			}
			o->x = pos.x;
			o->y = pos.y;
			o->vel = Net2Vec2i(e.u.BulletBounce.BounceVel);
		}
		break;
	case GAME_EVENT_REMOVE_BULLET:
		{
			TMobileObject *o = MobObjGetByUID(e.u.RemoveBullet.UID);
			if (o == NULL || !o->isInUse) break;
			MobObjDestroy(o);
		}
		break;
	case GAME_EVENT_PARTICLE_REMOVE:
		ParticleDestroy(&gParticles, e.u.ParticleRemoveId);
		break;
	case GAME_EVENT_GUN_FIRE:
		{
			const GunDescription *g = StrGunDescription(e.u.GunFire.Gun);
			const Vec2i fullPos = Net2Vec2i(e.u.GunFire.MuzzleFullPos);

			// Add bullets
			if (g->Bullet && !gCampaign.IsClient)
			{
				// Find the starting angle of the spread (clockwise)
				// Keep in mind the fencepost problem, i.e. spread of 3 means a
				// total spread angle of 2x width
				const double spreadStartAngle =
					g->AngleOffset -
					(g->Spread.Count - 1) * g->Spread.Width / 2;
				for (int i = 0; i < g->Spread.Count; i++)
				{
					const double recoil =
						((double)rand() / RAND_MAX * g->Recoil) -
						g->Recoil / 2;
					const double finalAngle =
						e.u.GunFire.Angle + spreadStartAngle +
						i * g->Spread.Width + recoil;
					GameEvent ab = GameEventNew(GAME_EVENT_ADD_BULLET);
					ab.u.AddBullet.UID = MobObjsObjsGetNextUID();
					strcpy(ab.u.AddBullet.BulletClass, g->Bullet->Name);
					ab.u.AddBullet.MuzzlePos = Vec2i2Net(fullPos);
					ab.u.AddBullet.MuzzleHeight = e.u.GunFire.Z;
					ab.u.AddBullet.Angle = (float)finalAngle;
					ab.u.AddBullet.Elevation =
						RAND_INT(g->ElevationLow, g->ElevationHigh);
					ab.u.AddBullet.Flags = e.u.GunFire.Flags;
					ab.u.AddBullet.PlayerUID = e.u.GunFire.PlayerUID;
					ab.u.AddBullet.ActorUID = e.u.GunFire.UID;
					GameEventsEnqueue(&gGameEvents, ab);
				}
			}

			// Add muzzle flash
			if (GunHasMuzzle(g))
			{
				GameEvent ap = GameEventNew(GAME_EVENT_ADD_PARTICLE);
				ap.u.AddParticle.Class = g->MuzzleFlash;
				ap.u.AddParticle.FullPos = fullPos;
				ap.u.AddParticle.Z = e.u.GunFire.Z;
				ap.u.AddParticle.Angle = e.u.GunFire.Angle;
				GameEventsEnqueue(&gGameEvents, ap);
			}
			// Sound
			if (e.u.GunFire.Sound && g->Sound)
			{
				SoundPlayAt(&gSoundDevice, g->Sound, Vec2iFull2Real(fullPos));
			}
			// Screen shake
			if (g->ShakeAmount > 0)
			{
				GameEvent s = GameEventNew(GAME_EVENT_SCREEN_SHAKE);
				s.u.ShakeAmount = g->ShakeAmount;
				GameEventsEnqueue(&gGameEvents, s);
			}
			// Brass shells
			// If we have a reload lead, defer the creation of shells until then
			if (g->Brass && g->ReloadLead == 0)
			{
				const direction_e d = RadiansToDirection(e.u.GunFire.Angle);
				const Vec2i muzzleOffset = GunGetMuzzleOffset(g, d);
				GunAddBrass(g, d, Vec2iMinus(fullPos, muzzleOffset));
			}
		}
		break;
	case GAME_EVENT_GUN_RELOAD:
		{
			const GunDescription *g = StrGunDescription(e.u.GunReload.Gun);
			const Vec2i fullPos = Net2Vec2i(e.u.GunReload.FullPos);
			SoundPlayAtPlusDistance(
				&gSoundDevice,
				g->ReloadSound,
				Vec2iFull2Real(fullPos),
				RELOAD_DISTANCE_PLUS);
			// Brass shells
			if (g->Brass)
			{
				GunAddBrass(g, (direction_e)e.u.GunReload.Direction, fullPos);
			}
		}
		break;
	case GAME_EVENT_GUN_STATE:
		{
			const TActor *a = ActorGetByUID(e.u.GunState.ActorUID);
			if (!a->isInUse) break;
			WeaponSetState(ActorGetGun(a), (gunstate_e)e.u.GunState.State);
		}
		break;
	case GAME_EVENT_ADD_BULLET:
		BulletAdd(e.u.AddBullet);
		break;
	case GAME_EVENT_ADD_PARTICLE:
		ParticleAdd(&gParticles, e.u.AddParticle);
		break;
	case GAME_EVENT_ACTOR_HIT:
		{
			TActor *a = ActorGetByUID(e.u.ActorHit.UID);
			if (!a->isInUse) break;
			ActorTakeHit(a, e.u.ActorHit.Special);
			if (e.u.ActorHit.Power > 0)
			{
				DamageActor(
					a, e.u.ActorHit.Power, e.u.ActorHit.HitterPlayerUID);
				if (e.u.ActorHit.PlayerUID >= 0)
				{
					HUDAddUpdate(
						&camera->HUD, NUMBER_UPDATE_HEALTH,
						e.u.ActorHit.PlayerUID, -e.u.ActorHit.Power);
				}

				AddBloodSplatter(
					a->Pos, e.u.ActorHit.Power,
					Net2Vec2i(e.u.ActorHit.Vel));
			}
		}
		break;
	case GAME_EVENT_TRIGGER:
		{
			const Tile *t =
				MapGetTile(&gMap, Net2Vec2i(e.u.TriggerEvent.Tile));
			CA_FOREACH(Trigger *, tp, t->triggers)
				if ((*tp)->id == (int)e.u.TriggerEvent.ID)
				{
					TriggerActivate(*tp, &gMap.triggers);
					break;
				}
			CA_FOREACH_END()
		}
		break;
	case GAME_EVENT_EXPLORE_TILES:
		// Process runs of explored tiles
		for (int i = 0; i < (int)e.u.ExploreTiles.Runs_count; i++)
		{
			Vec2i tile = Net2Vec2i(e.u.ExploreTiles.Runs[i].Tile);
			for (int j = 0; j < e.u.ExploreTiles.Runs[i].Run; j++)
			{
				MapMarkAsVisited(&gMap, tile);
				tile.x++;
				if (tile.x == gMap.Size.x)
				{
					tile.x = 0;
					tile.y++;
				}
			}
		}
		break;
	case GAME_EVENT_RESCUE_CHARACTER:
		{
			TActor *a = ActorGetByUID(e.u.Rescue.UID);
			if (!a->isInUse) break;
			a->flags &= ~FLAGS_PRISONER;
			SoundPlayAt(
				&gSoundDevice, StrSound("rescue"), Vec2iFull2Real(a->Pos));
		}
		break;
	case GAME_EVENT_OBJECTIVE_UPDATE:
		{
			ObjectiveDef *o = CArrayGet(
				&gMission.Objectives, e.u.ObjectiveUpdate.ObjectiveId);
			o->done += e.u.ObjectiveUpdate.Count;
			// Display a text update effect for the objective
			HUDAddUpdate(
				&camera->HUD, NUMBER_UPDATE_OBJECTIVE,
				e.u.ObjectiveUpdate.ObjectiveId, e.u.ObjectiveUpdate.Count);
			MissionSetMessageIfComplete(&gMission);
		}
		break;
	case GAME_EVENT_ADD_KEYS:
		gMission.KeyFlags |= e.u.AddKeys.KeyFlags;
		SoundPlayAt(
			&gSoundDevice, gSoundDevice.keySound, Net2Vec2i(e.u.AddKeys.Pos));
		// Clear cache since we may now have new paths
		PathCacheClear(&gPathCache);
		break;
	case GAME_EVENT_MISSION_COMPLETE:
		if (e.u.MissionComplete.ShowMsg)
		{
			HUDDisplayMessage(&camera->HUD, "Mission complete", -1);
		}
		camera->HUD.showExit = true;
		MapShowExitArea(&gMap);
		break;
	case GAME_EVENT_MISSION_INCOMPLETE:
		gMission.state = MISSION_STATE_PLAY;
		break;
	case GAME_EVENT_MISSION_PICKUP:
		gMission.state = MISSION_STATE_PICKUP;
		gMission.pickupTime = gMission.time;
		SoundPlay(&gSoundDevice, StrSound("whistle"));
		break;
	case GAME_EVENT_MISSION_END:
		gMission.isDone = true;
		break;
	default:
		assert(0 && "unknown game event");
		break;
	}
}