OpenRCT2/src/openrct2/actions/StaffHireNewAction.cpp

299 lines
9.5 KiB
C++

/*****************************************************************************
* 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<uint8_t>(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<uint8_t>(StaffType::Count))
{
// Invalid staff type.
log_error("Tried to use invalid staff type: %u", static_cast<uint32_t>(_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<uint8_t>(StaffType::Entertainer))
{
if (static_cast<uint8_t>(_entertainerType) >= static_cast<uint8_t>(EntertainerCostume::Count))
{
// Invalid entertainer costume
log_error("Tried to use invalid entertainer type: %u", static_cast<uint32_t>(_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<uint8_t>(_entertainerType))))
{
// Entertainer costume unavailable
log_error("Tried to use unavailable entertainer type: %u", static_cast<uint32_t>(_entertainerType));
return MakeResult(GameActions::Status::InvalidParameters, STR_CANT_HIRE_NEW_STAFF, STR_NONE);
}
}
Staff* newPeep = CreateEntity<Staff>();
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<Staff>())
{
if (static_cast<uint8_t>(searchPeep->AssignedStaffType) != _staffType)
continue;
if (searchPeep->Id == newStaffId)
{
found = true;
break;
}
}
if (!found)
break;
}
newPeep->Id = newStaffId;
newPeep->AssignedStaffType = static_cast<StaffType>(_staffType);
PeepSpriteType spriteType = spriteTypes[_staffType];
if (_staffType == static_cast<uint8_t>(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<Staff>()->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>(_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<Guest>())
{
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<Guest>())
{
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<uint32_t>(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 });
}