Skip to content

Commit

Permalink
feat: combat history
Browse files Browse the repository at this point in the history
  • Loading branch information
aymericdelab committed Nov 18, 2023
1 parent fdf1936 commit 0137c94
Show file tree
Hide file tree
Showing 18 changed files with 57,189 additions and 56,357 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,12 @@ export const Battalion = ({ battalion, onBuild, ...props }: BattalionProps) => {
>
{costPerUnit.map((cost, index) => (
<div className="flex text-lightest mt-2">
<ResourceIcon resource={findResourceById(cost.resourceId).trait} size="xs" className="mr-1" />
<ResourceIcon
key={index}
resource={findResourceById(cost.resourceId).trait}
size="xs"
className="mr-1"
/>
{divideByPrecision(cost.amount).toLocaleString("en-US")}
</div>
))}
Expand Down
216 changes: 216 additions & 0 deletions client/src/components/cityview/realm/combat/defence/AttackHistory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
import clsx from "clsx";
import { CombatResultInterface, Winner } from "../../../../../hooks/store/useCombatHistoryStore";
import { getComponentValue } from "@latticexyz/recs";
import { divideByPrecision, getEntityIdFromKeys } from "../../../../../utils/utils";
import { useDojo } from "../../../../../DojoContext";
import { getRealmNameById, getRealmOrderNameById } from "../../../../../utils/realms";
import { OrderIcon } from "../../../../../elements/OrderIcon";
import { useMemo } from "react";
import useUIStore from "../../../../../hooks/store/useUIStore";
// import ProgressBar from "../../../../../elements/ProgressBar";
import { ResourceCost } from "../../../../../elements/ResourceCost";
import useBlockchainStore from "../../../../../hooks/store/useBlockchainStore";
import { formatSecondsLeftInDaysHours } from "../../labor/laborUtils";

type AttackHistoryProps = {
combatResult: CombatResultInterface;
} & React.HTMLAttributes<HTMLDivElement>;

export const AttackHistory = ({ combatResult, ...props }: AttackHistoryProps) => {
const {
setup: {
components: { Realm, Quantity, Attack, Health, Defence },
},
} = useDojo();

const { attackerRealmEntityId, attackingEntityIds, stolenResources, winner, damage, attackTimestamp } = combatResult;

const nextBlockTimestamp = useBlockchainStore((state) => state.nextBlockTimestamp);

const setTooltip = useUIStore((state) => state.setTooltip);

let { realm_id: attackerRealmId } = getComponentValue(Realm, getEntityIdFromKeys([BigInt(attackerRealmEntityId)]));
let attackerName = attackerRealmId ? getRealmNameById(attackerRealmId) : undefined;

const attackerTotalSoldiers = useMemo(() => {
let total = 0;
for (const id of attackingEntityIds) {
let { value } = getComponentValue(Quantity, getEntityIdFromKeys([BigInt(id)]));
total += value;
}
return total;
}, [attackingEntityIds]);

const attackerTotalAttack = useMemo(() => {
let total = 0;
for (const id of attackingEntityIds) {
let { value } = getComponentValue(Attack, getEntityIdFromKeys([BigInt(id)]));
total += value;
}
return total;
}, [attackingEntityIds]);

const attackerTotalHealth = useMemo(() => {
let total = 0;
for (const id of attackingEntityIds) {
let { value } = getComponentValue(Health, getEntityIdFromKeys([BigInt(id)]));
total += value;
}
return total;
}, [attackingEntityIds]);

const attackerTotalDefence = useMemo(() => {
let total = 0;
for (const id of attackingEntityIds) {
let { value } = getComponentValue(Defence, getEntityIdFromKeys([BigInt(id)]));
total += value;
}
return total;
}, [attackingEntityIds]);

return (
<div
className={clsx(
"flex flex-col w-full mb-1 pb-1 pr-1 border rounded-md border-gray-gold text-xxs text-gray-gold",
props.className,
)}
onClick={props.onClick}
>
<div className="flex items-center text-xxs">
{winner && (
<div
className={clsx(
"flex items-center p-1 border text-light-pink rounded-br-md rounded-tl-md border-gray-gold",
winner === Winner.Target && "!text-order-brilliance !border-order-brilliance",
winner === Winner.Attacker && "!text-order-giants !border-order-giants",
)}
>
{winner === Winner.Target && "Failed"}
{winner === Winner.Attacker && "Success"}
</div>
)}
<div className="flex items-center pt-1 ml-1 -mt-2">
{stolenResources.length === 0 && attackerRealmId && (
<div className="flex items-center ml-1">
<div className="flex items-center ml-1 mr-1 text-gold">
<OrderIcon order={getRealmOrderNameById(attackerRealmId)} className="mr-1" size="xxs" />
{attackerName}
</div>
{winner === Winner.Attacker && (
<span className="italic text-light-pink">{`Attacked with ${attackerTotalSoldiers} battalions`}</span>
)}
{winner === Winner.Target && (
<span className="italic text-light-pink">{`Failed to attack with ${attackerTotalSoldiers} battalions`}</span>
)}
</div>
)}
{stolenResources.length > 0 && attackerRealmId && (
<div className="flex items-center ml-1">
<div className="flex items-center ml-1 mr-1 text-gold">
<OrderIcon order={getRealmOrderNameById(attackerRealmId)} className="mr-1" size="xxs" />
{attackerName}
</div>
<span className="italic text-light-pink">{`Has stolen ${stolenResources.length} resources`}</span>
</div>
)}
</div>
{attackTimestamp && nextBlockTimestamp && (
<div className="flex ml-auto -mt-1 italic text-light-pink">
{formatSecondsLeftInDaysHours(nextBlockTimestamp - attackTimestamp)}
</div>
)}
</div>
<div className="flex flex-col mt-2 space-y-2">
<div className="flex relative justify-between text-xxs text-lightest w-full">
<div className="flex items-center">
<div className="flex items-center h-6 mr-2">
<img src="/images/units/troop-icon.png" className="h-[28px]" />
<div className="flex ml-1 text-center">
<div className="bold mr-1">x{attackerTotalSoldiers}</div>
Battalions
</div>
</div>
</div>
<div className="flex absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 items-center">
<div
className="flex items-center h-6 mr-2"
onMouseEnter={() =>
setTooltip({
position: "top",
content: (
<>
<p className="whitespace-nowrap">Attack power</p>
</>
),
})
}
onMouseLeave={() => setTooltip(null)}
>
<img src="/images/icons/attack.png" className="h-full" />
<div className="flex flex-col ml-1 text-center">
<div className="bold ">{attackerTotalAttack}</div>
</div>
</div>
<div
className="flex items-center h-6 mr-2"
onMouseEnter={() =>
setTooltip({
position: "top",
content: (
<>
<p className="whitespace-nowrap">Defence power</p>
</>
),
})
}
onMouseLeave={() => setTooltip(null)}
>
<img src="/images/icons/defence.png" className="h-full" />
<div className="flex flex-col ml-1 text-center">
<div className="bold ">{attackerTotalDefence}</div>
</div>
</div>
</div>
<div className="flex items-center">
<div className="text-order-brilliance">{attackerTotalHealth && attackerTotalHealth.toLocaleString()}</div>
&nbsp;/ {10 * attackerTotalSoldiers} HP
</div>
</div>
{/* {attackerTotalHealth !== undefined && (
<div className="grid grid-cols-12 gap-0.5">
<ProgressBar
containerClassName="col-span-12 !bg-order-giants"
rounded
progress={(attackerTotalHealth / (attackerTotalSoldiers * 10)) * 100}
/>
</div>
)} */}
<div className="flex items-center justify-between mt-[8px] text-xxs">
{stolenResources && stolenResources.length > 0 && (
<div className="flex justify-center items-center space-x-1 flex-wrap">
{stolenResources.map(
(resource) =>
resource && (
<ResourceCost
key={resource.resourceId}
type="vertical"
color="text-order-brilliance"
resourceId={resource.resourceId}
amount={divideByPrecision(resource.amount)}
/>
),
)}
</div>
)}
{damage && damage > 0 && (
<div className="flex justify-center ml-2 mb-1 items-center space-x-1 flex-wrap">
{winner === Winner.Attacker && <span className="text-white"> Attacker gave</span>}
{winner === Winner.Target && <span className="text-white"> Attacker received</span>}
<span className="text-anger-light"> -{damage} Damage</span>
</div>
)}
</div>
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import useCombatHistoryStore from "../../../../../hooks/store/useCombatHistoryStore";
import { AttackHistory } from "./AttackHistory";

type AttackHistoryPanelProps = {};

export const AttackHistoryPanel = ({}: AttackHistoryPanelProps) => {
const combatHistory = useCombatHistoryStore((state) => state.combatHistory);

return (
<div className="relative flex flex-col p-2 min-h-[120px]">
<div className="flex flex-col">
{combatHistory.length > 0 && (
<>
{combatHistory.map((combatResult, i) => (
<AttackHistory key={i} combatResult={combatResult} />
))}
</>
)}
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { useEffect, useMemo, useState } from "react";
import { Tabs } from "../../../../../elements/tab";
import useUIStore from "../../../../../hooks/store/useUIStore";
import { useRoute, useLocation } from "wouter";
import useRealmStore from "../../../../../hooks/store/useRealmStore";
import { EnnemyRaidersPanel } from "./EnnemyRaidsPanel";
import { AttackHistoryPanel } from "./AttackHistoryPanel";

export type Order = {
orderId: number;
counterpartyOrderId: number;
tradeId: number;
};

type AttacksComponentProps = {};

export const AttacksComponent = ({}: AttacksComponentProps) => {
const [selectedTab, setSelectedTab] = useState(0);
const { realmEntityId } = useRealmStore();

const moveCameraToMarketView = useUIStore((state) => state.moveCameraToMarketView);
const moveCameraToCaravansView = useUIStore((state) => state.moveCameraToCaravansView);
const setTooltip = useUIStore((state) => state.setTooltip);

// @ts-ignore
const [location, setLocation] = useLocation();
// @ts-ignore
const [match, params]: any = useRoute("/realm/:id/:tab");

useEffect(() => {
if ([0, 1, 2].includes(selectedTab)) {
moveCameraToMarketView();
} else if ([3, 4].includes(selectedTab)) {
moveCameraToCaravansView();
}
}, [selectedTab]);

useEffect(() => {
const tabIndex = tabs.findIndex((tab) => tab.key === params?.tab);
if (tabIndex >= 0) {
setSelectedTab(tabIndex);
}
}, [params]);

const tabs = useMemo(
() => [
{
key: "ennemy-raiders",
label: (
<div
onMouseEnter={() =>
setTooltip({
position: "bottom",
content: (
<>
<p className="whitespace-nowrap">Ennemies on your realm</p>
</>
),
})
}
onMouseLeave={() => setTooltip(null)}
className="flex relative group flex-col items-center"
>
<div> Current Attacks </div>
</div>
),
component: <EnnemyRaidersPanel />,
},
{
key: "attack-history",
label: (
<div
onMouseEnter={() =>
setTooltip({
position: "bottom",
content: (
<>
<p className="whitespace-nowrap">Previous attacks</p>
</>
),
})
}
onMouseLeave={() => setTooltip(null)}
className="flex relative group flex-col items-center"
>
<div>Attack History</div>
</div>
),
component: <AttackHistoryPanel />,
},
],
[selectedTab],
);

return (
<>
<Tabs
selectedIndex={selectedTab}
onChange={(index: any) => setLocation(`/realm/${realmEntityId}/${tabs[index].key}`)}
variant="default"
className="h-full"
>
<Tabs.List>
{tabs.map((tab, index) => (
<Tabs.Tab key={index}>{tab.label}</Tabs.Tab>
))}
</Tabs.List>
<Tabs.Panels className="overflow-hidden">
{tabs.map((tab, index) => (
<Tabs.Panel key={index}>{tab.component}</Tabs.Panel>
))}
</Tabs.Panels>
</Tabs>
</>
);
};

export default AttacksComponent;
Loading

0 comments on commit 0137c94

Please sign in to comment.