Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 72 additions & 3 deletions appData/engine/engine.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@
"max": 768,
"editUnits": "subpxVelPrecise"
},

{
"key": "plat_grav",
"sceneType": "PLATFORM",
Expand All @@ -112,7 +111,6 @@
"max": 8192,
"editUnits": "subpxAccPrecise"
},

{
"key": "plat_max_fall_vel",
"sceneType": "PLATFORM",
Expand Down Expand Up @@ -1981,6 +1979,53 @@
}
]
},
{
"key": "adv_push_grace_frames",
"sceneType": "ADVENTURE",
"label": "FIELD_PUSH_GRACE_FRAMES",
"description": "FIELD_PUSH_GRACE_FRAMES_DESC",
"group": "GAMETYPE_ADVENTURE",
"type": "slider",
"cType": "UBYTE",
"defaultValue": 4,
"min": 0,
"max": 30,
"indent": 1,
"conditions": [
{
"key": "FEAT_ADVENTURE_PUSH",
"truthy": true
}
]
},
{
"key": "FEAT_ADVENTURE_PULL",
"sceneType": "ADVENTURE",
"label": "FIELD_FEAT_PULL",
"description": "FIELD_FEAT_PULL_DESC",
"group": "GAMETYPE_ADVENTURE",
"type": "checkbox",
"cType": "define",
"isHeading": true,
"defaultValue": 0
},
{
"key": "ADVENTURE_PULL_ANIM",
"sceneType": "ADVENTURE",
"label": "FIELD_PULL_ANIM_STATE",
"description": "FIELD_PULL_ANIM_STATE_DESC",
"group": "GAMETYPE_ADVENTURE",
"type": "animationstate",
"cType": "define",
"defaultValue": "",
"indent": 1,
"conditions": [
{
"key": "FEAT_ADVENTURE_PULL",
"truthy": true
}
]
},
{
"key": "FEAT_ADVENTURE_KNOCKBACK",
"sceneType": "ADVENTURE",
Expand Down Expand Up @@ -2394,6 +2439,30 @@
"label": "FIELD_IS_SOLID",
"description": "FIELD_IS_SOLID_DESC",
"setFlag": "solid"
},
{
"key": "pushable",
"label": "FIELD_IS_PUSHABLE",
"description": "FIELD_IS_PUSHABLE_DESC",
"setFlag": "pushable",
"conditions": [
{
"key": "solid",
"truthy": true
}
]
},
{
"key": "pullable",
"label": "FIELD_IS_PULLABLE",
"description": "FIELD_IS_PULLABLE_DESC",
"setFlag": "pullable",
"conditions": [
{
"key": "solid",
"truthy": true
}
]
}
],
"collisionTiles": [
Expand Down Expand Up @@ -2476,7 +2545,7 @@
"name": "FIELD_COLLISION_SLOPE_BOTTOM_RIGHT",
"icon": "0F0F1F3FFEFEFCF0",
"group": "corner"
}
}
]
},
{
Expand Down
2 changes: 1 addition & 1 deletion appData/engine/gbvm
40 changes: 30 additions & 10 deletions src/components/editors/actor/ActorEditorExtraCollisionFlags.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { sceneSelectors } from "store/features/entities/entitiesState";
import l10n, { L10NKey } from "shared/lib/lang/l10n";
import { ExtraActorCollisionFlagDef } from "store/features/engine/engineState";
import { CollisionExtraFlag } from "shared/lib/resources/types";

interface ActorEditorExtraCollisionFlagsProps {
actor: ActorNormalized;
Expand Down Expand Up @@ -57,11 +58,22 @@ export const ActorEditorExtraCollisionFlags: FC<
return <></>;
}

// Filter flags based on conditions (e.g. pushable requires solid to be set)
const visibleFlags = extraActorCollisionFlags.filter((flagDef) => {
if (!flagDef.conditions) return true;
return flagDef.conditions.every((cond) => {
const isSet = actor.collisionExtraFlags.includes(
cond.key as CollisionExtraFlag,
);
return cond.truthy ? isSet : !isSet;
});
});

return Array.from({
length: Math.ceil(extraActorCollisionFlags.length / 2),
length: Math.ceil(visibleFlags.length / 2),
}).map((_, rowIndex) => {
const startIndex = rowIndex * 2;
const items = extraActorCollisionFlags.slice(startIndex, startIndex + 2);
const items = visibleFlags.slice(startIndex, startIndex + 2);

return (
<FormRow key={rowIndex}>
Expand All @@ -77,16 +89,24 @@ export const ActorEditorExtraCollisionFlags: FC<
}
checked={actor.collisionExtraFlags.includes(flagDef.setFlag)}
onChange={() => {
onChangeActorProp(
"collisionExtraFlags",
removeArrayElements(
toggleArrayElement(
actor.collisionExtraFlags,
flagDef.setFlag,
),
flagDef.clearFlags ?? [],
const newFlags = removeArrayElements(
toggleArrayElement(
actor.collisionExtraFlags,
flagDef.setFlag,
),
flagDef.clearFlags ?? [],
);
onChangeActorProp("collisionExtraFlags", newFlags);
// Pushable/pullable actors must not have a collision group set,
// otherwise the collision group handler takes priority
// and the pushed/pulled script never fires.
if (
(flagDef.setFlag === "pushable" || flagDef.setFlag === "pullable") &&
newFlags.includes(flagDef.setFlag as CollisionExtraFlag) &&
actor.collisionGroup
) {
onChangeActorProp("collisionGroup", "");
}
}}
/>
))}
Expand Down
13 changes: 11 additions & 2 deletions src/components/editors/actor/ActorEditorProperties.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,18 @@ export const ActorEditorProperties: FC<ActorEditorPropertiesProps> = ({
[onChangeActorProp],
);

const isPushableOrPullable =
(actor?.collisionExtraFlags?.includes("pushable") ||
actor?.collisionExtraFlags?.includes("pullable")) &&
actor?.collisionExtraFlags?.includes("solid");

const onChangeCollisionGroup = useCallback(
(e: CollisionGroup) => onChangeActorProp("collisionGroup", e),
[onChangeActorProp],
(e: CollisionGroup) => {
// Pushable/pullable actors must not have a collision group set
if (isPushableOrPullable && e !== "") return;
onChangeActorProp("collisionGroup", e);
},
[onChangeActorProp, isPushableOrPullable],
);

const onlyCurrentSpriteMode = useCallback(
Expand Down
115 changes: 80 additions & 35 deletions src/components/editors/actor/ActorEditorScripts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ interface ScriptHandlers {
start: ScriptHandler;
interact: ScriptHandler;
update: ScriptHandler;
pushed: ScriptHandler;
pulled: ScriptHandler;
hit: {
hitPlayer: ScriptHandler;
hit1: ScriptHandler;
Expand All @@ -50,15 +52,24 @@ type ActorScriptKey =

type DefaultTab = "interact" | "start" | "update";
type CollisionTab = "hit" | "start" | "update";
type PushableTab = "pushed" | "start" | "update";
type PullableTab = "pulled" | "start" | "update";
type PushPullTab = "pushed" | "pulled" | "start" | "update";
type HitTab = "hitPlayer" | "hit1" | "hit2" | "hit3";

const getScriptKey = (
primaryTab: DefaultTab | CollisionTab,
primaryTab: DefaultTab | CollisionTab | PushableTab | PullableTab | PushPullTab,
secondaryTab: HitTab,
): ActorScriptKey => {
if (primaryTab === "interact") {
return "script";
}
if (primaryTab === "pushed") {
return "hit1Script";
}
if (primaryTab === "pulled") {
return "hit2Script";
}
if (primaryTab === "start") {
return "startScript";
}
Expand Down Expand Up @@ -121,7 +132,53 @@ export const ActorEditorScripts: FC<ActorEditorScriptsProps> = ({
[],
);

const tabs = Object.keys(actor?.collisionGroup ? collisionTabs : defaultTabs);
const pushableTabs: Record<PushableTab, string> = useMemo(
() => ({
pushed: l10n("SIDEBAR_ON_PUSHED"),
start: l10n("SIDEBAR_ON_INIT"),
update: l10n("SIDEBAR_ON_UPDATE"),
}),
[],
);

const pullableTabs: Record<PullableTab, string> = useMemo(
() => ({
pulled: l10n("SIDEBAR_ON_PULLED"),
start: l10n("SIDEBAR_ON_INIT"),
update: l10n("SIDEBAR_ON_UPDATE"),
}),
[],
);

const pushPullTabs: Record<PushPullTab, string> = useMemo(
() => ({
pushed: l10n("SIDEBAR_ON_PUSHED"),
pulled: l10n("SIDEBAR_ON_PULLED"),
start: l10n("SIDEBAR_ON_INIT"),
update: l10n("SIDEBAR_ON_UPDATE"),
}),
[],
);

const isPushable =
actor?.collisionExtraFlags?.includes("pushable") &&
actor?.collisionExtraFlags?.includes("solid");

const isPullable =
actor?.collisionExtraFlags?.includes("pullable") &&
actor?.collisionExtraFlags?.includes("solid");

const activeTabs = (isPushable && isPullable)
? pushPullTabs
: isPushable
? pushableTabs
: isPullable
? pullableTabs
: actor?.collisionGroup
? collisionTabs
: defaultTabs;

const tabs = Object.keys(activeTabs);
const secondaryTabs = Object.keys(hitTabs);
const lastScriptTab = useAppSelector((state) => state.editor.lastScriptTab);
const lastScriptTabSecondary = useAppSelector(
Expand All @@ -139,16 +196,14 @@ export const ActorEditorScripts: FC<ActorEditorScriptsProps> = ({
keyof ScriptHandlers["hit"]
>(initialSecondaryTab as keyof ScriptHandlers["hit"]);

// Make sure currently selected script tab is availble
// when collision group is modified otherwise use first available tab
// Make sure currently selected script tab is available
// when collision group or pushable is modified otherwise use first available tab
useEffect(() => {
const tabs = Object.keys(
actor?.collisionGroup ? collisionTabs : defaultTabs,
);
const tabs = Object.keys(activeTabs);
if (!tabs.includes(scriptMode)) {
setScriptMode(tabs[0] as keyof ScriptHandlers);
}
}, [scriptMode, actor?.collisionGroup, collisionTabs, defaultTabs]);
}, [scriptMode, activeTabs]);

const dispatch = useAppDispatch();

Expand Down Expand Up @@ -230,33 +285,23 @@ export const ActorEditorScripts: FC<ActorEditorScriptsProps> = ({
return (
<>
<StickyTabs>
{actor.collisionGroup ? (
<TabBar
value={scriptMode as CollisionTab}
values={collisionTabs}
onChange={onChangeScriptMode}
overflowActiveTab={scriptMode === "hit" || scriptMode === "update"}
buttons={
<>
{lockButton}
{scriptButton}
</>
}
/>
) : (
<TabBar
value={scriptMode as DefaultTab}
values={defaultTabs}
onChange={onChangeScriptMode}
overflowActiveTab={scriptMode === "update"}
buttons={
<>
{lockButton}
{scriptButton}
</>
}
/>
)}
<TabBar
value={scriptMode as DefaultTab & CollisionTab & PushableTab & PullableTab & PushPullTab}
values={activeTabs as Record<DefaultTab & CollisionTab & PushableTab & PullableTab & PushPullTab, string>}
onChange={onChangeScriptMode as (mode: DefaultTab & CollisionTab & PushableTab & PullableTab & PushPullTab) => void}
overflowActiveTab={
scriptMode === "hit" ||
scriptMode === "update" ||
(scriptMode as string) === "pushed" ||
(scriptMode as string) === "pulled"
}
buttons={
<>
{lockButton}
{scriptButton}
</>
}
/>
{scriptMode === "hit" && (
<TabBar
variant="secondary"
Expand Down
Loading