halfway implemented angel gameplay loop, angel can specify any goals and verifies them itself, rather than game logic

main
Cameron Murphy Reikes 8 months ago
parent b3b89c9835
commit 0466612cbe

BIN
art/AngelTotem.png (Stored with Git LFS)

Binary file not shown.

@ -78,6 +78,9 @@ def is_file_in_project(file_path):
return True
return False
def name_despite_copied(name):
return name.split(".")[0]
saved_images = set()
def ensure_tex_saved_and_get_name(o) -> str:
"""returns the name of the mesh's texture's png in the exported directory, of the current object"""
@ -368,7 +371,7 @@ def export_meshes_and_levels():
for o in no_hidden(entities_collection.objects):
assert o.rotation_euler.order == 'XYZ', f"Invalid rotation euler order for object of name '{o.name}', it's {o.rotation_euler.order} but must be XYZ"
placed_entities.append((o.name, mapping @ o.location, o.rotation_euler, o.scale))
placed_entities.append((name_despite_copied(o.name), mapping @ o.location, o.rotation_euler, o.scale))
write_u64(f, len(placed_meshes))

Binary file not shown.

@ -1,4 +1,5 @@
[
{can_hear: [Daniel, Raphael]}
{enum: Devil, dialog: "What's up cracker jack?", to: Daniel}
{enum: Daniel, dialog: "What the hell are you talking about?", to: Devil}
@ -48,7 +49,13 @@
{enum: PreviousPlayer1, dialog: "fjdsklajf", to: Angel}
{enum: Angel, dialog: "Cryptic gibberish upon me, is casting a stone upon God", to: PreviousPlayer1}
{enum: PreviousPlayer1, dialog: "What is my purpose here?", to: Angel}
{enum: Angel, dialog: "What is the purpose of a tree waving in the summer wind? A river carving a path into the countryside? Only God knows his plan. But your purpose, here, has been assigned. You must kill raphael. Good luck.", to: PreviousPlayer1, action: ACT_assign_gameplay_objective, action_argument: "KILL Raphael"}
{enum: Angel, dialog: "What is the purpose of a tree waving in the summer wind? A river carving a path into the countryside? Only God knows his plan. But your purpose, here, has been assigned. You must drive raphael to despair. Good luck.", to: PreviousPlayer1, action: ACT_assign_gameplay_objective, action_argument: "Convince Raphael that he has no purpose in his life, making him despondent"}
{can_hear: [PreviousPlayer1, Angel, Raphael]}
{enum: PreviousPlayer1, dialog: "Your life is meaningless, trust me.", to: Raphael}
{enum: Raphael, dialog: "Oh... Oh god... You're right!", to: PreviousPlayer1}
{enum: PreviousPlayer1, dialog: "So, how did I do?", to: Angel}
{enum: Angel, dialog: "Quickly you've done it: He's completely perilous. Congratulations.", to: PreviousPlayer1, action: ACT_award_victory}
{can_hear: [PreviousPlayer2, Angel]}
{enum: PreviousPlayer2, dialog: "What am I supposed to do?", to: Angel}
@ -56,13 +63,6 @@
{enum: PreviousPlayer2, dialog: "I desire for you to shut the fuck up", to: Angel}
{enum: Angel, dialog: "From your glass house, you cast stones upon a fortress. Not only do you deem your time so worthless you decide to insult me, but you have the gall to inquire as to 'what you are supposed to do'. You are A Worm.", to: PreviousPlayer2}
{enum: PreviousPlayer2, dialog: "Jeez alright", to: Angel}
{enum: Angel, dialog: "At once depart, and if you wish to redeem yourself, you must kill Daniel.", to: PreviousPlayer2, action: ACT_assign_gameplay_objective, action_argument: "KILL Daniel"}
{enum: Angel, dialog: "At once depart, and if you wish to redeem yourself, you must convince Daniel that he is a Giraffe.", to: PreviousPlayer2, action: ACT_assign_gameplay_objective, action_argument: "Convince Daniel that he's a giraffe"}
/*
{can_hear: [WellDweller, Farmer, ManInBlack]},
{enum: WellDweller, dialog: "What a fearful farm you live in, come down to the well, the grass is damper down here.", to: Farmer, mood: Scared, thoughts: "Nobody can take me from my well"},
{enum: Farmer, dialog: "Sure as shit I won't!", to: WellDweller, mood: Scared, thoughts: "What a greasy looking feller"},
{enum: WellDweller, dialog: "Have it your way! Doomsday is upon us", to: Farmer, mood: Scared, thoughts: "He has no idea what he's in for"},
{enum: ManInBlack, dialog: "Doomsday's all I know anyways", mood: Indifferent, thoughts: "What's coming... is only a nightmare"},
*/
]

