mirror of https://github.com/OpenRCT2/OpenRCT2.git
299 lines
9.5 KiB
C++
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 });
|
|
}
|