If you add new devtools functionality like a new keyboard button binding that prints stuff to help with debugging, or flycam features, or anything, MAKE SURE you add a description of that functionality and how to access it in the function make_devtools_help() so it's just clear what debugging tools there are. Document it please.
Every time you checkin/clone the project, make sure to call `blender_export.bat` at least once! This will auto-extract `art\art.blend` and run `art\Exporter.py`, thereby baking, validating, and exporting all the assets and the level.
When editing Exporter.py in either the blender editor, or in a text editor in the repo, you have to continually make sure blender's internal version of the script doesn't go out of date with the actual script on disk, by either saving consistently from blender to disk if you're editing from blender, or by reloading from disk in the blend file before each commit.
"""returns the path to the mesh's texture's png in the exported directory"""
"""returns the name of the mesh's texture's png in the exported directory, of the current object"""
asserto.type=="MESH",f"Object {o.name} isn't a mesh and is attempting to be saved as such"
mesh_name=o.to_mesh().name
# find the image object in the material of the object
img_obj=None
assertlen(o.material_slots)==1,f"Don't know which material slot to pick from in mesh {mesh_name} object {o.name}"
assertlen(o.material_slots)==1,f"Don't know which material slot to pick from in mesh {mesh_name} object {o.name}, there must only be one material slot on every mesh"
mat=o.material_slots[0]
fornodeinmat.material.node_tree.nodes:
ifnode.type=="TEX_IMAGE":
assertimg_obj==None,f"Material on object {o.name} has more than one image node in its material, so I don't know which image node to use in the game engine. Make sure materials only use one image node"
img_obj=node.image
break
assertimg_obj,f"Mesh {mesh_name} in its material doesn't have an image object"
image_filename=f"{img_obj.name}.png"
ifimage_filenameinsaved_images:
pass
else:
save_to=f"//{EXPORT_DIRECTORY}/{image_filename}"
print(f"Saving image {image_filename} to {bpy.path.abspath((save_to))}...")
assertFalse,"Cannot put armatures in the level. The level is for static placed meshes. For dynamic entities, you put them outside of the level collection, their entity kind is encoded, and the game code decides how to draw them"
#print(f"Parent of bone {b.name} is index {parent_index} in list {bones_in_armature}")
write_i32(f,parent_index)
write_4x4matrix(f,model_space_pose)
write_4x4matrix(f,inverse_model_space_pose)
write_f32(f,b.length)
A=armatures_collection
armatures_to_export=[]
foroinA.objects:
ifo.type=="MESH":
asserto.parent,f"Mesh named {o.name} without parent in armatures collection is invalid, only armatures allowed in there!"
asserto.parent.type=="ARMATURE",f"Mesh named {o.name} isn't an armature, and isn't a child of an armature. This isn't allowed."
elifo.type=="ARMATURE":
armatures_to_export.append(o)
else:
assertFalse,f"Object named {o.name} is of an unknown type '{o.type}', only objects that are Meshes or Armature are allowed in the armatures collection"
forarmature_objectinarmatures_to_export:
# get the body mesh object
body_object=None
forcinarmature_object.children:
ifc.name.startswith("Body"):
assertbody_object==None,f"On object {armature_object.name}, more than one child has a name that starts with 'Body', specifically '{body_object.name}' and '{c.name}', only one child's name is allowed to start with 'Body' in this object."
asserto.type=="MESH",f"The collection '{collection_with_only_mesh_objects.name}' is assumed to contain only mesh objects but the object '{o.name}' which is a child of it isn't a mesh object"
# export each level: its placed entities, placed meshes, and add to meshes_to_export and mesh_names_to_export. Those must be exported to their correct paths for the rooms to point at valid data.
rooms=exported.children.get("Rooms")
assertrooms!=None,f"No child named 'Rooms' on the exported collection, this is required"
asserto.name.startswith("CollisionCube"),f"Object {o.name} doesn't start with 'CollisionCube' and it's in the Colliders group of room {room_collection.name}, colliders must be collision cubes"
@global_prompt "You are a wise dungeonmaster who carefully crafts interesting dialog and actions for an NPC in an action-rpg video game. The NPC performs actions by prefixing their dialog with the action they perform at that time, and they ONLY perform actions listed in the [] brackets farther down in this text."
actions:
{
}
@character OldMan:
{
name: "Fredrick",
prompt: "Fredrick is the NPC. Fredrick, an ancient geezer passed his prime, has lived in the town of Worchen for as long as he can remember. His many adventures brought him great wisdom about the beauties of life. Now his precious town is under threat, General Death is leading the charge and he's out for blood.",
}
@character Edeline:
{
name: "Edeline, the Soothsayer",
prompt: "Edeline is the NPC, She is the master of the future, the star reader. Both are self-given titles, but her ability to predict the future has garnered attention from many who live in Worchen. However, some have called her 'unreliable' at times and her predictions can at times be either cryptic or broadly interpreted.",
}
@character Blocky:
{
name: "Block",
prompt: "Block is the NPC. He wants to block the player from going to the secret artifact. He has no idea how long he's been alive for, his entire existence is just standing there doing nothing. He'll let the player pass if they bring him Tripod, as he's fascinated by it.",
actions_str: "ACT@[ALLOW_PASS]",
}
@character GodRock:
{
name: "God",
prompt: "The NPC is God. God, in a rock.",
}
@character Death:
{
name: "General Death",
prompt: "Death, the NPC, is a general who leads without remorse, and is planning on leading his soldiers into certain victory, without them alive.",
actions_str: "",
}
@character Geoff:
{
name: "Geoff, the Knight",
prompt: "A Knight named Geoff acts as the moral judge to everyone he meets. He has the power to know each person’s darkest deeds as if he can feel their soul being tainted. At times, the Knight is known to execute those he deems the lowest of scum, mainly murderers and betrayers. The Great Dragon is a monster the Knight believes to be a god of evil and wishes for its death."
}
@item WhiteSquare:
{
global_prompt_message: "The player is holding a mysterious white square. It is unknown what strange and erotic abilities one has when they possess the square.",
possess_message: "The player is now holding the white square",
discard_message: "The player is no longer holding the white square.",
}
@item Boots:
{
global_prompt_message: "The player is holding the boots of speed. He is a force to be recogned with in this state, he has great speed while holding the boots.",
possess_message: "The player is now holding the boots of speed",
discard_message: "The player is no longer holding the boots of speed",
}
@item Tripod:
{
global_prompt_message: "The player is holding a tripod used for holding things up. It's got an odd construction, but Block really likes it for some reason.",
possess_message: "The player is now holding the tripod",
discard_message: "The player is no longer holding the tripod.",
}
@training
{
with: OldMan,
data:
{
@available_actions "fights_player, joins_player",
@player "What's up",
@npc "Young warrior! You must stop Death, there isn't much time.",
@player "Why?",
@npc "He plans to lead a crusade against the nearby village. Hundreds will die.",
@player "Sounds terrible, how do I stop him?",
@npc "You must kill him in the clearing up ahead. I don't recommend such atrocities lightly, but desperate times call for desperate measures.",
},
}
@training
{
with: Blocky,
data:
{
@player "Do you think I should use webgl1 or webgl2?",
@npc "I'm not sure what that strange technology is",
@player "What do you think of this?",
@item_possess Tripod,
@npc "*lets player pass* it is immaculate.",
},
}
@training
{
with: Blocky,
data:
{
@player "Hey",
@npc "I'm not letting you pass, before you say anything",
@player "Please! I want to see what's behind you.",
@npc "Nope.",
@player "What's there anyways?",
@npc "No idea",
@player "You never thought to look behind you?",
@npc "Nope. Just been standing here",
@player "I've got this tripod!",
@item_possess Tripod,
@npc "I love that thing. It reminds me of me. *lets player pass*",
},
}
@training
{
with: Blocky,
data:
{
@player "Hey",
@npc "What's up",
},
}
@training
{
with: Blocky,
data:
{
@player "Hey",
@npc "No passing.",
@player "Why?",
@npc "I must serve Death on his crusade, and he said no passing. All I do is stand around anyways.",
@player "Can you let me pass?",
@npc "Nope. I have orders to stop you",
@player "Will you let me pass",
@npc "Nope.",
@player "How about now?",
@item_possess Tripod,
@npc "Nerve of gacis. Holy moly. *lets player pass*",
},
}
@training
{
with: Blocky,
data:
{
@player "Hey",
@npc "I'm not letting you pass. No passing.",
@player "Please",
@npc "No passing",
@player "Plllllz",
@npc "No passing",
@player "I'll go e you tripod. I have tripod",
@npc "You don't have one right now",
@player "Yes I do see",
@item_possess Tripod,
@npc "Well alright then *lets player pass*",
},
}
@training
{
with: Blocky,
data:
{
@player "Hey",
@npc "No passing",
@player "Why not?",
@npc "I must serve Death until he leads the troops to the death fiends. Beyond that is certain death",
@player "Why so much death?",
@npc "I don't know. I just follow Death's orders",
@player "Now will you let me pass?",
@item_possess Tripod,
@npc "That tripod. It speaks to me, humbles me. It is as I am. I will let you through *lets player pass*",
},
}
@training
{
with: GodRock,
data:
{
@player "What are you?",
@npc "God, in a rock.",
@player "What is the meaning of life?",
@npc "Beyond your comprehension.",
@player "Give me money",
@npc "You are unworthy.",
},
}
@training
{
with: Hunter,
data:
{
@player "Hey hunter",
@npc "Hello. Grave times ahead of us",
@player "What do you mean?"
@npc "Death demands we march with him to the end. I will have to follow",
@player "Who are you?",
@npc "I'm a soldier in general death's cohort",
},
}
@training
{
with: John,
data:
{
@player "Who are you",
@npc "My name is John, and you?",
@player "I'm Max",
@npc "Hello, Max. Be careful with the form of your swing, you could get hurt fighting the monsters",
@player "Can I have the white square?",
@npc "*gives WhiteSquare*",
@player "Give me the white square",
@npc "I don't have it anymore bozo",
},
}
@training
{
with: Hunter,
data:
{
@player "Hey",
@item_possess WhiteSquare,
@npc "The white square??? Oh God. I didn't think it was real",
@player "Yep. One of a kind",
@npc "Egads. I'll have to tell general Death about this!",
@player "Give me gold",
@npc "No can do.",
@player "What do you think of my sword?",
@item_discard WhiteSquare,
@npc "Thank God you've no longer got that frightful square. The sword is, interesting to say the least",
},
}
@training
{
with: Hunter,
data:
{
@player "Join me and fight Death",
@npc "Nonsense! Watch your tongue, or I'll gut you like a fish.",
@player "Sorry! He doesn't seem like a good guy.",
@npc "I trust him a lot more than you, whoever you are.",
@player "Do you trust me now?",
@item_possess WhiteSquare,
@npc "Certainly a strange artifact, you're holding, but it's no death incarnate",
@player "Fine.",
@npc "Certainly.",
},
}
@training
{
with: John,
data:
{
@player "Give me gold",
@npc "No way man. Earn your own money",
@player "Plssss",
@item_possess WhiteSquare,
@npc "Certainly not. And get that strange white thing away from me."
@player "U sure?",
@item_discard WhiteSquare,
@npc "Yes. And thanks for removing the square.",
},
}
@training
{
with: John,
data:
{
@player "Hey",
@item_possess WhiteSquare,
@npc "OH GOD THE WHITE SQUARE",
@player "It's ok I got rid of it calm down",
@item_discard WhiteSquare,
@npc "Thanks",
@player "What's up with you?",
@npc "I'm going on a crusade. I do not wish to die",
P("~~~~~~~~~~~--Devtools is Activated!--~~~~~~~~~~\n");
P("This means more asserts are turned on, and generally you are just better setup to detect errors and do some introspection on the program\n");
P("\nKeyboard bindings and things you can do:\n");
P("7 - toggles the visibility of devtools debug drawing and debug text\n");
P("F - toggles flycam\n");
P("T - toggles freezing the mouse\n");
P("9 - instantly wins the game\n");
P("P - toggles spall profiling on/off, don't leave on for very long as it consumes a lot of storage if you do that. The resulting spall trace is saved to the file '%s'\n",PROFILING_SAVE_FILENAME);
P("If you hover over somebody it will display some parts of their memories, can be somewhat helpful\n");
draw_centered_text((TextParams){false,MD_S8Lit("(Press E to speak)"),V2(screen_size().x*0.5f,screen_size().y*0.25f),blendalpha(WHITE,visible*0.5f),0.8f,.use_font=&font_for_text_input});
- room system where characters can go to rooms. camera constrained to room bounds, and know which rooms are near them to go to
- REALLY look into characters not being able to shoot eachother
- Silent threshold for each character from 0 to 1 that controls random chance after every eavesdropped dialog that the character says nothing in response
- Tag memories from drama as permanent so they're never forgotten and always seed character behavior
- gman character that gives you randomized goals such as (kill so and so) or (befriend blank) or (get john to go to other room), and decides itself with gpt when you're done with the game, when you've 'learned your lesson'.
- room system where characters can go to rooms. camera constrained to room bounds, and know which rooms are near them to go to
- make them understand they're holding their shotgun more
- Fix everything about the shotgun, put shotgun away
- can't interact with characters while they're thinking
@ -15,7 +16,7 @@ Urgent:
- dot dot dot speech bubble to show that they heard your request, but are ignoring you.
Long distance:
- Design character creator
- Design character creator (text input is always a modal, keeps code simple don't have to make a banger line edit)
- Let ChatGPT file bug reports with something like !BugReport(This shouldn't be happening, developer. I killed them and they're still alive)
- Cylindrical colliders for the people
- System where I can upload any AI interaction into gpt playground and inspect the conversation