Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issue #328: Copy Tag #421

Draft
wants to merge 18 commits into
base: main
Choose a base branch
from
Draft
38 changes: 37 additions & 1 deletion app/src/libs/combat/process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { calcApplyRatio } from "./util";
import { calcEffectRoundInfo, isEffectActive } from "./util";
import { nanoid } from "nanoid";
import { clone, move, heal, damageBarrier, damageUser, calcDmgModifier } from "./tags";
import { absorb, reflect, recoil, lifesteal, drain, shield, poison } from "./tags";
import { absorb, reflect, recoil, lifesteal, drain, shield, poison, copy, mirror } from "./tags";
import { increaseStats, decreaseStats } from "./tags";
import { increaseDamageGiven, decreaseDamageGiven } from "./tags";
import { increaseDamageTaken, decreaseDamageTaken } from "./tags";
Expand Down Expand Up @@ -100,12 +100,17 @@ export const realizeTag = <T extends BattleEffect>(props: {
barrierAbsorb?: number;
}): T => {
const { tag, user, target, level, round, barrierAbsorb } = props;

// Ensure rounds exist when necessary
if ("rounds" in tag) {
tag.timeTracker = {};
tag.rounds = tag.rounds ?? 1; // Default to 1 if rounds are undefined
}

if ("power" in tag) {
tag.power = tag.power;
}

tag.id = nanoid();
tag.createdRound = round || 0;
tag.creatorId = user.userId;
Expand All @@ -119,11 +124,28 @@ export const realizeTag = <T extends BattleEffect>(props: {
tag.highestGenerals = user.highestGenerals;
tag.barrierAbsorb = barrierAbsorb || 0;
tag.actionId = props.actionId;

if (target) {
tag.targetHighestOffence = target.highestOffence;
tag.targetHighestDefence = target.highestDefence;
tag.targetHighestGenerals = target.highestGenerals;
}

// Ensure targetId is present in tag (if applicable)
if ("targetId" in tag) {
// Handle Copy Effect (Copies positive effects from target to self)
if (tag.type === "copy" && target) {
tag.targetId = user.userId; // Copy effects to self
tag.creatorId = user.userId;
}

// Handle Mirror Effect (Transfers negative effects from self to target)
if (tag.type === "mirror" && target) {
tag.targetId = target.userId; // Apply to opponent
tag.creatorId = user.userId;
}
}

return structuredClone(tag);
};

Expand Down Expand Up @@ -344,6 +366,20 @@ export const applyEffects = (
info = stun(e, newUsersEffects, curTarget);
} else if (e.type === "drain") {
info = drain(e, usersEffects, consequences, curTarget);
} else if (e.type === "copy") {
info = copy(e, usersEffects, curUser, curTarget);
if (info && usersEffects) {
usersEffects.push(...usersEffects.filter(
(eff) => eff.targetId === curUser.userId && eff.isNew
));
}
} else if (e.type === "mirror") {
info = mirror(e, usersEffects, curUser, curTarget);
if (info && usersEffects) {
usersEffects.push(...usersEffects.filter(
(eff) => eff.targetId === curTarget.userId && eff.isNew
));
}
}
}

Expand Down
70 changes: 70 additions & 0 deletions app/src/libs/combat/tags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1266,6 +1266,76 @@ export const shield = (effect: UserEffect, target: BattleUserState) => {
return info;
};

/** Copy positive effects from opponent to self */
export const copy = (
effect: UserEffect,
usersEffects: UserEffect[],
user: BattleUserState,
target: BattleUserState
): ActionEffect | undefined => {
// Find all positive effects on the target
const positiveEffects = usersEffects.filter(
(e) => e.targetId === target.userId && isPositiveUserEffect(e)
);

if (positiveEffects.length === 0) {
return { txt: `${user.username} tries to copy but finds no effects to copy.`, color: "blue" };
}

positiveEffects.forEach((posEffect) => {
const copiedEffect = structuredClone(posEffect);
copiedEffect.id = nanoid(); // Give it a new unique ID
copiedEffect.targetId = user.userId;
copiedEffect.creatorId = user.userId;
copiedEffect.isNew = true;
copiedEffect.castThisRound = true;
usersEffects.push(copiedEffect);
});

return {
txt: `${user.username} copies ${positiveEffects.length} effects from ${target.username}.`,
color: "blue",
};
};

/** Copy negative effects from self to target */
export const mirror = (
effect: UserEffect,
usersEffects: UserEffect[],
user: BattleUserState,
target: BattleUserState
): ActionEffect | undefined => {
// Find all negative effects on the user that have rounds between 1-10
const negativeEffects = usersEffects.filter(
(e) =>
e.targetId === user.userId &&
isNegativeUserEffect(e) &&
e.rounds !== undefined && // Ensure rounds are set
e.rounds > 0 &&
e.rounds <= 10 // Only allow effects with 1-10 rounds
);

if (negativeEffects.length === 0) {
return { txt: `${user.username} tries to mirror but finds no valid effects to reflect.`, color: "red" };
}

negativeEffects.forEach((negEffect) => {
const mirroredEffect = structuredClone(negEffect);
mirroredEffect.id = nanoid(); // Give it a new unique ID
mirroredEffect.targetId = target.userId;
mirroredEffect.creatorId = user.userId;
mirroredEffect.isNew = true;
mirroredEffect.castThisRound = true;
usersEffects.push(mirroredEffect);
});

return {
txt: `${user.username} mirrors ${negativeEffects.length} effects onto ${target.username}.`,
color: "red",
};
};


/**
* Move user on the battlefield
* 1. Remove user from current ground effect
Expand Down
24 changes: 23 additions & 1 deletion app/src/libs/combat/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,26 @@ export const WeaknessTag = z.object({
});
export type WeaknessTagType = z.infer<typeof WeaknessTag>;

export const CopyTag = z.object({
...BaseAttributes,
...PowerAttributes,
type: z.literal("copy").default("copy"),
description: msg("Copies all positive effects from the target to the user"),
calculation: z.enum(["percentage"]).default("percentage"),
rounds: z.coerce.number().int().min(1).max(10).default(3),
});
export type CopyTagType = z.infer<typeof CopyTag>;

export const MirrorTag = z.object({
...BaseAttributes,
...PowerAttributes,
type: z.literal("mirror").default("mirror"),
description: msg("Mirrors all negative effects from the user to the target"),
calculation: z.enum(["percentage"]).default("percentage"),
rounds: z.coerce.number().int().min(1).max(10).default(3),
});
export type MirrorTagType = z.infer<typeof MirrorTag>;

export const UnknownTag = z.object({
...BaseAttributes,
type: z.literal("unknown").default("unknown"),
Expand All @@ -734,13 +754,15 @@ export const AllTags = z.union([
ClearPreventTag.default({}),
ClearTag.default({}),
CloneTag.default({}),
CopyTag.default({}),
DamageTag.default({}),
DebuffPreventTag.default({}),
DecreaseDamageGivenTag.default({}),
DecreaseDamageTakenTag.default({}),
DecreaseHealGivenTag.default({}),
DecreasePoolCostTag.default({}),
DecreaseStatTag.default({}),
DrainTag.default({}),
FleePreventTag.default({}),
FleeTag.default({}),
HealTag.default({}),
Expand All @@ -751,7 +773,7 @@ export const AllTags = z.union([
IncreasePoolCostTag.default({}),
IncreaseStatTag.default({}),
LifeStealTag.default({}),
DrainTag.default({}),
MirrorTag.default({}),
MoveTag.default({}),
MovePreventTag.default({}),
OneHitKillPreventTag.default({}),
Expand Down
2 changes: 2 additions & 0 deletions app/src/libs/combat/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,8 @@ export const sortEffects = (
"reflect",
"decreaseheal",
"increaseheal",
"copy",
"mirror",
// End-modifiers
"move",
"visual",
Expand Down