318 lines
12 KiB
C++
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;
|
|
}
|