Skip to content

Commit

Permalink
Rework Battlegroup Visual (#515)
Browse files Browse the repository at this point in the history
* Make a custom card for battlegroups based on the unit upgrade card

* fix: stack with id key at bg card

* Replace resource icons

* Vertically align the flex content of bg card
  • Loading branch information
KingDarBoja authored Jul 18, 2024
1 parent fcfb75d commit 50934a7
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 126 deletions.
338 changes: 212 additions & 126 deletions components/unit-cards/battlegroup-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import {
Tooltip,
Select,
HoverCard,
Image,
BackgroundImage,
Text,
} from "@mantine/core";
import Link from "next/link";
import { useRouter } from "next/navigation";
Expand All @@ -24,12 +27,16 @@ import {
resolveBattlegroupBranches,
BattlegroupResolvedBranchType,
SbpsType,
hasCost,
} from "../../src/unitStats";
import { bgWorkarounds } from "../../src/unitStats/workarounds";
import { UnitUpgradeCard } from "./unit-upgrade-card";
import { useToggle } from "@mantine/hooks";
import { IconAdjustments } from "@tabler/icons-react";
import { getExplorerUnitRoute } from "../../src/routes";
import ImageWithFallback, { iconPlaceholder } from "../placeholders";
import { getIconsPathOnCDN } from "../../src/utils";
import { UnitCostCard } from "./unit-cost-card";

const useStyles = createStyles((theme) => ({
hiddenMobile: {
Expand All @@ -39,127 +46,35 @@ const useStyles = createStyles((theme) => ({
},
}));

function groupBy<T>(arr: T[], fn: (item: T) => any) {
return arr.reduce<Record<string, T[]>>((prev, curr) => {
const groupKey = fn(curr);
const group = prev[groupKey] || [];
group.push(curr);
return { ...prev, [groupKey]: group };
}, {});
}

const BattlegroupBranchMapping = (
branch: BattlegroupResolvedBranchType,
faction: raceType,
sbpsData: SbpsType[],
) => {
const router = useRouter();

const groupedRows = groupBy(branch.upgrades, (item) => item.upg.uiPosition.row);
// Create a series of grid elements per row.

const bgCallInCard = ({ upg, ability }: { upg: UpgradesType; ability: AbilitiesType }) => (
<Box
p="sm"
sx={(theme) => ({
borderRadius: theme.radius.md,
borderWidth: 2,
borderStyle: "solid",
borderColor: theme.colorScheme === "dark" ? theme.colors.dark[5] : theme.colors.gray[2],
})}
>
<UnitUpgradeCard
id={ability.id}
desc={{
screen_name: ability.ui.screenName || upg.ui.screenName,
help_text: ability.ui.helpText || "Ability / Call In",
extra_text: upg.ui.extraText,
brief_text: upg.ui.briefText,
icon_name: upg.ui.iconName,
extra_text_formatter: ability.ui.extraTextFormatter || upg.ui.extraTextFormatter,
brief_text_formatter: ability.ui.briefTextFormatter || upg.ui.briefTextFormatter,
}}
time_cost={{
manpower: ability.cost.manpower,
munition: ability.cost.munition,
fuel: ability.cost.fuel,
popcap: ability.cost.popcap,
time_seconds: ability.rechargeTime,
command: upg.cost.command,
}}
cfg={{ compact: true }}
/>
</Box>
function groupBy<T, K extends string | number>(arr: T[], fn: (item: T) => K) {
return arr.reduce<Record<K, T[]>>(
(prev, curr) => {
const groupKey = fn(curr);
const group = prev[groupKey] || [];
group.push(curr);
return { ...prev, [groupKey]: group };
},
{} as Record<K, T[]>,
);
}

const anchorLinkOrSelect = ({
spawnItems,
upg,
ability,
}: {
spawnItems: string[];
upg: UpgradesType;
ability: AbilitiesType;
}) => {
if (spawnItems.length > 1) {
const mappedSpawnItems = spawnItems.map((id) => {
const foundSbps = sbpsData.find((x) => x.id === id);
return { value: id, label: foundSbps?.ui.screenName || id };
});
return (
<HoverCard width={280} shadow="md" position="top">
<HoverCard.Target>{bgCallInCard({ upg, ability })}</HoverCard.Target>
<HoverCard.Dropdown>
<Select
label="Select squad / emplacement"
placeholder="Pick one"
data={mappedSpawnItems}
onChange={(val) => (val ? router.push(getExplorerUnitRoute(faction, val)) : "")}
/>
</HoverCard.Dropdown>
</HoverCard>
);
}

return (
<Anchor
color="undefined"
underline={false}
sx={{
"&:hover": {
textDecoration: "none",
},
}}
component={Link}
href={getExplorerUnitRoute(faction, spawnItems[0])}
>
{bgCallInCard({ upg, ability })}
</Anchor>
);
};

return (
<Stack align="center">
<Title order={4} color="orange.5" transform="uppercase">
{branch.name}
</Title>
enum BattlegroupArrows {
AVAILABLE_1X1 = "icons/hud/battlegroups/1x1_available.webp",
AVAILABLE_1X2 = "icons/hud/battlegroups/1x2_available.webp",
AVAILABLE_2X1 = "icons/hud/battlegroups/2x1_available.webp",
// AVAILABLE_2X1_LEFT: 'icons/hud/battlegroups/2x1_available_l',
// AVAILABLE_2X1_RIGHT: 'icons/hud/battlegroups/2x1_available_r',
AVAILABLE_2X2 = "icons/hud/battlegroups/2x2_available.webp",
// AVAILABLE_2X2_LEFT: 'icons/hud/battlegroups/2x2_available_l',
// AVAILABLE_2X2_RIGHT: 'icons/hud/battlegroups/2x2_available_r',
}

{Object.entries(groupedRows).map(([rowIndex, branchUpgrades]) => {
return (
<Grid key={`${rowIndex}_${branch.name}`} columns={branchUpgrades.length} w="100%">
{branchUpgrades.map(({ upg, ability, spawnItems }) => (
<Grid.Col key={upg.id} span={1} style={{ display: "flex", alignItems: "stretch" }}>
{spawnItems.length
? anchorLinkOrSelect({ spawnItems, upg, ability })
: bgCallInCard({ upg, ability })}
</Grid.Col>
))}
</Grid>
);
})}
</Stack>
);
};
enum BattlegroupBackgrounds {
dak = "icons/hud/battlegroups/portrait_bg_ak.webp",
german = "icons/hud/battlegroups/portrait_bg_ger.webp",
british = "icons/hud/battlegroups/portrait_bg_uk.webp",
american = "icons/hud/battlegroups/portrait_bg_us.webp",
}

export const BattlegroupCard = (
race: raceType,
Expand Down Expand Up @@ -191,10 +106,6 @@ export const BattlegroupCard = (
}
}

// console.group("Resolved BG");
// console.log(resolvedBattlegroups);
// console.groupEnd();

// Options to toggle between comparison or full width mode for desktop.
const [value, toggle] = useToggle([1, 2]);

Expand Down Expand Up @@ -232,8 +143,8 @@ export const BattlegroupCard = (
<Divider my={12} size="md"></Divider>

<Grid columns={2} gutter={0}>
<Grid.Col md={value}>
<Accordion p={0} chevronPosition="right">
<Grid.Col sm={value}>
<Accordion p={0} chevronPosition="right" variant="filled">
<Accordion.Item value="left_branch">
<Accordion.Control>
<Title order={4}>{branches.LEFT.name}</Title>
Expand All @@ -245,8 +156,8 @@ export const BattlegroupCard = (
</Accordion>
</Grid.Col>

<Grid.Col md={value}>
<Accordion p={0} chevronPosition="right">
<Grid.Col sm={value}>
<Accordion p={0} chevronPosition="right" variant="filled">
<Accordion.Item value="right_branch">
<Accordion.Control>
<Title order={4}>{branches.RIGHT.name}</Title>
Expand All @@ -264,3 +175,178 @@ export const BattlegroupCard = (
</Stack>
);
};

const BattlegroupBranchMapping = (
branch: BattlegroupResolvedBranchType,
faction: raceType,
sbpsData: SbpsType[],
) => {
const router = useRouter();

const groupedRows = groupBy(branch.upgrades, (item) => item.upg.uiPosition.row);

const factionBackgroundSrc = BattlegroupBackgrounds[faction];

// Create a series of grid elements per row.
const bgCallInCard = ({ upg, ability }: { upg: UpgradesType; ability: AbilitiesType }) => {
const costs = {
manpower: ability.cost.manpower,
munition: ability.cost.munition,
fuel: ability.cost.fuel,
popcap: ability.cost.popcap,
time_seconds: ability.rechargeTime,
command: upg.cost.command,
};

const spaceRegex = /\\r?\\n|\\r|\\n/g;
const specialRegex = /\*/g;

const briefTextFormatter = ability.ui.briefTextFormatter || upg.ui.briefTextFormatter;
const briefText =
upg.ui.briefText?.replace(spaceRegex, "\n")?.replace(specialRegex, "") ||
briefTextFormatter?.replace(spaceRegex, "\n")?.replace(specialRegex, "");

return (
<Box
p="sm"
w="100%"
sx={(theme) => ({
borderRadius: theme.radius.md,
borderWidth: 2,
borderStyle: "solid",
borderColor: theme.colorScheme === "dark" ? theme.colors.dark[5] : theme.colors.gray[2],
})}
>
<Flex direction="column" h="100%" gap={16} justify="space-between">
<Flex direction="row" gap={16}>
<BackgroundImage w={80} src={getIconsPathOnCDN(factionBackgroundSrc)} radius="md">
<ImageWithFallback
width={80}
height={80}
src={`/icons/${upg.ui.iconName}.webp`}
alt={ability.ui.screenName || upg.ui.screenName || ""}
fallbackSrc={iconPlaceholder}
></ImageWithFallback>
</BackgroundImage>

<Stack spacing={2} justify="center">
<Title order={6} transform="capitalize" color="yellow.5" lineClamp={1}>
{ability.ui.helpText || "Ability / Call In"}
</Title>
<Title order={5} transform="capitalize" lineClamp={2}>
{ability.ui.screenName || upg.ui.screenName}
</Title>
</Stack>
</Flex>

<Tooltip.Floating multiline style={{ whiteSpace: "pre-line" }} label={briefText}>
<Text fz="sm" lineClamp={7} style={{ whiteSpace: "pre-line" }}>
{briefText}
</Text>
</Tooltip.Floating>
<Flex>{hasCost(costs) ? UnitCostCard(costs) : <></>}</Flex>
</Flex>
</Box>
);
};

const anchorLinkOrSelect = ({
spawnItems,
upg,
ability,
}: {
spawnItems: string[];
upg: UpgradesType;
ability: AbilitiesType;
}) => {
if (spawnItems.length > 1) {
const mappedSpawnItems = spawnItems.map((id) => {
const foundSbps = sbpsData.find((x) => x.id === id);
return { value: id, label: foundSbps?.ui.screenName || id };
});
return (
<HoverCard width={280} shadow="md" position="top">
<HoverCard.Target>{bgCallInCard({ upg, ability })}</HoverCard.Target>
<HoverCard.Dropdown>
<Select
label="Select squad / emplacement"
placeholder="Pick one"
data={mappedSpawnItems}
onChange={(val) => (val ? router.push(getExplorerUnitRoute(faction, val)) : "")}
/>
</HoverCard.Dropdown>
</HoverCard>
);
}

return (
<Anchor
color="undefined"
underline={false}
sx={{
"&:hover": {
textDecoration: "none",
},
}}
component={Link}
href={getExplorerUnitRoute(faction, spawnItems[0])}
>
{bgCallInCard({ upg, ability })}
</Anchor>
);
};

return (
<Stack align="center">
<Title order={4} color="orange.5" transform="uppercase">
{branch.name}
</Title>

{Object.entries(groupedRows).map(([rowIndex, branchUpgrades], currIdx, ogArray) => {
const nextItem = ogArray[currIdx + 1];
let arrowOptionSrc = BattlegroupArrows.AVAILABLE_1X1;
if (nextItem?.[1].length === 2) {
if (branchUpgrades.length === 1) {
arrowOptionSrc = BattlegroupArrows.AVAILABLE_1X2;
} else {
arrowOptionSrc = BattlegroupArrows.AVAILABLE_2X2;
}
} else {
if (branchUpgrades.length === 2) {
arrowOptionSrc = BattlegroupArrows.AVAILABLE_2X1;
}
}
const rowNumber = parseInt(rowIndex);
return (
<Stack key={`${rowIndex}_${branch.name}`} spacing={0} w="100%">
<Grid columns={branchUpgrades.length} grow>
{branchUpgrades.map(({ upg, ability, spawnItems }) => {
return (
<Grid.Col key={upg.id} span={1} style={{ display: "flex" }}>
{spawnItems.length
? anchorLinkOrSelect({ spawnItems, upg, ability })
: bgCallInCard({ upg, ability })}
</Grid.Col>
);
})}
</Grid>
{/* Avoid the last row spawning arrows with this small check */}
{rowNumber < 3 ? (
<Flex justify="center">
<Image
fit="scale-down"
height={128}
sx={{ margin: "auto" }}
src={getIconsPathOnCDN(arrowOptionSrc)}
alt={"battlegroup arrow option"}
></Image>
</Flex>
) : (
<></>
)}
</Stack>
);
})}
</Stack>
);
};
Binary file modified public/icons/common/resources/resource_fuel.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/icons/common/resources/resource_manpower.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/icons/common/resources/resource_munition.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/icons/common/resources/resource_population.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/icons/common/resources/resource_skill_points.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 50934a7

Please sign in to comment.