2026-04-17 13:57:25 +08:00
|
|
|
import {CombatEntity, CombatState, EffectTable, PlayerEntity} from "./types";
|
|
|
|
|
import {CardData, EffectData} from "@/samples/slay-the-spire-like/system/types";
|
|
|
|
|
import {GameItemMeta} from "@/samples/slay-the-spire-like/system/progress/types";
|
|
|
|
|
import {GridInventory} from "@/samples/slay-the-spire-like/system/grid-inventory/types";
|
2026-04-16 14:00:49 +08:00
|
|
|
|
2026-04-17 09:27:20 +08:00
|
|
|
export function addEffect(effects: EffectTable, effect: EffectData, stacks: number){
|
|
|
|
|
let current = effects[effect.id];
|
|
|
|
|
|
|
|
|
|
if(!current) current = {data: effect, stacks};
|
|
|
|
|
else current.stacks += stacks;
|
|
|
|
|
|
|
|
|
|
if(current.stacks === 0 && effects[effect.id])
|
|
|
|
|
delete effects[effect.id];
|
|
|
|
|
else if(current.stacks !== 0 && !effects[effect.id])
|
|
|
|
|
effects[effect.id] = current;
|
2026-04-16 14:00:49 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-17 09:27:20 +08:00
|
|
|
export function addEntityEffect(entity: CombatEntity, effect: EffectData, stacks: number){
|
|
|
|
|
addEffect(entity.effects, effect, stacks);
|
2026-04-16 14:00:49 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-17 09:27:20 +08:00
|
|
|
export function addItemEffect(entity: PlayerEntity, itemKey: string, effect: EffectData, stacks: number){
|
|
|
|
|
entity.itemEffects[itemKey] = entity.itemEffects[itemKey] || {};
|
|
|
|
|
addEffect(entity.itemEffects[itemKey], effect, stacks);
|
2026-04-16 14:00:49 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-17 09:27:20 +08:00
|
|
|
export function onEntityEffectUpkeep(entity: CombatEntity){
|
|
|
|
|
for(const effect of Object.values(entity.effects)){
|
|
|
|
|
const lifecycle = effect.data.lifecycle;
|
|
|
|
|
if(lifecycle === 'temporary')
|
|
|
|
|
addEntityEffect(entity, effect.data, -effect.stacks);
|
|
|
|
|
else if(lifecycle === 'lingering')
|
|
|
|
|
addEntityEffect(entity, effect.data, effect.stacks >= 0 ? -1 : 1);
|
2026-04-16 14:00:49 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-17 11:57:07 +08:00
|
|
|
export function onEntityPostureDamage(entity: CombatEntity, damage: number){
|
|
|
|
|
for(const effect of Object.values(entity.effects)){
|
|
|
|
|
const lifecycle = effect.data.lifecycle;
|
|
|
|
|
if(lifecycle === 'posture')
|
|
|
|
|
addEntityEffect(entity, effect.data, -Math.min(damage, effect.stacks));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-17 09:27:20 +08:00
|
|
|
export function onPlayerItemEffectUpkeep(entity: PlayerEntity){
|
|
|
|
|
for(const [itemKey, itemEffects] of Object.entries(entity.itemEffects)){
|
|
|
|
|
for(const effect of Object.values(itemEffects)){
|
|
|
|
|
const lifecycle = effect.data.lifecycle;
|
|
|
|
|
if(lifecycle === 'itemTemporary')
|
|
|
|
|
addItemEffect(entity, itemKey, effect.data, -effect.stacks);
|
2026-04-16 14:00:49 +08:00
|
|
|
}
|
|
|
|
|
}
|
2026-04-17 10:00:14 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-17 11:57:07 +08:00
|
|
|
export function onItemPlay(entity: PlayerEntity, itemKey: string){
|
|
|
|
|
const effects = entity.itemEffects[itemKey];
|
|
|
|
|
if(!effects)return;
|
|
|
|
|
for(const effect of Object.values(effects)){
|
|
|
|
|
if(effect.data.lifecycle === 'itemUntilPlay'){
|
|
|
|
|
addItemEffect(entity, itemKey, effect.data, -effect.stacks);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function onItemDiscard(entity: PlayerEntity, itemKey: string){
|
|
|
|
|
const effects = entity.itemEffects[itemKey];
|
|
|
|
|
if(!effects)return;
|
|
|
|
|
for(const effect of Object.values(effects)){
|
|
|
|
|
if(effect.data.lifecycle === 'itemUntilDiscard'){
|
|
|
|
|
addItemEffect(entity, itemKey, effect.data, -effect.stacks);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-17 11:06:09 +08:00
|
|
|
export function* getAliveEnemies(state: CombatState) {
|
|
|
|
|
for (let enemy of state.enemies) {
|
|
|
|
|
if (enemy.isAlive) {
|
|
|
|
|
yield enemy;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-17 12:58:12 +08:00
|
|
|
|
|
|
|
|
export function getCombatEntity(state: CombatState, entityKey: string){
|
|
|
|
|
return entityKey === 'player' ? state.player : state.enemies.find(e => e.id === entityKey);
|
2026-04-17 13:57:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function canPlayCard(player: PlayerEntity, costType: CardData['costType'], costCount: number, itemId: string, inventory: GridInventory<GameItemMeta>): boolean {
|
|
|
|
|
if (costType === 'energy') {
|
|
|
|
|
return player.energy >= costCount;
|
|
|
|
|
}
|
|
|
|
|
if (costType === 'uses') {
|
|
|
|
|
const item = inventory.items.get(itemId);
|
|
|
|
|
if (!item || !item.meta) return false;
|
|
|
|
|
const depletion = item.meta.depletion ?? 0;
|
|
|
|
|
return depletion < costCount;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function payCardCost(player: PlayerEntity, costType: CardData['costType'], costCount: number, itemId: string, inventory: GridInventory<GameItemMeta>): void {
|
|
|
|
|
if (costType === 'energy') {
|
|
|
|
|
player.energy -= costCount;
|
|
|
|
|
} else if (costType === 'uses') {
|
|
|
|
|
const item = inventory.items.get(itemId);
|
|
|
|
|
if (item && item.meta) {
|
|
|
|
|
item.meta.depletion = (item.meta.depletion ?? 0) + costCount;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|