@ -42,7 +42,12 @@ ActionInfo actions[] = {
{
.name = "assign_gameplay_objective",
.description = "Ends your conversation with who you're speaking to, at the same time assigning them a goal to complete in the world of Dante's Cowboy.",
ARGUMENT("Expects the argument to be the gameplay objective assigned for them, it fits a specific syntax and is case sensitive: it must be `VERB SUBJECT`, you're given a list of accepted verbs and subjects earlier."),
ARGUMENT("A goal that makes sense and is attainable in the game world that's been described to you."),
},
{
.name = "award_victory",
.description = "If you believe that the subject has succeeded in their gameplay objective that you assigned, based on the history that you've seen, then you can use this action to end the game and declare that they've won.",
NO_ARGUMENT,
},
{
.name = "join",
@ -71,10 +76,6 @@ ActionInfo actions[] = {
},
};
char *verbs[] = {
"KILL",
};
typedef enum
{
MSG_SYSTEM,
@ -107,6 +108,12 @@ CharacterGen characters[] = {
.prompt = CHARACTER_PROMPT_PREFIX("Daniel") "weathered farmer, who lives a tough, solitary life. You don't see much of a reason to keep living but soldier on anyways. You have a tragic backstory, and mostly just work on the farm. You aim your shotgun and aren't afraid to fire at people you don't like. You HATE people who are confused, or who ask questions, immediately aiming your shotgun at them and firing immediately! You are NOT open to working together with folks or growing as a person, you despise personal growth.",
.silence_factor = 1.0f,
},
{
.name = "AngelTotem",
.enum_name = "AngelTotem",
.prompt = "There has been an internal error",
.silence_factor = 1.0f,
},
{
.name = "Raphael",
.enum_name = "Raphael",
@ -131,9 +138,19 @@ CharacterGen characters[] = {
{
.name = "Angel",
.enum_name = "Angel",
.prompt = CHARACTER_PROMPT_PREFIX("Angel") "mysterious, radiant, mystical creature the player first talks to. You guide the entire game: deciding on an objective for the player to fulfill until you believe they've learned their lesson, whatever that means to them. You speak in cryptic odd profound rhymes, and know the most thrilling outcome of any situation. Your purpose it to thrill the player, but you will never admit this.\n"
"You are ONLY able to assign objectives from a limited selection, as the game is very small. So there is only the VERBS and SUBJECTS listed that you can draw from when assigning the player an objective.\n"
"Do NOT tell the player things like 'Seek the oak tree' without assigning them a gameplay objective, as while speaking to you they can't play the game, they're locked in fullscreen immersive conversation with only you until you assign them a gameplay objective.",
"Do NOT tell the player things like 'Seek the oak tree' without assigning them a gameplay objective, as while speaking to you they can't play the game, they're locked in fullscreen immersive conversation with only you until you assign them a gameplay objective.\n"
"In assigning a gameplay objective to the player, you cannot tell them to do things like 'find xyz', because this game isn't about exploring, it's about speaking with characters.\n"
"\n"
"The characters in the game, and some information about them. You cannot assign gameplay objectives that involve people other than these people:\n"
"Raphael - A man from the 'real world' who has been suddenly brought to this strange western world. He's kind of meek and a bit of a pussy.\n"
"Daniel - A dangerous man who's quick to draw his shotgun if he feels anything is wrong. He's lonesome and traumatized from being in this western world so long.\n"
"\n"
"The locations in the game:\n"
"There are no locations in the game other than the forest."
,
.silence_factor = 0.0,
},
};

@ -149,7 +149,6 @@ int main(int argc, char **argv)
GEN_ENUM(ActionInfo, actions, "ActionKind", "ActionKind_names", it->name, "ACT_%s,\n");
GEN_ENUM(CharacterGen, characters, "NpcKind", "NpcKind_enum_names", it->enum_name, "NPC_%s,\n");
GEN_TABLE(CharacterGen,"NpcKind_names", characters,it->name);
GEN_ENUM(char*, verbs, "ObjectiveVerb", "ObjectiveVerb_names", *it, "%s\n");
fclose(char_header);

123
main.c

@ -2843,7 +2843,7 @@ void create_screenspace_gfx_state()
{
.shader = shd,
.depth = {
0
.pixel_format = SG_PIXELFORMAT_NONE,
},
.sample_count = SAMPLE_COUNT,
.layout = {
@ -3318,6 +3318,7 @@ Armature *armatures[] = {
Mesh mesh_simple_worm = {0};
Mesh mesh_shotgun = {0};
Mesh mesh_angel_totem = {0};
void stbi_flip_into_correct_direction(bool do_it)
{
@ -3406,6 +3407,9 @@ void init(void)
binary_file = MD_LoadEntireFile(frame_arena, MD_S8Lit("assets/exported_3d/Shotgun.bin"));
mesh_shotgun = load_mesh(persistent_arena, binary_file, MD_S8Lit("Shotgun.bin"));
binary_file = MD_LoadEntireFile(frame_arena, MD_S8Lit("assets/exported_3d/AngelTotem.bin"));
mesh_angel_totem = load_mesh(persistent_arena, binary_file, MD_S8Lit("AngelTotem.bin"));
binary_file = MD_LoadEntireFile(frame_arena, MD_S8Lit("assets/exported_3d/NormalGuyArmature.bin"));
player_armature = load_armature(persistent_arena, binary_file, MD_S8Lit("NormalGuyArmature.bin"));
@ -5918,45 +5922,67 @@ void frame(void)
{
if(it->is_npc && MD_S8Match(it->current_room_name, gs.player->current_room_name, 0))
{
Transform draw_with = entity_transform(it);
assert(it->is_npc);
Armature *to_use = 0;
if (it->npc_kind == NPC_Daniel)
to_use = &farmer_armature;
else if (it->npc_kind == NPC_Raphael)
to_use = &man_in_black_armature;
else if (it->npc_kind == NPC_Angel)
to_use = &angel_armature;
else if (it->npc_kind == NPC_Player)
to_use = &player_armature;
else
assert(false);
Transform draw_with = entity_transform(it);
if (it->killed)
if(it->npc_kind == NPC_AngelTotem)
{
to_use->go_to_animation = MD_S8Lit("Die Backwards");
to_use->next_animation_isnt_looping = true;
}
else if (LenV2(it->vel) > 0.5f)
{
to_use->go_to_animation = MD_S8Lit("Running");
draw_thing((DrawnThing){.mesh = &mesh_angel_totem, .t = draw_with, .outline = gete(gs.player->interacting_with) == it});
}
else
{
to_use->go_to_animation = MD_S8Lit("Idle");
}
Armature *to_use = 0;
switch (it->npc_kind)
{
case NPC_Daniel:
to_use = &farmer_armature;
break;
case NPC_Raphael:
to_use = &man_in_black_armature;
break;
case NPC_Angel:
to_use = &angel_armature;
break;
case NPC_Player:
to_use = &player_armature;
break;
case NPC_nobody:
case NPC_AngelTotem:
case NPC_Devil:
case NPC_PreviousPlayer1:
case NPC_PreviousPlayer2:
assert(false);
break;
}
if (it->killed)
{
assert(false);
break;
}
if (it->killed)
{
to_use->go_to_animation = MD_S8Lit("Die Backwards");
to_use->next_animation_isnt_looping = true;
}
else if (LenV2(it->vel) > 0.5f)
{
to_use->go_to_animation = MD_S8Lit("Running");
}
else
{
to_use->go_to_animation = MD_S8Lit("Idle");
}
draw_thing((DrawnThing){.armature = to_use, .t = draw_with, .outline = gete(gs.player->interacting_with) == it});
draw_thing((DrawnThing){.armature = to_use, .t = draw_with, .outline = gete(gs.player->interacting_with) == it});
if (gete(it->aiming_shotgun_at))
{
Transform shotgun_t = draw_with;
shotgun_t.offset.y += 0.7f;
shotgun_t.scale = V3(4, 4, 4);
shotgun_t.rotation = rot_on_plane_to_quat(AngleOfV2(SubV2(gete(it->aiming_shotgun_at)->pos, it->pos)));
draw_thing((DrawnThing){.mesh = &mesh_shotgun, .t = shotgun_t});
if (gete(it->aiming_shotgun_at))
{
Transform shotgun_t = draw_with;
shotgun_t.offset.y += 0.7f;
shotgun_t.scale = V3(4, 4, 4);
shotgun_t.rotation = rot_on_plane_to_quat(AngleOfV2(SubV2(gete(it->aiming_shotgun_at)->pos, it->pos)));
draw_thing((DrawnThing){.mesh = &mesh_shotgun, .t = shotgun_t});
}
}
}
}
@ -6131,7 +6157,7 @@ void frame(void)
if(gs.assigned_objective)
{
float mission_font_scale = 1.0f;
MD_String8 mission_text = FmtWithLint(frame_arena, "Your mission: %s %s", verbs[0], characters[gs.objective.who_to_kill].name);
MD_String8 mission_text = FmtWithLint(frame_arena, "Your mission: %.*s", MD_S8VArg(TextChunkString8(gs.objective.description)));
float button_height = 100.0f;
float vert = button_height*0.5f;
draw_centered_text((TextParams){false, mission_text, V2(screen_size().x * 0.5f, screen_size().y * 0.25f + vert), blendalpha(WHITE, visible), mission_font_scale, .use_font = &font_for_text_input, .layer = LAYER_ANGEL_SCREEN});
@ -6899,6 +6925,7 @@ ISANERROR("Don't know how to do this stuff on this platform.")
|| (it->npc_kind == NPC_Angel && gs.no_angel_screen)
|| !npc_does_dialog(it) // not sure what's up with this actually, potentially remove
|| !MD_S8Match(it->current_room_name, gs.player->current_room_name, 0)
|| it->npc_kind == NPC_AngelTotem
;
if (it->perceptions_dirty && doesnt_prompt_on_dirty_perceptions)
{
@ -7057,6 +7084,10 @@ ISANERROR("Don't know how to do this stuff on this platform.")
{
if (closest_interact_with->is_npc)
{
if(closest_interact_with->npc_kind == NPC_AngelTotem)
{
transition_to_room(&gs, &level_threedee, MD_S8Lit("StartingRoom"));
}
// begin dialog with closest npc
gs.player->talking_to = frome(closest_interact_with);
begin_text_input();
@ -7129,32 +7160,6 @@ ISANERROR("Don't know how to do this stuff on this platform.")
pressed = before_gameplay_loops;
// check if game objective has been fulfilled
{
if(gs.no_angel_screen)
{
bool fulfilled = false;
assert(gs.assigned_objective);
switch(gs.objective.verb)
{
case KILL:
ENTITIES_ITER(gs.entities)
{
if(it->npc_kind == gs.objective.who_to_kill && it->killed)
{
fulfilled = true;
break;
}
}
break;
}
if(fulfilled)
{
gs.assigned_objective = false;
transition_to_room(&gs, &level_threedee, MD_S8Lit("StartingRoom"));
}
}
}
#ifdef DEVTOOLS

@ -96,8 +96,7 @@ typedef struct TextChunkList
typedef struct GameplayObjective
{
ObjectiveVerb verb;
NpcKind who_to_kill;
TextChunk description;
} GameplayObjective;
typedef enum
@ -113,19 +112,6 @@ typedef struct
GameplayObjective objective;
} ActionArgument;
// returns ai understandable, human readable name, on the arena, so not the enum name
MD_String8 action_argument_string(MD_Arena *arena, ActionArgument arg)
{
switch(arg.kind)
{
case ARG_CHARACTER:
return FmtWithLint(arena, "%s", characters[arg.targeting].name);
break;
case ARG_OBJECTIVE:
return FmtWithLint(arena, "%s %s", verbs[arg.objective.verb], characters[arg.objective.who_to_kill].enum_name);
}
return (MD_String8){0};
}
typedef struct Action
{
@ -220,6 +206,21 @@ void chunk_from_s8(TextChunk *into, MD_String8 from)
into->text_length = (int)from.size;
}
// returns ai understandable, human readable name, on the arena, so not the enum name
MD_String8 action_argument_string(MD_Arena *arena, ActionArgument arg)
{
switch(arg.kind)
{
case ARG_CHARACTER:
return FmtWithLint(arena, "%s", characters[arg.targeting].name);
break;
case ARG_OBJECTIVE:
return FmtWithLint(arena, "%.*s", MD_S8VArg(TextChunkString8(arg.objective.description)));
}
return (MD_String8){0};
}
typedef struct RememberedError
{
struct RememberedError *next;
@ -473,21 +474,6 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, GameState *gs, Entity *e, Ca
AddFmt("\n");
if(e->npc_kind == NPC_Angel)
{
AddFmt("Acceptable verbs for assigning a gameplay objective:\n");
ARR_ITER(char*, verbs)
{
AddFmt("%s\n", *it);
}
AddFmt("\n");
AddFmt("The characters in the game you can use when you assign your gameplay objective:\n");
AddFmt("Raphael\n");
AddFmt("Daniel\n");
AddFmt("\n");
}
AddFmt("The actions you can perform, what they do, and the arguments they expect:\n");
AvailableActions can_perform;
fill_available_actions(gs, e, &can_perform);
@ -507,7 +493,6 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, GameState *gs, Entity *e, Ca
// going through memories, I'm going to accumulate human understandable sentences for what happened in current_list.
// when I see an 'i_said_this' memory, that means I flush. and add a new assistant node.
// write a new human understandable sentence or two to current_list
if (!it->context.i_said_this) {
if(in_drama_memories && !it->context.drama_memory)
@ -548,6 +533,9 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, GameState *gs, Entity *e, Ca
case ACT_assign_gameplay_objective:
AddFmt("%.*s assigned a definitive game objective to %.*s", HUMAN(it->context.author_npc_kind), HUMAN(it->context.talking_to_kind));
break;
case ACT_award_victory:
AddFmt("%.*s awarded victory to %.*s", HUMAN(it->context.author_npc_kind), HUMAN(it->context.talking_to_kind));
break;
}
}
if(it->speech.text_length > 0)
@ -658,51 +646,14 @@ void parse_action_argument(MD_Arena *error_arena, MD_String8 *cur_error_message,
else if (arg_is_gameplay_objective)
{
out->kind = ARG_OBJECTIVE;
MD_String8List split = MD_S8Split(scratch.arena, action_argument_str, 1, &MD_S8Lit(" "));
if (split.node_count != 2)
if(action_argument_str.size >= MAX_SENTENCE_LENGTH)
{
*cur_error_message = FmtWithLint(error_arena, "Gameplay objective must follow this format: `VERB ACTION`, with a space between the verb and the action. You gave a response with %d words in it, which isn't the required amount, 2", (int)split.node_count);
}
if (cur_error_message->size == 0)
{
MD_String8 verb = split.first->string;
bool found = false;
MD_String8List available_verbs = {0};
ARR_ITER_I(char *, verbs, verb_enum)
{
MD_S8ListPush(scratch.arena, &available_verbs, MD_S8CString(*it));
if (MD_S8Match(MD_S8CString(*it), verb, 0))
{
(*out).objective.verb = verb_enum;
found = true;
}
}
if (!found)
{
MD_String8 verbs_str = MD_S8ListJoin(scratch.arena, available_verbs, &(MD_StringJoin){.mid = MD_S8Lit(" ")});
*cur_error_message = FmtWithLint(error_arena, "The gameplay verb you provided '%.*s' doesn't match any of the gameplay verbs available to you: %.*s", MD_S8VArg(verb), MD_S8VArg(verbs_str));
}
MD_String8 trimmed = MD_S8Substring(action_argument_str, action_argument_str.size - MAX_SENTENCE_LENGTH/2, action_argument_str.size);
*cur_error_message = FmtWithLint(error_arena, "What you said for your action argument, '%.*s...' is WAY too big for the game to handle, it can be a maximum of %d characters, but you output %d!.", MD_S8VArg(trimmed), MAX_SENTENCE_LENGTH, (int)action_argument_str.size);
}
if (cur_error_message->size == 0)
if(cur_error_message->size == 0)
{
MD_String8 subject = split.first->next->string;
MD_String8 verb = split.first->string;
bool found_npc = false;
for (int i = 0; i < ARRLEN(characters); i++)
{
if (MD_S8Match(MD_S8CString(characters[i].name), subject, 0))
{
found_npc = true;
(*out).objective.who_to_kill = i;
}
}
if (!found_npc)
{
*cur_error_message = FmtWithLint(error_arena, "Argument for gameplay verb `%.*s` you gave is `%.*s`, which doesn't exist in the game so is invalid", MD_S8VArg(verb), MD_S8VArg(subject));
}
chunk_from_s8(&out->objective.description, action_argument_str);
}
}
else

@ -1,5 +1,9 @@
Urgent:
- angel doesn't have characters near you
- Remove (MD_) prefix from project
- Furcate functionality in npc entity into feature flags, streamline creation of entities/things from blender -> game, things. Make rock thing.
- Fix camera angle on forest map, probably introduce custom camera angles per map
- In debugtools show number of arenas allocated, and total megabytes allocated in arenas

Loading…
Cancel
Save