示例#1
0
void Lab::update(unsigned int ticks, StateRef<Lab> lab, sp<GameState> state)
{
	if (lab->current_project && lab->getTotalSkill() > 0)
	{
		// A little complication as we want to be correctly calculating progress in an integer when
		// working with sub-single progress 'unit' time units.
		// This also leaves any remaining ticks in the lab's ticks_since_last_progress, so they will
		// get added onto the next project that lab undertakes at the first update.
		unsigned ticks_per_progress_point = TICKS_PER_HOUR / lab->getTotalSkill();
		unsigned ticks_remaining_to_progress = ticks + lab->ticks_since_last_progress;

		unsigned progress_points =
		    std::min(ticks_remaining_to_progress / ticks_per_progress_point,
		             lab->current_project->man_hours - lab->current_project->man_hours_progress);
		unsigned ticks_left =
		    ticks_remaining_to_progress - progress_points * ticks_per_progress_point;

		lab->ticks_since_last_progress = ticks_left;

		lab->current_project->man_hours_progress += progress_points;
		if (lab->current_project->isComplete())
		{
			// FIXME: Show 'research complete' screen
			LogWarning("Completed research %s", lab->current_project->name.c_str());
			sp<Facility> lab_facility;

			for (auto &base : state->player_bases)
			{
				for (auto &facility : base.second->facilities)
				{
					if (facility->lab == lab)
					{
						lab_facility = facility;
						break;
					}
				}
				if (lab_facility)
					break;
			}
			if (!lab_facility)
			{
				LogError("No facility owns the current lab");
			}

			auto complete_data = mksp<ResearchCompleteData>();
			complete_data->topic = lab->current_project;
			complete_data->lab = lab;
			auto event = new UserEvent("RESEARCH_COMPLETE", complete_data);
			fw().PushEvent(event);
			Lab::setResearch(lab, {state.get(), ""});
		}
	}
}
示例#2
0
void BattleHazard::applyEffect(GameState &state)
{
	auto tile = tileObject->getOwningTile();

	auto set = tile->ownedObjects;
	for (auto obj : set)
	{
		if (tile->ownedObjects.find(obj) == tile->ownedObjects.end())
		{
			continue;
		}
		if (obj->getType() == TileObject::Type::Ground ||
		    obj->getType() == TileObject::Type::Feature ||
		    obj->getType() == TileObject::Type::LeftWall ||
		    obj->getType() == TileObject::Type::RightWall)
		{
			auto mp = std::static_pointer_cast<TileObjectBattleMapPart>(obj)->getOwner();
			switch (damageType->effectType)
			{
				case DamageType::EffectType::Fire:
					LogWarning("Set map part on fire!");
					break;
				default:
					mp->applyDamage(state, power, damageType);
					break;
			}
		}
		else if (obj->getType() == TileObject::Type::Unit)
		{
			StateRef<BattleUnit> u = {
			    &state, std::static_pointer_cast<TileObjectBattleUnit>(obj)->getUnit()->id};
			switch (damageType->effectType)
			{
				case DamageType::EffectType::Fire:
					LogWarning("Set unit on fire!");
					break;
				default:
				{
					// Determine direction of hit
					Vec3<float> velocity = -position;
					velocity -= Vec3<float>{0.5f, 0.5f, 0.5f};
					velocity += u->position;
					if (velocity.x == 0.0f && velocity.y == 0.0f)
					{
						velocity.z = 1.0f;
					}
					// Determine wether to hit head, legs or torso
					auto cposition = u->position;
					// Hit torso
					if (sqrtf(velocity.x * velocity.x + velocity.y * velocity.y) >
					    std::abs(velocity.z))
					{
						cposition.z += (float)u->getCurrentHeight() / 2.0f / 40.0f;
					}
					// Hit head
					else if (velocity.z < 0)
					{
						cposition.z += (float)u->getCurrentHeight() / 40.0f;
					}
					else
					{
						// Legs are defeault already
					}
					// Apply
					u->applyDamage(state, power, damageType,
					               u->determineBodyPartHit(damageType, cposition, velocity));
				}
				break;
			}
		}
	}
}
示例#3
0
void Lab::update(unsigned int ticks, StateRef<Lab> lab, sp<GameState> state)
{
	if (lab->current_project)
	{
		auto skill = lab->getTotalSkill();
		if (skill <= 0)
		{
			// If there's nobody assigned projects obviously don't progress
			return;
		}
		// A little complication as we want to be correctly calculating progress in an integer when
		// working with sub-single progress 'unit' time units.
		// This also leaves any remaining ticks in the lab's ticks_since_last_progress, so they will
		// get added onto the next project that lab undertakes at the first update.
		unsigned ticks_per_progress_hour = TICKS_PER_HOUR / skill;
		unsigned ticks_remaining_to_progress = ticks + lab->ticks_since_last_progress;

		unsigned progress_hours = 0;

		switch (lab->current_project->type)
		{
			case ResearchTopic::Type::Physics:
			case ResearchTopic::Type::BioChem:
				progress_hours = std::min(ticks_remaining_to_progress / ticks_per_progress_hour,
				                          lab->current_project->man_hours -
				                              lab->current_project->man_hours_progress);
				break;
			case ResearchTopic::Type::Engineering:
				progress_hours = std::min(ticks_remaining_to_progress / ticks_per_progress_hour,
				                          lab->current_project->man_hours * lab->getQuantity() -
				                              lab->manufacture_man_hours_invested);
				break;
			default:
				LogError("Unexpected lab type");
		}

		unsigned ticks_left =
		    ticks_remaining_to_progress - progress_hours * ticks_per_progress_hour;
		lab->ticks_since_last_progress = ticks_left;

		switch (lab->current_project->type)
		{
			case ResearchTopic::Type::Physics:
			case ResearchTopic::Type::BioChem:
				lab->current_project->man_hours_progress += progress_hours;
				if (lab->current_project->isComplete())
				{
					// Produce a research remains item
					StateRef<Base> thisBase;
					for (auto &base : state->player_bases)
					{
						for (auto &facility : base.second->facilities)
						{
							if (facility->lab == lab)
							{
								thisBase = {state.get(), base.first};
								break;
							}
						}
						if (thisBase)
						{
							break;
						}
					}
					lab->current_project->dependencies.items.produceRemains(thisBase);

					auto event = new GameResearchEvent(GameEventType::ResearchCompleted,
					                                   lab->current_project, lab);
					fw().pushEvent(event);
					Lab::setResearch(lab, {state.get(), ""}, state);
				}
				break;
			case ResearchTopic::Type::Engineering:
				lab->manufacture_man_hours_invested += progress_hours;
				if (lab->manufacture_man_hours_invested >= lab->current_project->man_hours)
				{
					// Add item to base
					bool found = false;
					UString item_name;
					for (auto &base : state->player_bases)
					{
						for (auto &facility : base.second->facilities)
						{
							if (facility->type->capacityType == FacilityType::Capacity::Workshop &&
							    facility->lab == lab)
							{
								switch (lab->current_project->item_type)
								{
									case ResearchTopic::ItemType::VehicleEquipment:
									{
										base.second->inventoryVehicleEquipment[lab->current_project
										                                           ->itemId] =
										    base.second->inventoryVehicleEquipment
										        [lab->current_project->itemId] +
										    1;
									}
									break;
									case ResearchTopic::ItemType::VehicleEquipmentAmmo:
									{
										base.second
										    ->inventoryVehicleAmmo[lab->current_project->itemId] =
										    base.second->inventoryVehicleAmmo[lab->current_project
										                                          ->itemId] +
										    1;
									}
									break;
									case ResearchTopic::ItemType::AgentEquipment:
									{
										// Apparently if we ++ it doesn't work on new entries
										// properly
										base.second->inventoryAgentEquipment[lab->current_project
										                                         ->itemId] =
										    base.second->inventoryAgentEquipment
										        [lab->current_project->itemId] +
										    1;
									}
									break;
									case ResearchTopic::ItemType::Craft:
									{
										auto type =
										    state->vehicle_types[lab->current_project->itemId];

										auto v = base.second->building->city->placeVehicle(
										    *state, {state.get(), type}, state->getPlayer(),
										    base.second->building);
										v->homeBuilding = {state.get(), base.second->building};
									}
									break;
								}
								found = true;
							}
							if (found)
								break;
						}
						if (found)
							break;
					}

					lab->manufacture_done++;

					if (lab->manufacture_done >= lab->manufacture_goal)
					{
						auto event = new GameManufactureEvent(
						    GameEventType::ManufactureCompleted, lab->current_project,
						    lab->manufacture_done, lab->manufacture_goal, lab);
						fw().pushEvent(event);
						Lab::setResearch(lab, {state.get(), ""}, state);
					}
					else
					{
						if (state->player->balance >= lab->current_project->cost)
						{
							state->player->balance -= lab->current_project->cost;
							lab->manufacture_man_hours_invested -= lab->current_project->man_hours;
						}
						else
						{
							auto event = new GameManufactureEvent(
							    GameEventType::ManufactureHalted, lab->current_project,
							    lab->manufacture_done, lab->manufacture_goal, lab);
							fw().pushEvent(event);
							Lab::setResearch(lab, {state.get(), ""}, state);
						}
					}
				}
				break;
			default:
				LogError("Unexpected lab type");
		}
	}
}
示例#4
0
void BattleHazard::applyEffect(GameState &state)
{
	auto tile = tileObject->getOwningTile();

	auto set = tile->ownedObjects;
	for (auto &obj : set)
	{
		if (tile->ownedObjects.find(obj) == tile->ownedObjects.end())
		{
			continue;
		}
		if (obj->getType() == TileObject::Type::Ground ||
		    obj->getType() == TileObject::Type::Feature ||
		    obj->getType() == TileObject::Type::LeftWall ||
		    obj->getType() == TileObject::Type::RightWall)
		{
			auto mp = std::static_pointer_cast<TileObjectBattleMapPart>(obj)->getOwner();
			switch (damageType->effectType)
			{
				case DamageType::EffectType::Fire:
					if (mp->applyBurning(state, age))
					{
						// Map part burned and provided fuel for our fire, keep the fire raging
						if (power < 0 && age > 10)
						{
							power = -power;
						}
					}
					break;
				default:
					switch (damageType->blockType)
					{
						case DamageType::BlockType::Gas:
						case DamageType::BlockType::Psionic:
							break;
						default:
							mp->applyDamage(state, power, damageType);
							break;
					}
					break;
			}
		}
		else if (obj->getType() == TileObject::Type::Item)
		{
			if (damageType->effectType == DamageType::EffectType::Fire)
			{
				// It was observed that armor resists fire damage deal to it
				// It also appears that damage is applied gradually at a rate of around 1 damage per
				// second
				// In tests, marsec armor (20% modifier) was hurt by fire but X-Com armor (10%
				// modifier) was not
				// If we apply damage once per turn, we apply 4 at once. Since we round down, 4 *
				// 20% will be rounded to 0
				// while it should be 1. So we add 1 here
				auto i = std::static_pointer_cast<TileObjectBattleItem>(obj)->getItem();
				i->applyDamage(state, 2 * TICKS_PER_HAZARD_UPDATE / TICKS_PER_SECOND + 1,
				               damageType);
			}
		}
		else if (obj->getType() == TileObject::Type::Unit)
		{
			StateRef<BattleUnit> u = {
			    &state, std::static_pointer_cast<TileObjectBattleUnit>(obj)->getUnit()->id};
			// Determine direction of hit
			Vec3<float> velocity = -position;
			velocity -= Vec3<float>{0.5f, 0.5f, 0.5f};
			velocity += u->position;
			if (velocity.x == 0.0f && velocity.y == 0.0f)
			{
				velocity.z = 1.0f;
			}
			// Determine wether to hit head, legs or torso
			auto cposition = u->position;
			// Hit torso
			if (sqrtf(velocity.x * velocity.x + velocity.y * velocity.y) > std::abs(velocity.z))
			{
				cposition.z += (float)u->getCurrentHeight() / 2.0f / 40.0f;
			}
			// Hit head
			else if (velocity.z < 0)
			{
				cposition.z += (float)u->getCurrentHeight() / 40.0f;
			}
			else
			{
				// Legs are defeault already
			}
			// Apply
			u->applyDamage(state, power, damageType,
			               u->determineBodyPartHit(damageType, cposition, velocity),
			               DamageSource::Hazard);
		}
	}
}
示例#5
0
bool VEquipment::fire(GameState &state, Vec3<float> targetPosition, Vec3<float> homingPosition,
                      StateRef<Vehicle> targetVehicle, bool manual)
{
	static const std::map<VEquipment::WeaponState, UString> WeaponStateMap = {
	    {WeaponState::Ready, "ready"},
	    {WeaponState::Disabled, "disabled"},
	    {WeaponState::Reloading, "reloading"},
	    {WeaponState::OutOfAmmo, "outofammo"},
	};

	auto muzzle = owner->getMuzzleLocation();
	if (!state.current_city->map->tileIsValid(muzzle))
	{
		return false;
	}
	if (this->type->type != EquipmentSlotType::VehicleWeapon)
	{
		LogError("fire() called on non-Weapon");
		return false;
	}
	auto vehicleTile = owner->tileObject;
	if (!vehicleTile)
	{
		LogError("Called on vehicle with no tile object?");
		return false;
	}
	if (this->weaponState != WeaponState::Ready)
	{
		UString stateName = "UNKNOWN";
		const auto it = WeaponStateMap.find(this->weaponState);
		if (it != WeaponStateMap.end())
			stateName = it->second;
		LogWarning("Trying to fire weapon in state %s", stateName);
		return false;
	}
	if (this->ammo <= 0 && this->type->max_ammo != 0)
	{
		LogWarning("Trying to fire weapon with no ammo");
		return false;
	}
	this->reloadTime = type->fire_delay;
	this->weaponState = WeaponState::Reloading;
	if (this->type->max_ammo != 0)
	{
		this->ammo--;
	}

	if (type->fire_sfx)
	{
		fw().soundBackend->playSample(type->fire_sfx, vehicleTile->getPosition());
	}

	if (this->ammo == 0 && this->type->max_ammo != 0)
	{
		this->weaponState = WeaponState::OutOfAmmo;
	}
	auto vehicleMuzzle = owner->getMuzzleLocation();

	auto fromScaled = vehicleMuzzle * VELOCITY_SCALE_CITY;
	auto toScaled = targetPosition * VELOCITY_SCALE_CITY;
	City::accuracyAlgorithmCity(state, fromScaled, toScaled, type->accuracy + owner->getAccuracy(),
	                            targetVehicle && targetVehicle->isCloaked());

	Vec3<float> velocity = toScaled - fromScaled;
	velocity = glm::normalize(velocity);
	// I believe this is the correct formula
	velocity *= type->speed * PROJECTILE_VELOCITY_MULTIPLIER;

	auto projectile = mksp<Projectile>(
	    type->guided ? Projectile::Type::Missile : Projectile::Type::Beam, owner, targetVehicle,
	    homingPosition, muzzle, velocity, type->turn_rate, type->ttl, type->damage, /*delay*/ 0,
	    /*depletion rate*/ 0, type->tail_size, type->projectile_sprites, type->impact_sfx,
	    type->explosion_graphic, state.city_common_image_list->projectileVoxelMap, type->stunTicks,
	    type->splitIntoTypes, manual);
	owner->tileObject->map.addObjectToMap(projectile);
	owner->city->projectiles.insert(projectile);

	return true;
}
示例#6
0
void BattleExplosion::damage(GameState &state, const TileMap &map, Vec3<int> pos, int damage)
{
	auto tile = map.getTile(pos);
	// Explosions with no hazard spawn smoke with half ttl
	if (!damageType->hazardType)
	{
		StateRef<DamageType> dtSmoke = {&state, "DAMAGETYPE_SMOKE"};
		state.current_battle->placeHazard(state, ownerOrganisation, ownerUnit, dtSmoke, pos,
		                                  dtSmoke->hazardType->getLifetime(state), damage, 2,
		                                  false);
	}
	// Explosions with no custom explosion doodad spawn hazards when dealing damage
	else if (!damageType->explosionDoodad)
	{
		state.current_battle->placeHazard(state, ownerOrganisation, ownerUnit, damageType, pos,
		                                  damageType->hazardType->getLifetime(state), damage, 1,
		                                  false);
	}
	// Gas does no direct damage
	if (damageType->doesImpactDamage())
	{
		auto set = tile->ownedObjects;
		for (auto &obj : set)
		{
			if (tile->ownedObjects.find(obj) == tile->ownedObjects.end())
			{
				continue;
			}
			if (obj->getType() == TileObject::Type::Ground ||
			    obj->getType() == TileObject::Type::Feature ||
			    obj->getType() == TileObject::Type::LeftWall ||
			    obj->getType() == TileObject::Type::RightWall)
			{
				auto mp = std::static_pointer_cast<TileObjectBattleMapPart>(obj)->getOwner();
				switch (damageType->effectType)
				{
					case DamageType::EffectType::Fire:
						// Nothing, map parts are not damaged by fire at explosion time
						break;
					default:
						mp->applyDamage(state, damage, damageType);
						break;
				}
			}
			else if (obj->getType() == TileObject::Type::Unit)
			{
				StateRef<BattleUnit> u = {
				    &state, std::static_pointer_cast<TileObjectBattleUnit>(obj)->getUnit()->id};
				if (affectedUnits.find(u) != affectedUnits.end())
				{
					continue;
				}
				affectedUnits.insert(u);
				// Determine direction of hit
				Vec3<float> velocity = -position;
				velocity -= Vec3<float>{0.5f, 0.5f, 0.5f};
				velocity += u->position;
				if (velocity.x == 0.0f && velocity.y == 0.0f)
				{
					velocity.z = 1.0f;
				}
				// Determine wether to hit head, legs or torso
				auto cposition = u->position;
				// Hit torso if coming from the side, not from above or below
				if (sqrtf(velocity.x * velocity.x + velocity.y * velocity.y) > std::abs(velocity.z))
				{
					cposition.z += (float)u->getCurrentHeight() / 2.0f / 40.0f;
				}
				// Hit head if coming from above
				else if (velocity.z < 0)
				{
					cposition.z += (float)u->getCurrentHeight() / 40.0f;
				}
				// Hit legs if coming from below
				else
				{
					// Legs are defeault already
				}
				// Apply
				u->applyDamage(state, damage, damageType,
				               u->determineBodyPartHit(damageType, cposition, velocity),
				               DamageSource::Impact, ownerUnit);
			}
			else if (obj->getType() == TileObject::Type::Item)
			{
				// Special effects do not damage items, fire damages items differently and not on
				// explosion impact
				if (damageType->effectType == DamageType::EffectType::None)
				{
					auto i = std::static_pointer_cast<TileObjectBattleItem>(obj)->getItem();
					i->applyDamage(state, damage, damageType);
				}
			}
		}
	}
}