valtech-components 4.0.625 → 4.0.627

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -59,7 +59,7 @@ import fixWebmDuration from 'fix-webm-duration';
59
59
  * Current version of valtech-components.
60
60
  * This is automatically updated during the publish process.
61
61
  */
62
- const VERSION = '4.0.625';
62
+ const VERSION = '4.0.627';
63
63
 
64
64
  // Control de estado de refresco (singleton a nivel de módulo)
65
65
  let isRefreshing = false;
@@ -74432,7 +74432,7 @@ function playCardFromHand(game, seat, card, options = {}) {
74432
74432
  spentOfferings = card.cost ?? 0;
74433
74433
  spendOfferings(player, spentOfferings);
74434
74434
  const life = card.life ?? 1;
74435
- player.battleZone.push({
74435
+ const newChar = {
74436
74436
  instanceId: `${seat.toLowerCase()}_${next.revision + 1}_${player.battleZone.length}`,
74437
74437
  cardId: card.id,
74438
74438
  name: displayName,
@@ -74443,7 +74443,11 @@ function playCardFromHand(game, seat, card, options = {}) {
74443
74443
  currentHp: life,
74444
74444
  maxHp: life,
74445
74445
  canAttack: false,
74446
- });
74446
+ };
74447
+ bakeEffects(newChar, card.effects ?? []);
74448
+ player.battleZone.push(newChar);
74449
+ fireEffects(next, newChar, 'on_enter', seat);
74450
+ fireEffects(next, newChar, 'passive', seat);
74447
74451
  player.battleZone.sort((left, right) => (left.slot ?? 0) - (right.slot ?? 0));
74448
74452
  }
74449
74453
  else if (card.type === 'tool') {
@@ -74538,8 +74542,10 @@ function resolveAttackLeader(game, attackerSeat, attackerInstanceId, messages) {
74538
74542
  const attacker = next.players[attackerSeat].battleZone.find(card => card.instanceId === attackerInstanceId);
74539
74543
  if (!attacker || !attacker.canAttack)
74540
74544
  return { game, attacked: false, attackerDamage: 0, targetDamage: 0, log: [] };
74541
- const damage = attacker.attack;
74545
+ fireEffects(next, attacker, 'on_attack', attackerSeat, { targeting: 'leader' });
74546
+ const damage = attacker.attack + (attacker.combatAttackBuff ?? 0);
74542
74547
  next.players[defenderSeat].leaderHp = Math.max(0, next.players[defenderSeat].leaderHp - damage);
74548
+ attacker.combatAttackBuff = 0;
74543
74549
  attacker.canAttack = false;
74544
74550
  const log = [
74545
74551
  `${attacker.name} ${attackerSeat === 'P1' ? messages.attacksOpponentLeader : messages.attacksYourLeader}`,
@@ -74559,8 +74565,14 @@ function resolveAttackCharacter(game, attackerSeat, attackerInstanceId, targetIn
74559
74565
  if (!attacker || !target || !attacker.canAttack) {
74560
74566
  return { game, attacked: false, attackerDamage: 0, targetDamage: 0, log: [] };
74561
74567
  }
74562
- target.currentHp -= attacker.attack;
74563
- attacker.currentHp -= target.attack;
74568
+ fireEffects(next, attacker, 'on_attack', attackerSeat, { targeting: 'character' }, target);
74569
+ fireEffects(next, target, 'on_defend', defenderSeat, { targeting: 'character' }, attacker);
74570
+ const attackerTotal = attacker.attack + (attacker.combatAttackBuff ?? 0);
74571
+ const targetTotal = target.attack + (target.combatAttackBuff ?? 0);
74572
+ target.currentHp -= attackerTotal;
74573
+ attacker.currentHp -= targetTotal;
74574
+ attacker.combatAttackBuff = 0;
74575
+ target.combatAttackBuff = 0;
74564
74576
  attacker.canAttack = false;
74565
74577
  cleanupDestroyed(next.players[attackerSeat], next);
74566
74578
  cleanupDestroyed(next.players[defenderSeat], next);
@@ -74571,8 +74583,8 @@ function resolveAttackCharacter(game, attackerSeat, attackerInstanceId, targetIn
74571
74583
  return {
74572
74584
  game: next,
74573
74585
  attacked: true,
74574
- attackerDamage: target.attack,
74575
- targetDamage: attacker.attack,
74586
+ attackerDamage: targetTotal,
74587
+ targetDamage: attackerTotal,
74576
74588
  log,
74577
74589
  };
74578
74590
  }
@@ -74625,6 +74637,9 @@ function cleanupDestroyed(player, game) {
74625
74637
  const destroyed = player.battleZone.filter(card => card.currentHp <= 0);
74626
74638
  if (!destroyed.length)
74627
74639
  return;
74640
+ for (const ch of destroyed) {
74641
+ fireEffects(game, ch, 'on_death', ch.owner);
74642
+ }
74628
74643
  game.historyZone.push(...destroyed.map(card => card.cardId));
74629
74644
  player.battleZone = player.battleZone.filter(card => card.currentHp > 0);
74630
74645
  }
@@ -74651,6 +74666,102 @@ function offeringAbilityReserveGain(abilityText, fallbackValue) {
74651
74666
  function opponentSeat(seat) {
74652
74667
  return seat === 'P1' ? 'P2' : 'P1';
74653
74668
  }
74669
+ function checkCondition(condition, ctx) {
74670
+ if (!condition)
74671
+ return true;
74672
+ return condition.targeting === ctx.targeting;
74673
+ }
74674
+ function applyMutationToCharacter(ch, effect) {
74675
+ const mut = effect.mutation;
74676
+ const duration = effect.duration ?? 'permanent';
74677
+ switch (mut.type) {
74678
+ case 'buff_attack':
74679
+ if (duration === 'combat') {
74680
+ ch.combatAttackBuff = (ch.combatAttackBuff ?? 0) + mut.amount;
74681
+ }
74682
+ else {
74683
+ ch.attack += mut.amount;
74684
+ }
74685
+ break;
74686
+ case 'buff_life':
74687
+ ch.currentHp += mut.amount;
74688
+ ch.maxHp += mut.amount;
74689
+ break;
74690
+ case 'deal_damage':
74691
+ ch.currentHp = Math.max(0, ch.currentHp - mut.amount);
74692
+ break;
74693
+ case 'restore_hp':
74694
+ ch.currentHp = Math.min(ch.maxHp, ch.currentHp + mut.amount);
74695
+ break;
74696
+ case 'destroy':
74697
+ ch.currentHp = 0;
74698
+ break;
74699
+ }
74700
+ }
74701
+ function applyEffect(game, effect, source, combatOpponent, seat) {
74702
+ const { mutation: mut, target } = effect;
74703
+ const opp = opponentSeat(seat);
74704
+ switch (target) {
74705
+ case 'self':
74706
+ applyMutationToCharacter(source, effect);
74707
+ break;
74708
+ case 'combat_opponent':
74709
+ if (combatOpponent)
74710
+ applyMutationToCharacter(combatOpponent, effect);
74711
+ break;
74712
+ case 'all_friendly':
74713
+ for (const ch of game.players[seat].battleZone)
74714
+ applyMutationToCharacter(ch, effect);
74715
+ break;
74716
+ case 'all_enemy':
74717
+ for (const ch of game.players[opp].battleZone)
74718
+ applyMutationToCharacter(ch, effect);
74719
+ break;
74720
+ case 'opponent_leader':
74721
+ case 'own_leader': {
74722
+ const ls = target === 'opponent_leader' ? opp : seat;
74723
+ if (mut.type === 'deal_damage') {
74724
+ game.players[ls].leaderHp = Math.max(0, game.players[ls].leaderHp - mut.amount);
74725
+ }
74726
+ else if (mut.type === 'restore_hp') {
74727
+ game.players[ls].leaderHp = Math.min(game.players[ls].leaderMaxHp, game.players[ls].leaderHp + mut.amount);
74728
+ }
74729
+ break;
74730
+ }
74731
+ }
74732
+ // game-level mutations that bypass the character switch
74733
+ if (mut.type === 'draw_card') {
74734
+ for (let i = 0; i < mut.amount; i++)
74735
+ drawCardFor(game, seat);
74736
+ }
74737
+ else if (mut.type === 'discard_card') {
74738
+ const tSeat = mut.who === 'opponent' ? opp : seat;
74739
+ const hand = game.players[tSeat].hand ?? [];
74740
+ let seed = game.revision * 31 + game.turnCount * 7;
74741
+ for (let i = 0; i < Math.min(mut.amount, hand.length); i++) {
74742
+ seed = (seed * 1664525 + 1013904223) & 0xffffffff;
74743
+ const idx = Math.abs(seed) % hand.length;
74744
+ game.historyZone.push(hand.splice(idx, 1)[0]);
74745
+ }
74746
+ game.players[tSeat].hand = hand;
74747
+ }
74748
+ else if (mut.type === 'gain_offerings') {
74749
+ game.players[seat].temporaryOfferingReserve =
74750
+ (game.players[seat].temporaryOfferingReserve ?? 0) + mut.amount;
74751
+ }
74752
+ }
74753
+ function fireEffects(game, source, trigger, seat, ctx = {}, combatOpponent = null) {
74754
+ for (const effect of source.effects ?? []) {
74755
+ if (effect.trigger !== trigger)
74756
+ continue;
74757
+ if (!checkCondition(effect.condition, ctx))
74758
+ continue;
74759
+ applyEffect(game, effect, source, combatOpponent, seat);
74760
+ }
74761
+ }
74762
+ function bakeEffects(instance, effects) {
74763
+ instance.effects = [...(instance.effects ?? []), ...effects];
74764
+ }
74654
74765
  // --- Tool equip passives (H8) ---
74655
74766
  function applyToolPassives(ch, card) {
74656
74767
  const text = card.abilities?.[0]?.text ?? '';
@@ -74667,6 +74778,7 @@ function applyToolPassives(ch, card) {
74667
74778
  if (lower.includes('puede atacar el turno en que entra')) {
74668
74779
  ch.canAttack = true;
74669
74780
  }
74781
+ bakeEffects(ch, card.effects ?? []);
74670
74782
  }
74671
74783
  // --- Effect resolution (H7) ---
74672
74784
  function resolveCardEffect(game, card, seat) {