Files
shweets-sim/src/items.cpp
T

318 lines
12 KiB
C++

#include "play.h"
constexpr float HOLD_SPIN = 0.1f;
constexpr float ROTATION_GROUND_DAMPING = 0.85f;
constexpr float ROTATION_MAX = 90;
constexpr float ROTATION_RETURN_SPEED = 120;
constexpr float THROW_THRESHOLD = 10;
void Items::add(Play& play, int id, Vec2 position, Vec2 velocity, float rotation)
{
ItemEntity& entity = list.emplace_back(ItemEntity{play.character->item_actor_get(id), velocity, 0, id});
entity.actor.position = position;
entity.actor.rotation = rotation;
discovered[id] = true;
}
// Forgets which item is being eaten, and lets go of the held one unless asked to keep it.
void Items::drop(Hold hold)
{
if (hold == HOLD_RELEASE)
{
held = -1;
feeding.animation = -1;
}
feeding.item = -1;
feeding.event = -1;
feeding.rect = {NAN, NAN, NAN, NAN};
}
int Items::spawn_delay_get(Play& play)
{
Catalog& catalog = play.character->catalog;
return std::max(1, (int)std::round(catalog.spawnDelay * std::pow(catalog.spawnDelayMultiplier, play.body.stage)));
}
static float spawn_weight_get(Play& play, int id)
{
Catalog& catalog = play.character->catalog;
Item& item = catalog.items[id];
if (item.calories > play.body.capacity_max_get() || item.rarity < 0)
return 0;
return std::max(0.0f, catalog.rarities[item.rarity].weight);
}
bool Items::is_spawn_possible(Play& play)
{
Catalog& catalog = play.character->catalog;
if ((int)list.size() >= catalog.spawnLimit)
return false;
for (int i = 0; i < (int)catalog.items.size(); i++)
if (spawn_weight_get(play, i) > 0)
return true;
return false;
}
void Items::spawn_queue_add(Play& play, int id)
{
Catalog& catalog = play.character->catalog;
if (id < 0 || id >= (int)catalog.items.size())
return;
play.audio->play(play.character->sounds[SOUND_SUMMON]);
if (catalog.items[id].rarity >= 0)
play.audio->play(catalog.rarities[catalog.items[id].rarity].sound);
spawnQueue.push_back(id);
spawnDelay = spawn_delay_get(play);
}
// Queues an item picked at random by rarity, once the spawn delay is up.
void Items::spawn_queue_add(Play& play)
{
if (spawnDelay > 0 || !is_spawn_possible(play))
return;
std::vector<Item>& items = play.character->catalog.items;
float total = 0;
for (int i = 0; i < (int)items.size(); i++)
total += spawn_weight_get(play, i);
float roll = random_get(0, total);
for (int i = 0; i < (int)items.size(); i++)
if (float weight = spawn_weight_get(play, i); weight > 0 && (roll -= weight) < 0)
return spawn_queue_add(play, i);
}
void Items::update(Play& play, Pointer pointer)
{
Character& character = *play.character;
Body& body = play.body;
Stats& stats = body.stats;
Actor& active = play.pointer_actor_get();
Audio& audio = *play.audio;
Area& area = character.area;
Rect bounds = play.bounds;
bool isOver = body.is_over_capacity();
auto text_play = [&](Hook hook) {
if (!pointer.isAutomated)
play.text.set(play, character.dialogue.hook_get(hook));
};
auto release = [&](bool isThrown) {
drop();
active.play(CURSOR_ANIMATION_IDLE);
if (!isThrown && !body.actor.pending)
body.idle_play();
};
if (play.text.isScene && held >= 0)
{
release(false);
feeding.isTextPending = false;
}
if (feeding.isTextPending && feeding.animation == body.actor.animation &&
(!body.actor.isPlaying || body.actor.is_finished()))
{
play.text.set(play, character.dialogue.hook_get(feeding.isTextFull ? HOOK_EAT_FULL : HOOK_EAT));
feeding.isTextPending = false;
feeding.animation = -1;
}
bool wasHovered = isHovered;
isHovered = false;
tooltip = -1;
if (spawnDelay > 0)
spawnDelay--;
for (int id : spawnQueue)
{
Rect spawn = area.anm2 ? area.spawn : bounds;
Vec2 position = {spawn.x + random_get(0, spawn.w), spawn.y + random_get(0, spawn.h)};
if (!discovered[id])
{
play.toast_add("New item found!");
audio.play(character.sounds[SOUND_NEW_ITEM]);
}
add(play, id, position);
stats.totalSpawned++;
play.particles_spawn(character.catalog.summonParticles, position);
}
spawnQueue.clear();
if (held >= 0 && held < (int)list.size())
{
ItemEntity& entity = list[held];
Item& item = character.catalog.items[entity.id];
float& rotation = *entity.actor.rotation;
Vec2 delta = pointerPrevious - pointer.position;
entity.actor.position = pointer.position;
entity.velocity = {};
entity.angularVelocity = (entity.angularVelocity - delta.x * HOLD_SPIN) * area.friction;
rotation = std::clamp(rotation * area.friction, -ROTATION_MAX, ROTATION_MAX);
int removed = -1;
if (item.use == USE_EDIBLE)
{
bool isEdible = item.calories > 0 && stats.calories + item.calories <= body.capacity_max_get();
if (isJustHeld)
text_play(isEdible ? (isOver ? HOOK_FEED_FULL : HOOK_FEED)
: item.calories > stats.capacity ? HOOK_LOW_CAPACITY
: HOOK_FULL);
isJustHeld = false;
for (EatArea& eatArea : character.eatAreas)
{
Rect rect = body.eat_rect_get(eatArea);
bool isInside = is_point_in_rect(rect, entity.actor.position);
bool isAreaActive = isEdible && isInside;
bool isPending = feeding.item == held && feeding.event == eatArea.event;
bool isPendingActive = isPending && is_point_in_rect(feeding.rect, entity.actor.position);
std::string eatName = eatArea.animation + body.suffix;
if (isAreaActive || isPendingActive)
{
if (isPending && eatArea.event >= 0 && body.actor.event == eatArea.event)
{
Traits& traits = character.traits;
body.actor.event = -1;
stats.calories += item.calories;
stats.totalCalories += item.calories;
stats.capacity =
std::clamp(stats.capacity + item.capacityBonus, traits.capacity, traits.capacityMax);
stats.digestionRate = std::clamp(stats.digestionRate + item.digestionBonus,
traits.digestionRateMin, traits.digestionRateMax);
stats.totalConsumed++;
removed = held;
drop();
feeding.animation = body.actor.animation;
feeding.isTextPending = !pointer.isAutomated;
feeding.isTextFull = isOver;
break;
}
feeding.item = held;
feeding.event = eatArea.event;
if (isAreaActive)
feeding.rect = rect;
if (!body.actor.is_playing(eatName))
body.play(eatArea.animation, TRANSITION_NORMAL, INTERRUPT_ALWAYS);
}
else if (feeding.item == held)
drop(HOLD_KEEP);
if (body.actor.is_playing(eatName) && !isInside)
text_play(isOver ? HOOK_FOOD_TAKEN_FULL : HOOK_FOOD_TAKEN);
if (isAreaActive)
break;
}
}
else if (item.use == USE_TOOL && item.isToggleSpritesheet &&
is_point_in_rect(body.actor.bounds_get(), entity.actor.position) && character.alternate.texture &&
character.alternate.sheet < (int)body.actor.sheets.size())
{
stats.isAlternate = !stats.isAlternate;
body.actor.sheets[character.alternate.sheet] =
stats.isAlternate ? character.alternate.texture : character.anm2->sheets[character.alternate.sheet];
audio.play(character.alternate.sound);
removed = held;
drop();
}
if (removed < 0 && pointer.isReleased)
{
bool isThrown = std::abs(delta.x) >= THROW_THRESHOLD || std::abs(delta.y) >= THROW_THRESHOLD;
audio.play(character.sounds[isThrown ? SOUND_THROW : SOUND_RELEASE]);
if (isThrown)
text_play(HOOK_THROW);
entity.velocity = entity.velocity - delta;
release(isThrown);
}
if (removed >= 0)
list.erase(list.begin() + removed);
}
int moved = -1;
bool isTargetLocked = pointer.target >= 0 && pointer.target < (int)list.size();
float right = bounds.x + bounds.w;
float bottom = bounds.y + bounds.h;
for (int i = 0; i < (int)list.size(); i++)
{
ItemEntity& entity = list[i];
Vec2& position = entity.actor.position;
Vec2& velocity = entity.velocity;
float& rotation = *entity.actor.rotation;
float gravity = character.catalog.items[entity.id].gravity;
gravity = gravity != 0 ? gravity : area.gravity;
entity.actor.update(audio);
position = position + velocity;
rotation += entity.angularVelocity;
if (is_point_in_rect(entity.actor.bounds_get(), pointer.position) && !pointer.isCaptured &&
(!isTargetLocked || i == pointer.target))
{
isHovered = true;
tooltip = held == i || pointer.isAutomated ? -1 : entity.id;
active.play(CURSOR_ANIMATION_HOVER, TRANSITION_IF_IDLE);
play.cursorState = CURSOR_HOVER;
if (pointer.isClicked)
{
audio.play(character.sounds[SOUND_GRAB]);
play.particles_spawn(character.cursor.grabParticles, pointer.position);
isJustHeld = true;
}
if (pointer.isDown)
{
active.play(CURSOR_ANIMATION_GRAB);
play.cursorState = CURSOR_ACTION;
held = moved = i;
}
if (pointer.isRightClicked)
{
audio.play(character.sounds[SOUND_DISPOSE]);
active.play(CURSOR_ANIMATION_DISPOSE, TRANSITION_FORCE, INTERRUPT_NEVER);
play.particles_spawn(character.catalog.disposeParticles, position);
held = held == i ? -1 : held > i ? held - 1 : held;
moved = moved == i ? -1 : moved > i ? moved - 1 : moved;
list.erase(list.begin() + i--);
continue;
}
}
if (i != held)
{
if (position.x <= bounds.x || position.x >= right)
{
position.x = position.x <= bounds.x ? bounds.x + 1 : right - 1;
velocity.x = -velocity.x * area.friction;
entity.angularVelocity = -entity.angularVelocity * area.friction;
rotation = -rotation;
audio.play(character.sounds[SOUND_BOUNCE]);
}
if (position.y <= bounds.y || position.y >= bottom)
{
if (position.y >= bottom && velocity.y <= gravity)
{
position.y = bottom;
velocity = {velocity.x * area.friction, 0};
rotation = std::remainder(rotation, 360.0f);
float step = ROTATION_RETURN_SPEED * TICK;
rotation = std::abs(rotation) <= step ? 0 : rotation - std::copysign(step, rotation);
entity.angularVelocity *= ROTATION_GROUND_DAMPING;
}
else
{
velocity.y = -velocity.y;
entity.angularVelocity *= area.friction;
audio.play(character.sounds[SOUND_BOUNCE]);
}
velocity.y *= area.friction;
}
velocity.x *= area.airResistance;
velocity.y += gravity;
}
position = {std::clamp(position.x, bounds.x, right), std::clamp(position.y, bounds.y, bottom)};
}
if (moved >= 0 && moved < (int)list.size() - 1)
{
ItemEntity entity = std::move(list[moved]);
list.erase(list.begin() + moved);
list.push_back(std::move(entity));
held = (int)list.size() - 1;
}
if (wasHovered && !isHovered)
active.play(CURSOR_ANIMATION_IDLE);
pointerPrevious = pointer.position;
}