/***************************************************************************** * Copyright (c) 2014-2020 OpenRCT2 developers * * For a complete list of all authors, please refer to contributors.md * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 * * OpenRCT2 is licensed under the GNU General Public License version 3. *****************************************************************************/ #include "StaffHireNewAction.h" #include "../Cheats.h" #include "../Context.h" #include "../core/MemoryStream.h" #include "../drawing/Drawing.h" #include "../interface/Window.h" #include "../localisation/Localisation.h" #include "../localisation/StringIds.h" #include "../management/Finance.h" #include "../peep/Staff.h" #include "../ride/Ride.h" #include "../scenario/Scenario.h" #include "../ui/UiContext.h" #include "../ui/WindowManager.h" #include "../world/Entrance.h" #include "../world/Park.h" #include "../world/Sprite.h" /* rct2: 0x009929FC */ static constexpr const PeepSpriteType spriteTypes[] = { PeepSpriteType::Handyman, PeepSpriteType::Mechanic, PeepSpriteType::Security, PeepSpriteType::EntertainerPanda, }; StaffHireNewAction::StaffHireNewAction( bool autoPosition, StaffType staffType, EntertainerCostume entertainerType, uint32_t staffOrders) : _autoPosition(autoPosition) , _staffType(static_cast(staffType)) , _entertainerType(entertainerType) , _staffOrders(staffOrders) { } void StaffHireNewAction::AcceptParameters(GameActionParameterVisitor& visitor) { visitor.Visit("autoPosition", _autoPosition); visitor.Visit("staffType", _staffType); visitor.Visit("entertainerType", _entertainerType); visitor.Visit("staffOrders", _staffOrders); } uint16_t StaffHireNewAction::GetActionFlags() const { return GameAction::GetActionFlags() | GameActions::Flags::AllowWhilePaused; } void StaffHireNewAction::Serialise(DataSerialiser& stream) { GameAction::Serialise(stream); stream << DS_TAG(_autoPosition) << DS_TAG(_staffType) << DS_TAG(_entertainerType) << DS_TAG(_staffOrders); } GameActions::Result::Ptr StaffHireNewAction::Query() const { return QueryExecute(false); } GameActions::Result::Ptr StaffHireNewAction::Execute() const { return QueryExecute(true); } GameActions::Result::Ptr StaffHireNewAction::QueryExecute(bool execute) const { auto res = MakeResult(); res->Expenditure = ExpenditureType::Wages; if (_staffType >= static_cast(StaffType::Count)) { // Invalid staff type. log_error("Tried to use invalid staff type: %u", static_cast(_staffType)); return MakeResult(GameActions::Status::InvalidParameters, STR_CANT_HIRE_NEW_STAFF, STR_NONE); } if (GetNumFreeEntities() < 400) { return MakeResult(GameActions::Status::NoFreeElements, STR_CANT_HIRE_NEW_STAFF, STR_TOO_MANY_PEOPLE_IN_GAME); } if (_staffType == static_cast(StaffType::Entertainer)) { if (static_cast(_entertainerType) >= static_cast(EntertainerCostume::Count)) { // Invalid entertainer costume log_error("Tried to use invalid entertainer type: %u", static_cast(_entertainerType)); return MakeResult(GameActions::Status::InvalidParameters, STR_CANT_HIRE_NEW_STAFF, STR_NONE); } uint32_t availableCostumes = staff_get_available_entertainer_costumes(); if (!(availableCostumes & (1 << static_cast(_entertainerType)))) { // Entertainer costume unavailable log_error("Tried to use unavailable entertainer type: %u", static_cast(_entertainerType)); return MakeResult(GameActions::Status::InvalidParameters, STR_CANT_HIRE_NEW_STAFF, STR_NONE); } } Staff* newPeep = CreateEntity(); if (newPeep == nullptr) { // Too many peeps exist already. return MakeResult(GameActions::Status::NoFreeElements, STR_CANT_HIRE_NEW_STAFF, STR_TOO_MANY_PEOPLE_IN_GAME); } if (execute == false) { // In query we just want to see if we can obtain a sprite slot. sprite_remove(newPeep); res->SetData(StaffHireNewActionResult{ SPRITE_INDEX_NULL }); } else { newPeep->WindowInvalidateFlags = 0; newPeep->Action = PeepActionType::Walking; newPeep->SpecialSprite = 0; newPeep->ActionSpriteImageOffset = 0; newPeep->WalkingFrameNum = 0; newPeep->ActionSpriteType = PeepActionSpriteType::None; newPeep->PathCheckOptimisation = 0; newPeep->PeepFlags = 0; newPeep->StaffLawnsMown = 0; newPeep->StaffGardensWatered = 0; newPeep->StaffLitterSwept = 0; newPeep->StaffBinsEmptied = 0; newPeep->StaffOrders = _staffOrders; // We search for the first available Id for a given staff type uint32_t newStaffId = 0; for (;;) { bool found = false; ++newStaffId; for (auto searchPeep : EntityList()) { if (static_cast(searchPeep->AssignedStaffType) != _staffType) continue; if (searchPeep->Id == newStaffId) { found = true; break; } } if (!found) break; } newPeep->Id = newStaffId; newPeep->AssignedStaffType = static_cast(_staffType); PeepSpriteType spriteType = spriteTypes[_staffType]; if (_staffType == static_cast(StaffType::Entertainer)) { spriteType = EntertainerCostumeToSprite(_entertainerType); } newPeep->Name = nullptr; newPeep->SpriteType = spriteType; const rct_sprite_bounds* spriteBounds = &GetSpriteBounds(spriteType); newPeep->sprite_width = spriteBounds->sprite_width; newPeep->sprite_height_negative = spriteBounds->sprite_height_negative; newPeep->sprite_height_positive = spriteBounds->sprite_height_positive; if (_autoPosition) { AutoPositionNewStaff(newPeep); } else { // NOTE: This state is required for the window to act. newPeep->State = PeepState::Picked; // INVESTIGATE: x and y are LOCATION_NULL at this point. newPeep->MoveTo(newPeep->GetLocation()); } // Staff uses this newPeep->As()->SetHireDate(gDateMonthsElapsed); newPeep->PathfindGoal.x = 0xFF; newPeep->PathfindGoal.y = 0xFF; newPeep->PathfindGoal.z = 0xFF; newPeep->PathfindGoal.direction = INVALID_DIRECTION; uint8_t colour = staff_get_colour(static_cast(_staffType)); newPeep->TshirtColour = colour; newPeep->TrousersColour = colour; // Staff energy determines their walking speed newPeep->Energy = 0x60; newPeep->EnergyTarget = 0x60; newPeep->StaffMowingTimeout = 0; newPeep->PatrolInfo = nullptr; res->SetData(StaffHireNewActionResult{ newPeep->sprite_index }); } return res; } void StaffHireNewAction::AutoPositionNewStaff(Peep* newPeep) const { // Find a location to place new staff member newPeep->State = PeepState::Falling; uint32_t count = 0; PathElement* guest_tile = nullptr; // Count number of walking guests { for (auto guest : EntityList()) { if (guest->State == PeepState::Walking) { // Check the walking guest's tile. Only count them if they're on a path tile. guest_tile = map_get_path_element_at(TileCoordsXYZ{ guest->NextLoc }); if (guest_tile != nullptr) ++count; } } } CoordsXYZ newLocation{}; if (count > 0) { // Place staff at a random guest uint32_t rand = scenario_rand_max(count); Guest* chosenGuest = nullptr; for (auto guest : EntityList()) { if (guest->State == PeepState::Walking) { guest_tile = map_get_path_element_at(TileCoordsXYZ{ guest->NextLoc }); if (guest_tile != nullptr) { if (rand == 0) { chosenGuest = guest; break; } --rand; } } } if (chosenGuest != nullptr) { newLocation = chosenGuest->GetLocation(); } else { // User must pick a location newPeep->State = PeepState::Picked; newLocation = newPeep->GetLocation(); } } else { // No walking guests; pick random park entrance if (!gParkEntrances.empty()) { auto rand = scenario_rand_max(static_cast(gParkEntrances.size())); const auto& entrance = gParkEntrances[rand]; auto dir = entrance.direction; newLocation = entrance; // TODO: Replace with CoordsDirectionDelta newLocation.x += 16 + ((dir & 1) == 0 ? ((dir & 2) ? 32 : -32) : 0); newLocation.y += 16 + ((dir & 1) == 1 ? ((dir & 2) ? -32 : 32) : 0); } else { // User must pick a location newPeep->State = PeepState::Picked; newLocation = newPeep->GetLocation(); } } newPeep->MoveTo(newLocation + CoordsXYZ{ 0, 0, 16 }); }