Skip to content

Commit

Permalink
Allow selecting the AI in the SetupPanel in the editor.
Browse files Browse the repository at this point in the history
GitOrigin-RevId: dcde32caeb6fdf1bcfc52ec7ec26b7b6ad6db658
  • Loading branch information
cpojer committed May 14, 2024
1 parent c80d0cc commit b0dc628
Show file tree
Hide file tree
Showing 15 changed files with 148 additions and 23 deletions.
6 changes: 2 additions & 4 deletions apollo/actions/executeGameAction.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { hasAI } from '@deities/athena/map/Player.tsx';
import MapData from '@deities/athena/MapData.tsx';
import { VisionT } from '@deities/athena/Vision.tsx';
import { Action, execute, MutateActionResponseFn } from '../Action.tsx';
Expand All @@ -19,6 +18,7 @@ export type AIRegistryEntry = Readonly<{
new (effects: Effects): AIType;
};
name: string;
published: boolean;
}>;

export type AIRegistryT = ReadonlyMap<number, AIRegistryEntry>;
Expand All @@ -38,9 +38,7 @@ export function executeAIAction(
activeMap.getCurrentPlayer().isBot()
) {
const player = activeMap.getCurrentPlayer();
const AIClass = ((hasAI(player) &&
player.ai != null &&
AIRegistry.get(player.ai)) ||
const AIClass = ((player.ai != null && AIRegistry.get(player.ai)) ||
AIRegistry.get(0))!.class;

const ai = new AIClass(effects);
Expand Down
1 change: 1 addition & 0 deletions athena/MapData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ const nullPlayer = new HumanPlayer(
'-1',
0,
0,
undefined,
new Set(),
new Set(),
0,
Expand Down
3 changes: 1 addition & 2 deletions athena/lib/validateMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import {
} from '../map/Configuration.tsx';
import Entity from '../map/Entity.tsx';
import Player, {
hasAI,
PlaceholderPlayer,
PlayerID,
toPlayerID,
Expand Down Expand Up @@ -391,7 +390,7 @@ export default function validateMap(
toPlayerID(id),
id,
0,
hasAI(player) && player.ai != null && AIRegistry.has(player.ai)
player.ai != null && AIRegistry.has(player.ai)
? player.ai
: undefined,
player.skills,
Expand Down
37 changes: 25 additions & 12 deletions athena/map/Player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const DynamicPlayerIDs = new Set([

type BasePlainPlayerType = Readonly<{
activeSkills: ReadonlyArray<Skill>;
ai: number | undefined;
charge: number | undefined;
funds: number;
id: PlayerID;
Expand All @@ -41,7 +42,6 @@ export type PlainPlayerType = BasePlainPlayerType &

type PlainBotType = BasePlainPlayerType &
Readonly<{
ai?: number;
name: string;
}>;

Expand All @@ -66,6 +66,7 @@ export default abstract class Player {
public readonly id: PlayerID,
public readonly teamId: PlayerID,
public readonly funds: number,
public readonly ai: number | undefined,
public readonly skills: ReadonlySet<Skill>,
public readonly activeSkills: ReadonlySet<Skill>,
public readonly charge: number,
Expand Down Expand Up @@ -159,6 +160,7 @@ export default abstract class Player {

abstract copy(_: {
activeSkills?: ReadonlySet<Skill>;
ai?: number;
charge?: number;
funds?: number;
id?: PlayerID;
Expand All @@ -177,10 +179,10 @@ export class PlaceholderPlayer extends Player {
id: PlayerID,
teamId: PlayerID,
funds: number,
public readonly ai: number | undefined,
ai: number | undefined,
skills: ReadonlySet<Skill>,
) {
super(id, teamId, funds, skills, new Set(), 0, null, 0);
super(id, teamId, funds, ai, skills, new Set(), 0, null, 0);
}

copy({
Expand Down Expand Up @@ -231,14 +233,14 @@ export class Bot extends Player {
public readonly name: string,
teamId: PlayerID,
funds: number,
public readonly ai: number | undefined,
ai: number | undefined,
skills: ReadonlySet<Skill>,
activeSkills: ReadonlySet<Skill>,
charge: number,
stats: PlayerStatistics | null,
misses: number,
) {
super(id, teamId, funds, skills, activeSkills, charge, stats, misses);
super(id, teamId, funds, ai, skills, activeSkills, charge, stats, misses);
}

copy({
Expand Down Expand Up @@ -320,17 +322,19 @@ export class HumanPlayer extends Player {
public readonly userId: string,
teamId: PlayerID,
funds: number,
ai: number | undefined,
skills: ReadonlySet<Skill>,
activeSkills: ReadonlySet<Skill>,
charge: number,
stats: PlayerStatistics | null,
misses: number,
) {
super(id, teamId, funds, skills, activeSkills, charge, stats, misses);
super(id, teamId, funds, ai, skills, activeSkills, charge, stats, misses);
}

copy({
activeSkills,
ai,
charge,
funds,
id,
Expand All @@ -341,6 +345,7 @@ export class HumanPlayer extends Player {
userId,
}: {
activeSkills?: ReadonlySet<Skill>;
ai?: number;
charge?: number;
funds?: number;
id?: PlayerID;
Expand All @@ -355,6 +360,7 @@ export class HumanPlayer extends Player {
userId ?? this.userId,
teamId ?? this.teamId,
funds ?? this.funds,
ai ?? this.ai,
skills ?? this.skills,
activeSkills ?? this.activeSkills,
charge ?? this.charge,
Expand All @@ -364,10 +370,20 @@ export class HumanPlayer extends Player {
}

toJSON(): PlainPlayerType {
const { activeSkills, charge, funds, id, misses, skills, stats, userId } =
this;
const {
activeSkills,
ai,
charge,
funds,
id,
misses,
skills,
stats,
userId,
} = this;
return {
activeSkills: [...activeSkills],
ai,
charge,
funds,
id,
Expand All @@ -386,6 +402,7 @@ export class HumanPlayer extends Player {
userId,
player.teamId,
player.funds,
player.ai,
player.skills,
player.activeSkills,
player.charge,
Expand Down Expand Up @@ -508,7 +525,3 @@ export function isHumanPlayer(player: Player): player is HumanPlayer {
export function isBot(player: Player): player is Bot {
return player.isBot();
}

export function hasAI(player: Player): player is Bot | PlaceholderPlayer {
return isBot(player) || player.isPlaceholder();
}
1 change: 1 addition & 0 deletions athena/map/Serialization.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export function decodePlayers(
player.userId,
teamId,
player.funds,
player.ai,
skills,
activeSkills,
player.charge || 0,
Expand Down
2 changes: 1 addition & 1 deletion dionysus/AIRegistry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { AIRegistryT } from '@deities/apollo/actions/executeGameAction.tsx';
import DionysusAlpha from './DionysusAlpha.tsx';

const AIRegistry: AIRegistryT = new Map([
[0, { class: DionysusAlpha, name: 'Alpha' }],
[0, { class: DionysusAlpha, name: 'Alpha', published: true }],
]);

export default AIRegistry;
1 change: 1 addition & 0 deletions hera/editor/lib/changePlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export default async function changePlayer(
userId,
existingPlayer?.teamId || id,
0,
undefined,
existingPlayer?.skills || new Set(),
new Set(),
existingPlayer?.charge || 0,
Expand Down
21 changes: 21 additions & 0 deletions hera/editor/panels/SetupPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import updatePlayer from '@deities/athena/lib/updatePlayer.tsx';
import updatePlayers from '@deities/athena/lib/updatePlayers.tsx';
import { DefaultMapSkillSlots } from '@deities/athena/map/Configuration.tsx';
import { HumanPlayer, PlayerID } from '@deities/athena/map/Player.tsx';
import AIRegistry from '@deities/dionysus/AIRegistry.tsx';
import isPresent from '@deities/hephaestus/isPresent.tsx';
import sortBy from '@deities/hephaestus/sortBy.tsx';
import Stack from '@deities/ui/Stack.tsx';
Expand All @@ -13,6 +14,11 @@ import PlayerSelector from '../../ui/PlayerSelector.tsx';
import TeamSelector from '../../ui/TeamSelector.tsx';
import { SetMapFunction } from '../Types.tsx';

const aiRegistry =
process.env.NODE_ENV === 'development'
? AIRegistry
: new Map([...AIRegistry].filter(([, { published }]) => published));

export default function MapEditorSetupPanel({
setMap,
state,
Expand Down Expand Up @@ -47,6 +53,19 @@ export default function MapEditorSetupPanel({
[mapWithPlayers],
);

const onSelectAI = useCallback(
(playerID: PlayerID, ai: number) => {
const player = map.getPlayer(playerID);
setMap(
'teams',
map.copy({
teams: updatePlayer(map.teams, player.copy({ ai })),
}),
);
},
[map, setMap],
);

const onSelectSkills = useCallback(
(playerID: PlayerID, slot: number, skill: Skill | null) => {
const player = map.getPlayer(playerID);
Expand Down Expand Up @@ -80,10 +99,12 @@ export default function MapEditorSetupPanel({
placeholders
/>
<PlayerSelector
aiRegistry={aiRegistry.size > 1 ? aiRegistry : null}
availableSkills={Skills}
hasSkills
map={mapWithPlayers}
onSelect={null}
onSelectAI={onSelectAI}
onSelectSkills={onSelectSkills}
skillSlots={DefaultMapSkillSlots}
users={placeholderUsers}
Expand Down
34 changes: 34 additions & 0 deletions hera/ui/AISelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { AIRegistryT } from '@deities/apollo/actions/executeGameAction.tsx';
import InlineLink from '@deities/ui/InlineLink.tsx';
import Select from '@deities/ui/Select.tsx';
import Stack from '@deities/ui/Stack.tsx';

export default function AISelector({
currentAI,
registry,
setAI,
}: {
currentAI: number | undefined;
registry: AIRegistryT;
setAI: (id: number) => void;
}) {
const currentEntry = (
currentAI != null ? registry.get(currentAI) : registry.get(0)
)!;
return (
<Stack alignCenter gap={16}>
<fbt desc="Label to pick an AI">AI:</fbt>
<Select selectedItem={currentEntry.name}>
{[...registry].map(([id, { name }]) => (
<InlineLink
key={id}
onClick={() => setAI(id)}
selectedText={id === currentAI}
>
{name}
</InlineLink>
))}
</Select>
</Stack>
);
}
Loading

0 comments on commit b0dc628

Please sign in to comment.