custom levels :))))

This commit is contained in:
dominicentek 2023-09-04 11:46:19 +02:00
parent 2d8e4f0b8a
commit aae9b7a7bc
14 changed files with 347 additions and 10 deletions

View file

@ -0,0 +1,36 @@
#include "src/game/envfx_snow.h"
#include "src/game/custom_level.h"
const GeoLayout sa_area_3_geo[] = {
GEO_NODE_START(),
GEO_OPEN_NODE(),
GEO_ASM(LAYER_OPAQUE, geo_custom_level),
GEO_CLOSE_NODE(),
GEO_RETURN(),
};
const GeoLayout sa_area_3[] = {
GEO_NODE_SCREEN_AREA(10, SCREEN_WIDTH/2, SCREEN_HEIGHT/2, SCREEN_WIDTH/2, SCREEN_HEIGHT/2),
GEO_OPEN_NODE(),
GEO_ZBUFFER(0),
GEO_OPEN_NODE(),
GEO_NODE_ORTHO(100.0000),
GEO_OPEN_NODE(),
GEO_BACKGROUND_DYN(geo_skybox_main),
GEO_CLOSE_NODE(),
GEO_CLOSE_NODE(),
GEO_ZBUFFER(1),
GEO_OPEN_NODE(),
GEO_CAMERA_FRUSTUM_WITH_FUNC(45.0000, 100, 30000, geo_camera_fov),
GEO_OPEN_NODE(),
GEO_CAMERA(CAMERA_MODE_CLOSE, 0, 0, 0, 0, -100, 0, geo_camera_main),
GEO_OPEN_NODE(),
GEO_BRANCH(1, sa_area_3_geo),
GEO_RENDER_OBJ(),
GEO_ASM(ENVFX_MODE_NONE, geo_envfx_main),
GEO_CLOSE_NODE(),
GEO_CLOSE_NODE(),
GEO_CLOSE_NODE(),
GEO_DISPLAY_LIST(LAYER_OPAQUE, sa_dl_material_revert_render_settings),
GEO_CLOSE_NODE(),
GEO_END(),
};

View file

@ -0,0 +1,4 @@
const MacroObject sa_area_3_macro_objs[] = {
MACRO_OBJECT_END(),
};

View file

@ -1,2 +1,3 @@
#include "levels/sa/area_2/geo.inc.c"
#include "levels/sa/area_1/geo.inc.c"
#include "levels/sa/area_3/geo.inc.c"

View file

@ -6,6 +6,9 @@ extern const GeoLayout sa_area_1_geo[];
extern const GeoLayout sa_area_1[];
extern const Collision sa_area_1_collision[];
extern const MacroObject sa_area_1_macro_objs[];
extern const GeoLayout sa_area_3_geo[];
extern const GeoLayout sa_area_3[];
extern const MacroObject sa_area_3_macro_objs[];
extern Lights1 sa_dl_sm64_material_lights;
extern Vtx sa_dl_Plane_001_mesh_vtx_0[4];
extern Gfx sa_dl_Plane_001_mesh_tri_0[];

View file

@ -4,4 +4,5 @@
#include "levels/sa/area_1/collision.inc.c"
#include "levels/sa/area_1/macro.inc.c"
#include "levels/sa/area_1/spline.inc.c"
#include "levels/sa/area_3/macro.inc.c"
#include "levels/sa/model.inc.c"

View file

