Add Hiisuya's New Actor Script (#156)

* add actor script

* edit readme

* changes (-no for now)
This commit is contained in:
Recardo
2024-12-03 16:51:02 +01:00
committed by GitHub
parent 2f03115758
commit e0f4819964
6 changed files with 344 additions and 0 deletions

View File

@@ -32,6 +32,7 @@ This project is using the following tools:
- [gzinject](https://github.com/krimtonz/gzinject), *injector for gz*, by krimtonz
- [z64compress](https://github.com/z64tools/z64compress), *the fastest Zelda 64 rom compressor*, by z64tools
- [Flips](https://github.com/Alcaro/Flips), *patcher for IPS and BPS files*, by Alcaro
- [New Actor Script](https://github.com/hiisuya/oot_new_actor), *Python script and files to automate creating a new actor*, by hiisuya
**Note: This repository does not include any of the assets necessary to build the ROM. A prior copy of the game is required to extract the needed assets.**
@@ -61,6 +62,8 @@ This project includes an example scene, available if ``INCLUDE_EXAMPLE_SCENE`` i
This also includes an example cutscene, playable in the example scene when holding ``L`` + ``R`` and pressing ``A``.
Use ``./new_actor.py --help`` for instructions on easily adding a new actor to the game.
## Changing build options
The project Makefile is fairly configurable and can be used to build other versions of the game or prepare the repo for modding.
@@ -77,6 +80,7 @@ Most discussions happen on our [Discord Server](https://discord.gg/brETAakcXr),
List of every HackerOoT contributors, from most recent to oldest contribution:
- hiisuya
- Zeldaboy14
- Reonu
- Thar0

264
new_actor.py Normal file
View File

@@ -0,0 +1,264 @@
#!/usr/bin/env python3
import os
import re
import sys
import shutil
import argparse
import textwrap
from os import path
from pathlib import Path
createObject = True
useModAssets = False
useActorProfile = False
def convertActorName(name: str):
actorSpec = "_".join(it[0].upper() + it[1:] for it in name.split("_"))
actorDefine = "ACTOR_" + actorSpec.upper()
objectSpec = "object" + "_" + name.lower()
objectDefine = objectSpec.upper()
actorFileName = "z_" + name.lower()
return dict(
actorSpec = actorSpec,
actorDefine = actorDefine,
objectSpec = objectSpec,
objectDefine = objectDefine,
actorFileName = actorFileName
)
# check if folders exists, if not, create them and necessary files
def makeFilesAndFolders(folderName, fileName, objectName):
filePathActor = Path(path.curdir, "src/overlays/actors", f"ovl_{folderName}")
filePathObject = Path(path.curdir, ("mod_assets/objects" if useModAssets else "assets/objects"), f"{objectName}")
filePaths = [filePathActor, filePathObject]
if path.exists(filePathActor): sys.exit("Aborting... Actor folder already exists")
if path.exists(filePathObject) and createObject == True: sys.exit("Aborting... Object folder already exists")
for filePath in filePaths:
if filePath == filePathObject and not createObject:
break
os.mkdir(filePath)
with open(Path(filePath, f'{(fileName if filePath == filePathActor else objectName)}.c'), "w") as file:
file.write("'this is a c file... hopefully!'")
with open(Path(filePath, f'{(fileName if filePath == filePathActor else objectName)}.h'), "w") as file:
file.write("'and this is an h file... hopefully!'")
def isInFile(text, filePath):
with open(filePath) as fd:
for line_num, line in enumerate(fd.readlines()):
if text in line:
return line_num
return None
# write actor define after last entry, not at end of file
def addToTables(actorSpec, actorDefine, objectSpec, objectDefine):
filePathActor = Path(path.curdir, "include/tables/actor_table.h")
filePathObject = Path(path.curdir, "include/tables/object_table.h")
filePaths = [filePathActor, filePathObject]
actorLineNum = 0
if path.exists(filePathActor) and path.exists(filePathObject):
inFileActor = isInFile(actorSpec, filePathActor)
inFileObject = isInFile(objectSpec, filePathObject)
if inFileActor or inFileObject:
if inFileActor: print(f"Actor Define already exists! {actorDefine} was found on line {inFileActor}")
if inFileObject: print(f"Object Define already exists! {objectDefine} was found on line {inFileObject}")
sys.exit()
for filePath in filePaths:
if filePath == filePathActor: newLine = str(f'DEFINE_ACTOR({actorSpec}, {actorDefine}, ACTOROVL_ALLOC_NORMAL, "{actorSpec}")')
else:
if createObject == False:
break
newLine = str(f'DEFINE_OBJECT({objectSpec}, {objectDefine})')
with open(filePath, "r+") as file:
file_data = file.readlines()
num_lines = sum(1 for _ in file_data)
shouldAppend = 2
num = 0
while shouldAppend > 1 :
for line in reversed(list(open(filePath))):
if num == 0:
if "DEFINE" in line:
file.seek(0, 2)
file.seek(file.tell() - 1)
last_char = file.read()
if last_char == '\n':
file.truncate(file.tell() - 1)
shouldAppend = True
break
else:
if "DEFINE" in line:
line_num = file_data.index(line) + 1
shouldAppend = False
break
num += 1
if not shouldAppend:
file_data[line_num] = newLine
file.seek(0)
for line in file_data:
file.write(line)
if shouldAppend:
with open(filePath, "a") as file:
file.write("\n" + newLine)
line_num = num_lines + 1
if filePath == filePathActor: actorLineNum = line_num
return actorLineNum
def addToSpec(actorSpec, actorFileName, objectSpec, actorFileLine):
# read tables and get actor located specifically before the most recently added one
# use that to determine where in the spec file to write
filePathActor = Path(path.curdir, "include/tables/actor_table.h")
filePathSpec = Path(path.curdir, "spec")
useNewBuild = False
with open(filePathActor, "r") as file:
file_data = file.readlines()
prevActor = file_data[actorFileLine - 2]
prevActor = prevActor[13:(len(actorSpec) + 13)]
with open(filePathSpec, "r") as file:
file_data = file.readlines()
inFile = isInFile('name "gameplay_keep"', filePathSpec)
if inFile:
startLineActor = inFile
if createObject:
inFile = isInFile('name "g_pn_01"', filePathSpec)
if inFile:
startLineObject = inFile
useNewBuild = isInFile('$(BUILD_DIR)/', filePathSpec)
with open(filePathSpec, "w") as file:
if createObject:
file_data[startLineObject - 2] = textwrap.dedent(
f"""
beginseg
name "{objectSpec}"
romalign 0x1000
include "{"$(BUILD_DIR)" if useNewBuild else "build"}/assets/objects/{objectSpec}/{objectSpec}.o"
number 6
endseg
""")
file_data[startLineActor - 2] = textwrap.dedent(
f"""
beginseg
name "ovl_{actorSpec}"
include "{"$(BUILD_DIR)" if useNewBuild else "build"}/src/overlays/actors/ovl_{actorSpec}/{actorFileName}.o"
include "{"$(BUILD_DIR)" if useNewBuild else "build"}/src/overlays/actors/ovl_{actorSpec}/ovl_{actorSpec}_reloc.o"
endseg
""")
file.seek(0)
for line in file_data:
file.write(line)
def completeFiles(actorSpec, actorDefine, actorFileName, objectSpec, objectDefine):
filePathActor = Path(path.curdir, "src/overlays/actors", f"ovl_{actorSpec}")
filePathObject = Path(path.curdir, f"{('mod_assets' if useModAssets else 'assets')}/objects", f"{objectSpec}")
# ACTOR
shutil.copyfile(Path(path.curdir, "template_files/z_actor.c"), f"{filePathActor}/{actorFileName}.c")
shutil.copyfile(Path(path.curdir, "template_files/z_actor.h"), f"{filePathActor}/{actorFileName}.h")
with open(f"{filePathActor}/{actorFileName}.c", 'r') as file:
dataC = file.read()
dataC = dataC.replace("{actorSpec}", actorSpec)
dataC = dataC.replace("{actorDefine}", actorDefine)
dataC = dataC.replace("{actorFileName}", actorFileName)
dataC = dataC.replace("{objectDefine}", objectDefine if createObject else "OBJECT_GAMEPLAY_KEEP")
dataC = dataC.replace("{actorVar}", "ActorProfile" if useActorProfile else "ActorInit")
dataC = dataC.replace("{actorInitVar}", "Profile" if useActorProfile else "InitVars")
with open(f"{filePathActor}/{actorFileName}.c", 'w') as file:
file.write(dataC)
with open(f"{filePathActor}/{actorFileName}.h", 'r') as file:
dataH = file.read()
dataH = dataH.replace("{actorSpec}", actorSpec)
dataH = dataH.replace("{actorFileNameCaps}", actorFileName.upper())
dataH = dataH.replace("{includeObject}", f'\n#include "assets/objects/{objectSpec}/{objectSpec}.h"' if createObject else "")
with open(f"{filePathActor}/{actorFileName}.h", 'w') as file:
file.write(dataH)
if createObject:
# OBJECT
shutil.copyfile(Path(path.curdir, "template_files/object.c"), f"{filePathObject}/{objectSpec}.c")
shutil.copyfile(Path(path.curdir, "template_files/object.h"), f"{filePathObject}/{objectSpec}.h")
with open(f"{filePathObject}/{objectSpec}.c", 'r') as file:
dataC = file.read()
dataC = dataC.replace("{objectSpec}", objectSpec)
with open(f"{filePathObject}/{objectSpec}.c", 'w') as file:
file.write(dataC)
with open(f"{filePathObject}/{objectSpec}.h", 'r') as file:
dataH = file.read()
dataH = dataH.replace("{objectSpec}", objectSpec)
dataH = dataH.replace("{objectSpecCap}", objectSpec.upper())
with open(f"{filePathObject}/{objectSpec}.h", 'w') as file:
file.write(dataH)
def main():
names = convertActorName(actorName)
print(f'Creating new Actor: {names["actorSpec"]}')
makeFilesAndFolders(names["actorSpec"], names["actorFileName"], names["objectSpec"])
actorFileLine = addToTables(names["actorSpec"], names["actorDefine"], names["objectSpec"], names["objectDefine"])
addToSpec(names["actorSpec"], names["actorFileName"], names["objectSpec"], actorFileLine)
completeFiles(names["actorSpec"], names["actorDefine"], names["actorFileName"], names["objectSpec"], names["objectDefine"])
print(f'{names["actorSpec"]} created!')
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-n", "--name", required = True, help = "Name of actor. Use alphanumeric and/or _ as valid characters", default = "")
parser.add_argument("-no", "--noobject", required = False, help = "Optional. Use if you do not want to create an object for this actor", action='store_false')
args = parser.parse_args()
if not os.path.exists(Path(path.curdir, "src") or not os.path.exists(Path(path.curdir, "spec"))):
sys.exit("new_actor.py does not seem to be in the repo root directory.\nPut this file, and the template_files folder into the root (where the src and assets/mod_assets folders are located)!")
else:
if not os.path.exists(Path(path.curdir, "template_files")):
sys.exit("template_files folder not found.\nMake sure the new_actor.py file and template_files folder are in your repo root folder!")
if args.noobject == False:
createObject = args.noobject
if os.path.exists(Path(path.curdir, "mod_assets/objects")):
useModAssets = True
if not os.path.exists(Path(path.curdir, "assets/objects")) and createObject:
os.makedirs(Path(path.curdir, "assets/objects"))
if isInFile('ActorProfile', "include/z64actor.h"):
useActorProfile = True
if re.match(r'^[A-Za-z0-9_-]+$', args.name):
actorName = args.name.replace("-", "_")
main()
else:
sys.exit("Invalid name. Please use Alphanumeric characters!")

4
template_files/object.c Normal file
View File

@@ -0,0 +1,4 @@
#include "ultra64.h"
#include "z64.h"
#include "macros.h"
#include "{objectSpec}.h"

4
template_files/object.h Normal file
View File

@@ -0,0 +1,4 @@
#ifndef {objectSpecCap}_H
#define {objectSpecCap}_H
#endif

52
template_files/z_actor.c Normal file
View File

@@ -0,0 +1,52 @@
/*
* File: {actorFileName}.c
* Overlay: ovl_{actorSpec}
* Description: Custom Actor
*/
#include "{actorFileName}.h"
#define FLAGS (0)
void {actorSpec}_Init(Actor* thisx, PlayState* play);
void {actorSpec}_Destroy(Actor* thisx, PlayState* play);
void {actorSpec}_Update(Actor* thisx, PlayState* play);
void {actorSpec}_Draw(Actor* thisx, PlayState* play);
void {actorSpec}_DoNothing({actorSpec}* this, PlayState* play);
{actorVar} {actorSpec}_{actorInitVar} = {
{actorDefine},
ACTORCAT_PROP,
FLAGS,
{objectDefine},
sizeof({actorSpec}),
(ActorFunc){actorSpec}_Init,
(ActorFunc){actorSpec}_Destroy,
(ActorFunc){actorSpec}_Update,
(ActorFunc){actorSpec}_Draw,
};
void {actorSpec}_Init(Actor* thisx, PlayState* play) {
{actorSpec}* this = ({actorSpec}*)thisx;
this->actionFunc = {actorSpec}_DoNothing;
}
void {actorSpec}_Destroy(Actor* thisx, PlayState* play) {
{actorSpec}* this = ({actorSpec}*)thisx;
}
void {actorSpec}_Update(Actor* thisx, PlayState* play) {
{actorSpec}* this = ({actorSpec}*)thisx;
this->actionFunc(this, play);
}
void {actorSpec}_Draw(Actor* thisx, PlayState* play) {
{actorSpec}* this = ({actorSpec}*)thisx;
}
void {actorSpec}_DoNothing({actorSpec}* this, PlayState* play) {
}

16
template_files/z_actor.h Normal file
View File

@@ -0,0 +1,16 @@
#ifndef {actorFileNameCaps}_H
#define {actorFileNameCaps}_H
#include "ultra64.h"
#include "global.h"{includeObject}
struct {actorSpec};
typedef void (*{actorSpec}ActionFunc)(struct {actorSpec}*, PlayState*);
typedef struct {actorSpec} {
Actor actor;
{actorSpec}ActionFunc actionFunc;
} {actorSpec};
#endif