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(), ""}); } } }
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; } } } }
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"); } } }
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); } } }
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; }
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); } } } } }