@ -6,6 +6,7 @@
#include "dialog_ids.h"
#include "segment_symbols.h"
#include "level_commands.h"
#include "src/game/custom_level.h"
#include "game/level_update.h"
@ -68,6 +69,18 @@ const LevelScript level_sa_entry[] = {
/* Fast64 end persistent block [area commands] */
END_AREA(),
AREA(3, sa_area_3),
WARP_NODE(0x0A, LEVEL_BOB, 0x01, 0x0A, WARP_NO_CHECKPOINT),
WARP_NODE(0xF0, LEVEL_BOB, 0x01, 0x0A, WARP_NO_CHECKPOINT),
WARP_NODE(0xF1, LEVEL_BOB, 0x01, 0x0A, WARP_NO_CHECKPOINT),
OBJECT(MODEL_NONE, 0, 100, 0, 0, 0, 0, 0x000A0000, bhvSpinAirborneWarp),
MARIO_POS(0x03, 0, 100, 0, 0),
TERRAIN(custom_level_collision),
MACRO_OBJECTS(sa_area_3_macro_objs),
STOP_MUSIC(0),
TERRAIN_TYPE(TERRAIN_GRASS),
END_AREA(),
FREE_LEVEL_POOL(),
MARIO_POS(0x02, 180, 0, 200, 0),
CALL(0, lvl_init_or_update),

View file

@ -3482,6 +3482,7 @@ void update_camera(struct Camera *c) {
}
} else {
if (gMarioState->action == ACT_DEBUG_FREE_MOVE) mode_custom_fly(c);
else if (gCurrAreaIndex == 3 && gCurrLevelNum == LEVEL_SA) mode_8_directions_camera(c);
else {
switch (c->mode) {
case CAMERA_MODE_BEHIND_MARIO:

123
src/game/custom_level.c Normal file
View file

@ -0,0 +1,123 @@
#include "custom_level.h"
#include <string.h>
#include "include/types.h"
#include "include/surface_terrains.h"
Collision custom_level_collision[1024 * 64] = {
COL_INIT(),
COL_VERTEX_INIT(0),
COL_TRI_INIT(0, 0),
COL_TRI_STOP(),
COL_END(),
};
Gfx* custom_level_dl;
Gfx* geo_iter;
Vtx* vertices;
int* faces;
char** textures;
int vertex_iter = 0;
int face_iter = 0;
int used_vert = 0;
int tex_iter = 0;
Lights1 lights = gdSPDefLights1(0x7F, 0x7F, 0x7F, 0xFF, 0xFF, 0xFF, 0x28, 0x28, 0x28);
void custom_level_new() {
if (vertices != NULL) {
free(vertices);
free(faces);
free(custom_level_dl);
for (int i = 0; i < tex_iter; i++) {
free(textures[i]);
}
free(textures);
geo_iter = NULL;
}
vertices = malloc(sizeof(Vtx) * 8192);
faces = malloc(sizeof(int) * 3 * 8192);
custom_level_dl = malloc(sizeof(Gfx) * 16384);
textures = (char**)malloc(sizeof(char*) * 128);
for (int i = 0; i < 128; i++) {
textures[i] = (char*)malloc(sizeof(char) * 256);
}
vertex_iter = 0;
face_iter = 0;
used_vert = 0;
tex_iter = 0;
geo_iter = custom_level_dl;
gDPPipeSync(geo_iter++);
gDPSetCombineLERP(geo_iter++, TEXEL0, 0, SHADE, 0, 0, 0, 0, ENVIRONMENT, TEXEL0, 0, SHADE, 0, 0, 0, 0, ENVIRONMENT);
gSPTexture(geo_iter++, 65535, 65535, 0, 0, 1);
gSPSetLights1(geo_iter++, lights);
custom_level_texture("");
}
void custom_level_vertex(float x, float y, float z, float u, float v) {
if (vertex_iter == 8192) return;
vertices[vertex_iter].v.ob[0] = x;
vertices[vertex_iter].v.ob[1] = y;
vertices[vertex_iter].v.ob[2] = z;
vertices[vertex_iter].v.flag = 0;
vertices[vertex_iter].v.tc[0] = u;
vertices[vertex_iter].v.tc[1] = v;
vertices[vertex_iter].v.cn[0] = 255;
vertices[vertex_iter].v.cn[1] = 255;
vertices[vertex_iter].v.cn[2] = 255;
vertices[vertex_iter].v.cn[3] = 255;
vertex_iter++;
}
void custom_level_face() {
int begin = used_vert;
int num_vert = vertex_iter - used_vert;
gSPVertex(geo_iter++, vertices + begin, num_vert, 0);
for (int i = 2; i < num_vert; i++) {
if (face_iter == 8192) break;
faces[face_iter * 3 + 0] = begin;
faces[face_iter * 3 + 1] = i - 1 + begin;
faces[face_iter * 3 + 2] = i + begin;
face_iter++;
gSP1Triangle(geo_iter++, 0, i - 1, i, 0);
}
used_vert = vertex_iter;
}
void custom_level_texture(char* texpath) {
textures[tex_iter][0] = 1;
memcpy(textures[tex_iter] + 1, texpath, strlen(texpath) + 1);
gDPSetTextureImage(geo_iter++, G_IM_FMT_RGBA, G_IM_SIZ_16b_LOAD_BLOCK, 1, textures[tex_iter]);
gDPSetTile(geo_iter++, G_IM_FMT_RGBA, G_IM_SIZ_16b_LOAD_BLOCK, 0, 0, 7, 0, G_TX_WRAP | G_TX_NOMIRROR, 5, 0, G_TX_WRAP | G_TX_NOMIRROR, 5, 0);
gDPLoadBlock(geo_iter++, 7, 0, 0, 1023, 256);
gDPSetTile(geo_iter++, G_IM_FMT_RGBA, G_IM_SIZ_16b, 8, 0, 0, 0, G_TX_WRAP | G_TX_NOMIRROR, 5, 0, G_TX_WRAP | G_TX_NOMIRROR, 5, 0);
gDPSetTileSize(geo_iter++, 0, 0, 0, 124, 124);
tex_iter++;
}
void custom_level_finish() {
int iter = 0;
custom_level_collision[iter++] = TERRAIN_LOAD_VERTICES;
custom_level_collision[iter++] = vertex_iter;
int lowest = 0x7FFF;
for (int i = 0; i < vertex_iter; i++) {
for (int j = 0; j < 3; j++) {
if (lowest > vertices[i].v.ob[j]) lowest = vertices[i].v.ob[j];
custom_level_collision[iter++] = vertices[i].v.ob[j];
}
}
custom_level_collision[iter++] = 0;
custom_level_collision[iter++] = face_iter;
for (int i = 0; i < face_iter; i++) {
for (int j = 0; j < 3; j++) {
custom_level_collision[iter++] = faces[i * 3 + j];
}
}
custom_level_collision[iter++] = TERRAIN_LOAD_CONTINUE;
custom_level_collision[iter++] = TERRAIN_LOAD_END;
gSPEndDisplayList(geo_iter++);
}
Gfx *geo_custom_level(s32 sp, struct GraphNode* graphNode, UNUSED void* context) {
return custom_level_dl;
}

18
src/game/custom_level.h Normal file
View file

@ -0,0 +1,18 @@
#ifndef CUSTOM_LEVEL_H
#define CUSTOM_LEVEL_H
#include "include/types.h"
#include "include/macros.h"
extern Collision custom_level_collision[];
extern Gfx* custom_level_dl;
void custom_level_new();
void custom_level_vertex(float x, float y, float z, float u, float v);
void custom_level_face();
void custom_level_texture(char* texture);
void custom_level_finish();
Gfx* geo_custom_level(s32 sp, struct GraphNode* graphNode, UNUSED void* context);
#endif

View file

@ -9,6 +9,7 @@
#include <ctype.h>
#ifdef _WIN32
#include <direct.h>
#include <windows.h>
#endif
#include "macros.h"
@ -108,6 +109,9 @@ bool fs_init(const char **rodirs, const char *gamedir, const char *writepath) {
// as a special case, mount writepath itself
fs_mount(fs_writepath);
// mount absolute path
fs_mount("/");
return true;
}

View file

@ -33,6 +33,8 @@
#include "src/engine/geo_layout.h"
#include "src/game/area.h"
#define SUPPORT_CHECK(x) assert(x)
// SCALE_M_N: upscale/downscale M-bit integer to N-bit
@ -626,8 +628,15 @@ static void import_texture(int tile) {
// the "texture data" is actually a C string with the path to our texture in it
// load it from an external image in our data path
char texname[SYS_MAX_PATH];
snprintf(texname, sizeof(texname), FS_TEXTUREDIR "/%s.png", (const char*)rdp.loaded_texture[tile].addr);
load_texture(texname);
char* texpath = rdp.loaded_texture[tile].addr;
if (texpath[0] == 1) {
texpath++;
load_texture(texpath);
}
else {
snprintf(texname, sizeof(texname), FS_TEXTUREDIR "/%s.png", texpath);
load_texture(texname);
}
#else
// the texture data is actual texture data
int t0 = get_time();
@ -1020,9 +1029,12 @@ static void gfx_sp_vertex(size_t n_vertices, size_t dest_index, const Vtx *verti
V = (int32_t)((doty / 127.0f + 1.0f) / 4.0f * rsp.texture_scaling_factor.t);
}
} else {
d->color.r = v->cn[0] / (world_light_dir4);
d->color.g = v->cn[1] / (world_light_dir4);
d->color.b = v->cn[2] / (world_light_dir4);
d->color.r = v->cn[0] / (world_light_dir4) * gLightingColor[0];
d->color.g = v->cn[1] / (world_light_dir4) * gLightingColor[1];
d->color.b = v->cn[2] / (world_light_dir4) * gLightingColor[2];
if (d->color.r > 255) d->color.r = 255;
if (d->color.g > 255) d->color.g = 255;
if (d->color.b > 255) d->color.b = 255;
}
d->u = U;

View file

@ -511,12 +511,16 @@ void saturn_imgui_update() {
saturn_load_project((char*)(std::string(saturnProjectFilename) + ".spj").c_str());
}
ImGui::SameLine(70);
bool in_custom_level = gCurrLevelNum == LEVEL_SA && gCurrAreaIndex == 3;
if (in_custom_level) ImGui::BeginDisabled();
if (ImGui::Button(ICON_FA_SAVE " Save###project_file_save")) {
saturn_save_project((char*)(std::string(saturnProjectFilename) + ".spj").c_str());
saturn_load_project_list();
}
if (in_custom_level) ImGui::EndDisabled();
ImGui::SameLine();
imgui_bundled_help_marker("NOTE: Project files are currently EXPERIMENTAL and prone to crashing!");
if (in_custom_level) ImGui::Text("Saving in a custom\nlevel isn't supported");
ImGui::EndMenu();
}
if (ImGui::MenuItem(ICON_FA_UNDO " Load Autosaved")) {
@ -660,8 +664,14 @@ void saturn_imgui_update() {
ImGui::TextDisabled(ICON_FK_GITHUB " " GIT_BRANCH " " GIT_HASH);
#endif
#endif
ImGui::SameLine(ImGui::GetWindowWidth() - 135);
ImGui::Text("Autosaving in %ds", autosaveDelay / 30);
if (gCurrLevelNum == LEVEL_SA && gCurrAreaIndex == 3) {
ImGui::SameLine(ImGui::GetWindowWidth() - 280);
ImGui::Text("Saving in custom level isn't supported");
}
else {
ImGui::SameLine(ImGui::GetWindowWidth() - 140);
ImGui::Text("Autosaving in %ds", autosaveDelay / 30);
}
ImGui::EndMenuBar();
}
ImGui::End();

View file

@ -4,6 +4,7 @@
#include <iostream>
#include <algorithm>
#include <map>
#include <fstream>
#include "saturn/libs/imgui/imgui.h"
#include "saturn/libs/imgui/imgui_internal.h"
@ -21,6 +22,7 @@
#include <SDL2/SDL.h>
#include "icons/IconsForkAwesome.h"
#include "saturn/libs/portable-file-dialogs.h"
#include "data/dynos.cpp.h"
@ -38,6 +40,7 @@ extern "C" {
#include "src/game/interaction.h"
#include "include/behavior_data.h"
#include "game/object_helpers.h"
#include "game/custom_level.h"
}
using namespace std;
@ -61,6 +64,12 @@ float gravity = 1;
int current_location_index = 0;
char location_name[256];
float custom_level_scale = 100.f;
bool is_custom_level_loaded = false;
std::string custom_level_path;
std::string custom_level_filename;
std::string custom_level_dirname;
s16 levelList[] = {
LEVEL_SA, LEVEL_CASTLE_GROUNDS, LEVEL_CASTLE, LEVEL_CASTLE_COURTYARD, LEVEL_BOB,
LEVEL_WF, LEVEL_PSS, LEVEL_TOTWC, LEVEL_JRB, LEVEL_CCM,
@ -204,6 +213,75 @@ int get_saturn_level_id(int level) {
}
}
std::vector<std::string> split(std::string input, char character) {
std::vector<std::string> tokens = {};
std::string token = "";
for (int i = 0; i < input.length(); i++) {
if (input[i] == '\r') continue;
if (input[i] == character) {
tokens.push_back(token);
token = "";
}
else token += input[i];
}
tokens.push_back(token);
return tokens;
}
std::vector<std::vector<std::string>> tokenize(std::string input) {
std::vector<std::vector<std::string>> tokens = {};
auto lines = split(input, '\n');
for (auto line : lines) {
tokens.push_back(split(line, ' '));
}
return tokens;
}
void parse_materials(char* data, std::map<std::string, filesystem::path>* materials) {
auto tokens = tokenize(std::string(data));
std::string matname = "";
for (auto line : tokens) {
if (line[0] == "newmtl") matname = line[1];
if (line[0] == "map_Kd" && matname != "") materials->insert({ matname, filesystem::absolute(std::filesystem::path(custom_level_dirname) / line[1]) });
}
}
void parse_custom_level(char* data) {
auto tokens = tokenize(std::string(data));
custom_level_new();
std::vector<std::array<float, 3>> vertices = {};
std::vector<std::array<float, 2>> uv = {};
std::map<std::string, filesystem::path> materials = {};
for (auto line : tokens) {
if (line.size() == 0) continue;
if (line[0] == "mtllib") {
filesystem::path path = filesystem::absolute(std::filesystem::path(custom_level_dirname) / line[1]);
if (!filesystem::exists(path)) continue;
auto size = filesystem::file_size(path);
char* mtldata = (char*)malloc(size);
std::ifstream file = std::ifstream(path, std::ios::binary);
file.read(mtldata, size);
parse_materials(mtldata, &materials);
free(mtldata);
}
if (line[0] == "v") vertices.push_back({ std::stof(line[1]), std::stof(line[2]), std::stof(line[3]) });
if (line[0] == "vt") uv.push_back({ std::stof(line[1]), std::stof(line[2]) });
if (line[0] == "usemtl") {
if (materials.find(line[1]) == materials.end()) continue;
custom_level_texture((char*)materials[line[1]].c_str());
}
if (line[0] == "f") {
for (int i = 1; i < line.size(); i++) {
auto indexes = split(line[i], '/');
int v = std::stoi(indexes[0]) - 1;
int vt = std::stoi(indexes[1]) - 1;
custom_level_vertex(vertices[v][0] * custom_level_scale, vertices[v][1] * custom_level_scale, vertices[v][2] * custom_level_scale, uv[vt][0] * 1024, uv[vt][1] * 1024);
}
custom_level_face();
}
}
custom_level_finish();
}
void smachinima_imgui_init() {
Cheats.EnableCheats = true;
Cheats.GodMode = true;
@ -414,6 +492,37 @@ void imgui_machinima_quick_options() {
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Custom Level")) {
bool in_custom_level = gCurrLevelNum == LEVEL_SA && gCurrAreaIndex == 3;
ImGui::PushItemWidth(80);
ImGui::InputFloat("Scale###cl_scale", &custom_level_scale);
ImGui::PopItemWidth();
if (!is_custom_level_loaded || in_custom_level) ImGui::BeginDisabled();
if (ImGui::Button("Load Level")) {
auto size = filesystem::file_size(custom_level_path);
char* data = (char*)malloc(size);
std::ifstream file = std::ifstream((char*)custom_level_path.c_str(), std::ios::binary);
file.read(data, size);
parse_custom_level(data);
free(data);
warp_to_level(0, 3);
}
if (!is_custom_level_loaded || in_custom_level) ImGui::EndDisabled();
ImGui::SameLine();
if (ImGui::Button("Load .obj")) {
auto selection = pfd::open_file("Select a model", ".", { "Wavefront Model (.obj)", "*.obj" }, pfd::opt::none).result();
if (selection.size() != 0) {
std::string path = selection[0];
is_custom_level_loaded = true;
custom_level_path = path;
custom_level_dirname = filesystem::path(path).parent_path();
custom_level_filename = filesystem::path(path).filename();
}
}
ImGui::Text(is_custom_level_loaded ? custom_level_filename.c_str() : "No model loaded!");
ImGui::EndMenu();
}
}
}

View file

@ -367,9 +367,11 @@ void saturn_update() {
// Autosave
if (autosaveDelay <= 0) autosaveDelay = 30 * configAutosaveDelay;
autosaveDelay--;
if (autosaveDelay == 0) saturn_save_project("autosave.spj");
if (gCurrLevelNum != LEVEL_SA || gCurrAreaIndex != 3) {
if (autosaveDelay <= 0) autosaveDelay = 30 * configAutosaveDelay;
autosaveDelay--;
if (autosaveDelay == 0) saturn_save_project("autosave.spj");
}
}
float saturn_keyframe_setup_interpolation(std::string id, int frame, int* keyframe, bool* last) {