/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*/
#include "twine/debugger/debugtools.h"
#include "backends/imgui/components/imgui_logger.h"
#include "backends/imgui/imgui.h"
#include "backends/imgui/imgui_fonts.h"
#include "backends/imgui/imgui_utils.h"
#include "common/log.h"
#include "common/scummsys.h"
#include "common/str-enc.h"
#include "common/str.h"
#include "common/util.h"
#include "graphics/palette.h"
#include "twine/debugger/debug_state.h"
#include "twine/debugger/dt-internal.h"
#include "twine/holomap.h"
#include "twine/holomap_v1.h"
#include "twine/parser/entity.h"
#include "twine/renderer/redraw.h"
#include "twine/renderer/screens.h"
#include "twine/resources/resources.h"
#include "twine/scene/actor.h"
#include "twine/scene/gamestate.h"
#include "twine/scene/grid.h"
#include "twine/scene/scene.h"
#include "twine/shared.h"
#include "twine/twine.h"
namespace ImGuiEx {
bool InputIVec3(const char *label, TwinE::IVec3 &v, ImGuiInputTextFlags flags = 0) {
int tmp[3] = {v.x, v.y, v.z};
ImGui::InputInt3(label, tmp, flags);
if (ImGui::IsItemDeactivatedAfterEdit()) {
v.x = tmp[0];
v.y = tmp[1];
v.z = tmp[2];
return true;
}
return false;
}
bool InputAngle(const char *label, int32 *v, int step = 1, int step_fast = 100, const char *format = "%.2f", ImGuiInputTextFlags flags = 0) {
double tmp = TwinE::AngleToDegree(*v);
if (ImGui::InputDouble(label, &tmp, step, step_fast, format, flags)) {
*v = TwinE::DegreeToAngle(tmp);
return true;
}
ImGui::SetItemTooltip("Angle: %i", (int)*v);
return false;
}
bool InputBoundingBox(ImGuiID id, const char *prefixLabel, TwinE::BoundingBox &bbox) {
TwinE::BoundingBox copy = bbox;
Common::String idStr = Common::String::format("%s mins##mins%u", prefixLabel, id);
if (ImGuiEx::InputIVec3(idStr.c_str(), copy.mins)) {
if (copy.isValid()) {
bbox.mins = copy.mins;
}
return true;
}
idStr = Common::String::format("%s maxs##maxs%u", prefixLabel, id);
if (ImGuiEx::InputIVec3(idStr.c_str(), copy.maxs)) {
if (copy.isValid()) {
bbox.maxs = copy.maxs;
}
return true;
}
return false;
}
} // namespace ImGuiEx
namespace TwinE {
#define HOLOMAP_FLAGS_TITLE "Holomap flags"
#define GAME_FLAGS_TITLE "Game flags"
#define ACTOR_DETAILS_TITLE "Actor"
#define MENU_TEXT_TITLE "Menu texts"
static const char *toString(ShapeType type) {
switch (type) {
case ShapeType::kNone:
return "None";
case ShapeType::kSolid:
return "Solid";
case ShapeType::kStairsTopLeft:
return "StairsTopLeft";
case ShapeType::kStairsTopRight:
return "StairsTopRight";
case ShapeType::kStairsBottomLeft:
return "StairsBottomLeft";
case ShapeType::kStairsBottomRight:
return "StairsBottomRight";
case ShapeType::kDoubleSideStairsTop1:
return "DoubleSideStairsTop1";
case ShapeType::kDoubleSideStairsBottom1:
return "DoubleSideStairsBottom1";
case ShapeType::kDoubleSideStairsLeft1:
return "DoubleSideStairsLeft1";
case ShapeType::kDoubleSideStairsRight1:
return "DoubleSideStairsRight1";
case ShapeType::kDoubleSideStairsTop2:
return "DoubleSideStairsTop2";
case ShapeType::kDoubleSideStairsBottom2:
return "DoubleSideStairsBottom2";
case ShapeType::kDoubleSideStairsLeft2:
return "DoubleSideStairsLeft2";
case ShapeType::kDoubleSideStairsRight2:
return "DoubleSideStairsRight2";
case ShapeType::kFlatBottom1:
return "FlatBottom1";
case ShapeType::kFlatBottom2:
return "FlatBottom2";
default:
return "Unknown";
}
}
static void onLog(LogMessageType::Type type, int level, uint32 debugChannel, const char *message) {
switch (type) {
case LogMessageType::kError:
_logger->addLog("[error]%s", message);
break;
case LogMessageType::kWarning:
_logger->addLog("[warn]%s", message);
break;
case LogMessageType::kInfo:
_logger->addLog("%s", message);
break;
case LogMessageType::kDebug:
_logger->addLog("[debug]%s", message);
break;
}
}
void onImGuiInit() {
ImGuiIO &io = ImGui::GetIO();
io.Fonts->AddFontDefault();
ImFontConfig icons_config;
icons_config.MergeMode = true;
icons_config.PixelSnapH = false;
icons_config.OversampleH = 3;
icons_config.OversampleV = 3;
icons_config.GlyphOffset = {0, 4};
static const ImWchar icons_ranges[] = {ICON_MIN_MS, ICON_MAX_MS, 0};
ImGui::addTTFFontFromArchive("MaterialSymbolsSharp.ttf", 16.f, &icons_config, icons_ranges);
_tinyFont = ImGui::addTTFFontFromArchive("LiberationSans-Regular.ttf", 10.0f, nullptr, nullptr);
_logger = new ImGuiEx::ImGuiLogger;
Common::setLogWatcher(onLog);
}
static void holomapFlagsWindow(TwinEEngine *engine) {
if (!engine->_debugState->_holomapFlagsWindow) {
return;
}
if (ImGui::Begin(HOLOMAP_FLAGS_TITLE, &engine->_debugState->_holomapFlagsWindow)) {
if (ImGui::BeginTable("###holomapflags", 8)) {
for (int i = 0; i < engine->numHoloPos(); ++i) {
ImGui::TableNextColumn();
Common::String id = Common::String::format("[%03d]", i);
ImGuiEx::InputInt(id.c_str(), &engine->_gameState->_holomapFlags[i]);
ImGui::SetItemTooltip("%s", engine->_holomap->getLocationName(i));
}
ImGui::EndTable();
}
if (engine->isLBA1()) {
HolomapV1 *holomap = (HolomapV1*)engine->_holomap;
ImGuiEx::InputInt("current", &holomap->_current);
ImGuiEx::InputInt("otimer", &holomap->_otimer);
ImGuiEx::InputInt("dalpha", &holomap->_dalpha);
ImGuiEx::InputInt("dbeta", &holomap->_dbeta);
ImGuiEx::InputInt("calpha", &holomap->_calpha);
ImGuiEx::InputInt("cbeta", &holomap->_cbeta);
ImGuiEx::InputInt("cgamma", &holomap->_cgamma);
ImGuiEx::InputInt("oalpha", &holomap->_oalpha);
ImGuiEx::InputInt("obeta", &holomap->_obeta);
ImGui::Checkbox("automove", &holomap->_automove);
ImGui::Checkbox("flagredraw", &holomap->_flagredraw);
ImGui::Checkbox("dialstat", &holomap->_dialstat);
ImGui::Checkbox("flagpal", &holomap->_flagpal);
}
}
ImGui::End();
}
static void paletteWindow(TwinEEngine *engine) {
if (!engine->_debugState->_paletteWindow) {
return;
}
const ImVec2 available = ImGui::GetContentRegionAvail();
const float contentRegionHeight = available.y + ImGui::GetCursorPosY();
const ImVec2 windowSize(10.0f * ImGui::GetFrameHeight(), contentRegionHeight);
ImGui::SetNextWindowSize(windowSize, ImGuiCond_FirstUseEver);
if (ImGui::Begin("Palettes", &engine->_debugState->_paletteWindow)) {
if (engine->_screens->_flagPalettePcx) {
ImGui::Text("palettepcx is active");
} else {
ImGui::Text("ptrpal is active");
}
ImGui::SeparatorText("Front buffer palette");
const Graphics::Palette &frontBufferPalette = engine->_frontVideoBuffer.getPalette();
ImGui::PushID("frontBufferPalette");
ImGuiEx::Palette(frontBufferPalette);
ImGui::PopID();
ImGui::SeparatorText("PalettePCX");
ImGui::PushID("palettePcx");
ImGuiEx::Palette(engine->_screens->_palettePcx);
ImGui::PopID();
ImGui::SeparatorText("Palette");
ImGui::PushID("ptrPal");
ImGuiEx::Palette(engine->_screens->_ptrPal);
ImGui::PopID();
}
ImGui::End();
}
static float WaitTime(void *data, int i) {
TwinE::DebugState::FrameDataBuffer &buffer = *(TwinE::DebugState::FrameDataBuffer *)data;
return (float)buffer[i].waitMillis;
}
static float FrameTime(void *data, int i) {
TwinE::DebugState::FrameDataBuffer &buffer = *(TwinE::DebugState::FrameDataBuffer *)data;
return (float)buffer[i].frameTime;
}
static void frameTimeWindow(TwinEEngine *engine) {
if (!engine->_debugState->_frameTimeWindow) {
return;
}
if (ImGui::Begin("Frame time", &engine->_debugState->_frameTimeWindow)) {
ImGui::Checkbox("Record", &engine->_debugState->_frameDataRecording);
ImGui::PlotHistogram("Wait time", WaitTime, &engine->_debugState->_frameData, (int)engine->_debugState->_frameData.size(), 0, "Wait time in millis", -100.0f, 100.0f, ImVec2(0, 80));
ImGui::PlotHistogram("Frame time", FrameTime, &engine->_debugState->_frameData, (int)engine->_debugState->_frameData.size(), 0, "Frame time in millis", -100.0f, 100.0f, ImVec2(0, 80));
}
ImGui::End();
}
static void sceneFlagsWindow(TwinEEngine *engine) {
if (!engine->_debugState->_sceneFlagsWindow) {
return;
}
if (ImGui::Begin("Scene flags", &engine->_debugState->_sceneFlagsWindow)) {
if (ImGui::BeginTable("###sceneflags", 8)) {
for (int i = 0; i < NUM_SCENES_FLAGS; ++i) {
ImGui::TableNextColumn();
Common::String id = Common::String::format("[%03d]", i);
ImGuiEx::InputInt(id.c_str(), &engine->_scene->_listFlagCube[i]);
}
ImGui::EndTable();
}
}
ImGui::End();
}
static void gameFlagsWindow(TwinEEngine *engine) {
if (!engine->_debugState->_gameFlagsWindow) {
return;
}
if (ImGui::Begin(GAME_FLAGS_TITLE, &engine->_debugState->_gameFlagsWindow)) {
ImGui::Text("Chapter %i", engine->_gameState->getChapter());
if (ImGui::BeginTable("###gameflags", 8)) {
for (int i = 0; i < NUM_GAME_FLAGS; ++i) {
ImGui::TableNextColumn();
Common::String id = Common::String::format("[%03d]", i);
int16 val = engine->_gameState->hasGameFlag(i);
if (ImGuiEx::InputInt(id.c_str(), &val)) {
engine->_gameState->setGameFlag(i, val);
}
}
ImGui::EndTable();
}
}
ImGui::End();
}
static void menuTextsWindow(TwinEEngine *engine) {
if (!engine->_debugState->_menuTextWindow) {
return;
}
if (ImGui::Begin(MENU_TEXT_TITLE, &engine->_debugState->_menuTextWindow)) {
int id = (int)engine->_debugState->_textBankId;
if (ImGui::InputInt("Text bank", &id)) {
engine->_debugState->_textBankId = (TextBankId)id;
}
const TextBankId oldTextBankId = engine->_text->textBank();
engine->_text->initDial(engine->_debugState->_textBankId);
for (int32 i = 0; i < 1000; ++i) {
char buf[256];
if (engine->_text->getMenuText((TextId)i, buf, sizeof(buf))) {
ImGui::Text("%4i: %s\n", i, buf);
}
}
engine->_text->initDial(oldTextBankId);
}
ImGui::End();
}
static void sceneSelectionCombo(TwinEEngine *engine) {
Scene *scene = engine->_scene;
GameState *gameState = engine->_gameState;
Common::U32String originalSceneName(gameState->_sceneName, Common::kDos850);
const Common::String sceneName = originalSceneName.encode(Common::kUtf8);
if (ImGui::BeginCombo("Scene", sceneName.c_str())) {
for (int i = 0; i < engine->numHoloPos(); ++i) {
Common::U32String originalLocationName(engine->_holomap->getLocationName(i), Common::kDos850);
const Common::String locationName = originalLocationName.encode(Common::kUtf8);
Common::String name = Common::String::format("[%03d] %s", i, locationName.c_str());
if (ImGui::Selectable(name.c_str(), i == engine->_scene->_numCube)) {
scene->_numCube = i;
scene->_newCube = scene->_numCube;
engine->_redraw->_firstTime = true;
}
}
ImGui::EndCombo();
}
}
static const struct ZonesDesc {
const char *name;
ZoneType type;
const char *desc;
} ZoneDescriptions[] = {
{"Cube", ZoneType::kCube, "Change to another scene"},
{"Camera", ZoneType::kCamera, "Binds camera view"},
{"Sceneric", ZoneType::kSceneric, "For use in Life Script"},
{"Grid", ZoneType::kGrid, "Set disappearing Grid fragment"},
{"Object", ZoneType::kObject, "Give bonus"},
{"Text", ZoneType::kText, "Displays text message"},
{"Ladder", ZoneType::kLadder, "Hero can climb on it"},
{"Escalator", ZoneType::kEscalator, nullptr},
{"Hit", ZoneType::kHit, nullptr},
{"Rail", ZoneType::kRail, nullptr}};
static void sceneDetailsWindows(TwinEEngine *engine) {
if (!engine->_debugState->_sceneDetailsWindow) {
return;
}
if (ImGui::Begin("Scene", &engine->_debugState->_sceneDetailsWindow)) {
Scene *scene = engine->_scene;
GameState *gameState = engine->_gameState;
ImGui::Text("Scene: %i", scene->_numCube);
ImGui::Text("Scene name: %s", gameState->_sceneName);
sceneSelectionCombo(engine);
if (ImGui::Checkbox("Bounding boxes", &engine->_debugState->_showingActors)) {
engine->_redraw->_firstTime = true;
}
if (ImGui::Checkbox("Clipping", &engine->_debugState->_showingClips)) {
engine->_redraw->_firstTime = true;
}
if (ImGui::Checkbox("Zones", &engine->_debugState->_showingZones)) {
engine->_redraw->_firstTime = true;
}
// if (ImGui::Checkbox("Tracks", &engine->_debugState->_showingTracks)) {
// engine->_redraw->_firstTime = true;
// }
if (engine->_debugState->_showingZones) {
if (ImGui::CollapsingHeader("Show zone types")) {
for (int i = 0; i < ARRAYSIZE(ZoneDescriptions); ++i) {
if (ImGui::CheckboxFlags(ZoneDescriptions[i].name, &engine->_debugState->_typeZones, (1u << (uint32)ZoneDescriptions[i].type))) {
engine->_redraw->_firstTime = true;
}
if (ZoneDescriptions[i].desc) {
ImGui::SetItemTooltip(ZoneDescriptions[i].desc);
}
}
}
}
if (ImGui::CollapsingHeader("Zones##zonesheader")) {
for (int i = 0; i < scene->_sceneNumZones; ++i) {
ZoneStruct *zone = &scene->_sceneZones[i];
ImGui::Text("Zone idx: %i", i);
ImGui::Indent();
const ZonesDesc &zoneDesc = ZoneDescriptions[(int)zone->type];
ImGui::Text("Type: %s", zoneDesc.name);
if (zoneDesc.desc != nullptr) {
ImGui::SameLine();
ImGui::Text("%s", zoneDesc.desc);
}
ImGui::PushID(i);
ImGuiEx::InputIVec3("Mins", zone->mins);
ImGuiEx::InputIVec3("Maxs", zone->maxs);
ImGui::PopID();
ImGui::Text("Num: %i", zone->num);
ImGui::Text("Info0: %i", zone->infoData.generic.info0);
ImGui::Text("Info1: %i", zone->infoData.generic.info1);
ImGui::Text("Info2: %i", zone->infoData.generic.info2);
ImGui::Text("Info3: %i", zone->infoData.generic.info3);
ImGui::Text("Info4: %i", zone->infoData.generic.info4);
ImGui::Text("Info5: %i", zone->infoData.generic.info5);
ImGui::Text("Info6: %i", zone->infoData.generic.info6);
ImGui::Text("Info7: %i", zone->infoData.generic.info7);
ImGui::Unindent();
}
}
if (ImGui::CollapsingHeader("Tracks##tracksheader")) {
for (int i = 0; i < scene->_sceneNumTracks; ++i) {
ImGui::Text("Track %i: %i %i %i", i, scene->_sceneTracks[i].x, scene->_sceneTracks[i].y, scene->_sceneTracks[i].z);
}
}
if (ImGui::CollapsingHeader("Trajectories##trajectoriesheader")) {
const TrajectoryData &trajectories = engine->_resources->getTrajectories();
for (int i = 0; i < (int)trajectories.getTrajectories().size(); ++i) {
const Trajectory *trajectory = trajectories.getTrajectory(i);
ImGui::Text("Trajectory %i", i);
ImGui::SameLine();
Common::String buttonId = Common::String::format("Activate##activateTrajectory%i", i);
if (ImGui::Button(buttonId.c_str())) {
scene->_numHolomapTraj = i;
scene->reloadCurrentScene();
}
ImGui::Indent();
ImGui::Text("location: %i", trajectory->locationIdx);
ImGui::Text("trajectory location: %i", trajectory->trajLocationIdx);
ImGui::Text("vehicle: %i", trajectory->vehicleIdx);
ImGui::Text("pos: %i %i %i", trajectory->angle.x, trajectory->angle.y, trajectory->angle.z);
ImGui::Text("num anim frames: %i", trajectory->numAnimFrames);
ImGui::Unindent();
}
}
ImGuiEx::InputInt("Previous scene index", &scene->_oldcube);
ImGuiEx::InputInt("Need change scene index", &scene->_newCube);
ImGui::Text("Climbing flag");
ImGui::SameLine();
ImGuiEx::Boolean(scene->_flagClimbing);
ImGuiEx::InputInt("Currently followed actor", &scene->_numObjFollow);
ImGui::Checkbox("Render grid tiles", &scene->_flagRenderGrid);
ImGuiEx::InputInt("Current script value", &scene->_currentScriptValue);
ImGuiEx::InputInt("Talking actor", &scene->_talkingActor);
ImGuiEx::InputInt("Cube jingle", &scene->_cubeJingle);
ImGuiEx::InputIVec3("New hero pos", scene->_sceneStart);
ImGuiEx::InputInt("Alpha light", &scene->_alphaLight);
ImGuiEx::InputInt("Beta light", &scene->_betaLight);
ImGuiEx::InputInt("Fall Y position", &scene->_startYFalling);
ImGui::Text("Hero position type: %i", (int)scene->_flagChgCube);
}
ImGui::End();
}
static void actorDetailsWindow(int &actorIdx, TwinEEngine *engine) {
if (!engine->_debugState->_actorDetailsWindow) {
return;
}
ActorStruct *actor = engine->_scene->getActor(actorIdx);
if (actor == nullptr) {
return;
}
if (ImGui::Begin(ACTOR_DETAILS_TITLE, &engine->_debugState->_actorDetailsWindow)) {
if (actorIdx < 0 || actorIdx > engine->_scene->_nbObjets) {
actorIdx = 0;
}
Common::String currentActorLabel = Common::String::format("Actor %i", actorIdx);
if (ImGui::BeginCombo("Actor", currentActorLabel.c_str())) {
for (int i = 0; i < engine->_scene->_nbObjets; ++i) {
Common::String label = Common::String::format("Actor %i", i);
if (engine->_scene->_mecaPenguinIdx == i) {
label += " (Penguin)";
}
const bool selected = i == actorIdx;
if (ImGui::Selectable(label.c_str(), selected)) {
actorIdx = i;
}
}
ImGui::EndCombo();
}
ImGui::Separator();
ImGuiEx::InputIVec3("Pos", actor->_posObj);
ImGuiEx::InputAngle("Rotation", &actor->_beta);
ImGuiEx::InputInt("Rotation speed", &actor->_srot);
ImGuiEx::InputInt("Life", &actor->_lifePoint);
ImGuiEx::InputInt("Armor", &actor->_armor);
ImGuiEx::InputBoundingBox(actorIdx, "Bounding box", actor->_boundingBox);
if (ImGui::CollapsingHeader("Properties")) {
if (ImGui::BeginTable("Properties", 2)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableHeadersRow();
ImGui::TableNextColumn();
ImGui::Text("Followed");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_followedActor);
ImGui::TableNextColumn();
ImGui::Text("Control mode");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_move);
ImGui::TableNextColumn();
ImGui::Text("Delay");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_delayInMillis);
ImGui::TableNextColumn();
ImGui::Text("Strength");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_hitForce);
ImGui::TableNextColumn();
ImGui::Text("Hit by");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_hitBy);
ImGui::TableNextColumn();
ImGui::Text("Bonus");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_bonusParameter);
ImGui::TableNextColumn();
ImGui::Text("ZoneSce");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_zoneSce);
ImGui::TableNextColumn();
ImGui::Text("Brick shape");
ImGui::TableNextColumn();
ImGui::Text("%s", toString(actor->brickShape()));
ImGui::TableNextColumn();
ImGui::Text("Brick causes damage");
ImGui::TableNextColumn();
ImGuiEx::Boolean(actor->brickCausesDamage());
ImGui::TableNextColumn();
ImGui::Text("Collision");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_objCol);
ImGui::TableNextColumn();
ImGui::Text("Carried by");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_carryBy);
ImGui::TableNextColumn();
ImGui::Text("Talk color");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_talkColor);
ImGui::TableNextColumn();
ImGui::Text("Body");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_body); // TODO: link to resources
ImGui::TableNextColumn();
ImGui::Text("Gen body");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_genBody);
ImGui::TableNextColumn();
ImGui::Text("Save gen body");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_saveGenBody);
ImGui::TableNextColumn();
ImGui::Text("Gen anim");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_genAnim);
ImGui::TableNextColumn();
ImGui::Text("Next gen anim");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_nextGenAnim);
ImGui::TableNextColumn();
ImGui::Text("Ptr anim action");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_ptrAnimAction);
ImGui::TableNextColumn();
ImGui::Text("Sprite");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_sprite);
ImGui::TableNextColumn();
ImGui::Text("A3DS");
ImGui::TableNextColumn();
ImGui::Text("%i %i %i", actor->A3DS.Num, actor->A3DS.Deb, actor->A3DS.Fin);
ImGui::EndTable();
}
}
if (ImGui::CollapsingHeader("Work Flags")) {
static const char *Names[] = {
"WAIT_HIT_FRAME",
"OK_HIT",
"ANIM_END",
"NEW_FRAME",
"WAS_DRAWN",
"OBJ_DEAD",
"AUTO_STOP_DOOR",
"ANIM_MASTER_ROT",
"FALLING",
"IS_TARGETABLE",
"IS_BLINKING",
"DRAW_SHADOW",
"ANIM_MASTER_GRAVITY",
"SKATING",
"OK_RENVOIE",
"LEFT_JUMP",
"RIGHT_JUMP",
"WAIT_SUPER_HIT",
"TRACK_MASTER_ROT",
"FLY_JETPACK",
"DONT_PICK_CODE_JEU",
"MANUAL_INTER_FRAME",
"WAIT_COORD",
"CHECK_FALLING"};
if (ImGui::BeginTable("##workflags", 6)) {
for (int i = 0; i < ARRAYSIZE(Names); ++i) {
ImGui::TableNextColumn();
ImGui::CheckboxFlags(Names[i], (uint32_t *)&actor->_workFlags, (1 << i));
}
ImGui::EndTable();
}
}
if (ImGui::CollapsingHeader("Flags")) {
static const char *Names[] = {
"CHECK_OBJ_COL",
"CHECK_BRICK_COL",
"CHECK_ZONE",
"SPRITE_CLIP",
"PUSHABLE",
"COL_BASSE",
"CHECK_CODE_JEU",
"CHECK_WATER_COL",
"0x000100",
"INVISIBLE",
"SPRITE_3D",
"OBJ_FALLABLE",
"NO_SHADOW",
"OBJ_BACKGROUND",
"OBJ_CARRIER",
"MINI_ZV",
"POS_INVALIDE",
"NO_CHOC",
"ANIM_3DS",
"NO_PRE_CLIP",
"OBJ_ZBUFFER",
"OBJ_IN_WATER",
};
if (ImGui::BeginTable("##staticflags", 6)) {
for (int i = 0; i < ARRAYSIZE(Names); ++i) {
ImGui::TableNextColumn();
ImGui::CheckboxFlags(Names[i], (uint32_t *)&actor->_flags, (1 << i));
}
ImGui::EndTable();
}
}
if (actor->_body != -1) {
ImGui::SeparatorText("Body");
if (actor->_entityDataPtr != nullptr) {
BodyData &bodyData = actor->_entityDataPtr->getBody(actor->_body);
ImGuiEx::InputBoundingBox((int)(uintptr)&bodyData, "Bounding box", bodyData.bbox);
} else {
ImGui::Text("No entity data");
}
}
ImGui::SeparatorText("Entity");
EntityData &entityData = actor->_entityData;
Common::Array &entityBodies = entityData.getBodies();
ImGui::Text("Bodies: %i", (int)entityBodies.size());
for (EntityBody &entityBody : entityBodies) {
ImGui::Text("%s index: %i", Resources::HQR_FILE3D_FILE, entityBody.index);
ImGui::Indent();
ImGui::Text("%s index: %i", Resources::HQR_BODY_FILE, entityBody.hqrBodyIndex);
Common::String id = Common::String::format("Has bounding box##%i", entityBody.index);
ImGui::Checkbox(id.c_str(), &entityBody.actorBoundingBox.hasBoundingBox);
ImGuiEx::InputBoundingBox((int)(uintptr)&entityBody, "Bounding box", entityBody.actorBoundingBox.bbox);
ImGui::Unindent();
}
Common::Array &animations = entityData.getAnimations();
ImGui::Text("Animations: %i", (int)animations.size());
for (EntityAnim &animation : animations) {
ImGui::Text("Animation type: %i", (int)animation.animation);
ImGui::Indent();
ImGui::Text("Body animation index: %i", (int)animation.animIndex);
ImGui::Text("actions: %i", (int)animation._actions.size());
for (EntityAnim::Action &action : animation._actions) {
ImGui::BulletText("%i", (int)action.type);
}
ImGui::Unindent();
}
}
ImGui::End();
}
static void gameStateMenu(TwinEEngine *engine) {
if (ImGui::BeginMenu("Game State")) {
int keys = engine->_gameState->_nbLittleKeys;
if (ImGui::InputInt("Keys", &keys)) {
engine->_gameState->setKeys(keys);
}
int kashes = engine->_gameState->_goldPieces;
if (ImGui::InputInt("Cash", &kashes)) {
engine->_gameState->setKashes(kashes);
}
int zlitos = engine->_gameState->_zlitosPieces;
if (ImGui::InputInt("Zlitos", &zlitos)) {
engine->_gameState->setZlitos(zlitos);
}
int magicPoints = engine->_gameState->_magicPoint;
if (ImGui::InputInt("Magic points", &magicPoints)) {
engine->_gameState->setMagicPoints(magicPoints);
}
int magicLevel = engine->_gameState->_magicLevelIdx;
if (ImGui::InputInt("Magic level", &magicLevel)) {
engine->_gameState->_magicLevelIdx = CLIP(magicLevel, 0, 4);
}
int leafs = engine->_gameState->_inventoryNumLeafs;
if (ImGui::InputInt("Leafs", &leafs)) {
engine->_gameState->setLeafs(leafs);
}
int leafBoxes = engine->_gameState->_inventoryNumLeafsBox;
if (ImGui::InputInt("Leaf boxes", &leafBoxes)) {
engine->_gameState->setLeafBoxes(leafBoxes);
}
int gas = engine->_gameState->_inventoryNumGas;
if (ImGui::InputInt("Gas", &gas)) {
engine->_gameState->setGas(gas);
}
const TextBankId oldTextBankId = engine->_text->textBank();
engine->_text->initDial(TextBankId::Inventory_Intro_and_Holomap);
for (int i = 0; i < NUM_INVENTORY_ITEMS; ++i) {
Common::String label;
if (engine->_text->getText((TextId)(100 + i))) {
Common::U32String original(engine->_text->_currDialTextEntry->string, Common::kDos850);
label = original.encode(Common::kUtf8).substr(0, 30);
} else {
label = Common::String::format("Item %i", i);
}
uint8 &value = engine->_gameState->_inventoryFlags[i];
bool hasItem = value != 0;
if (ImGui::Checkbox(label.c_str(), &hasItem)) {
value = hasItem == 0 ? 0 : 1;
}
}
engine->_text->initDial(oldTextBankId);
ImGui::EndMenu();
}
}
static void gridMenu(TwinEEngine *engine) {
if (ImGui::BeginMenu("Grid")) {
ImGui::Text("World cube %i %i %i", engine->_grid->_worldCube.x, engine->_grid->_worldCube.y, engine->_grid->_worldCube.z);
#if 0
Grid *grid = engine->_grid;
if (ImGui::Button(ICON_MS_ADD)) {
grid->_cellingGridIdx++;
if (grid->_cellingGridIdx > 133) {
grid->_cellingGridIdx = 133;
}
}
if (ImGui::Button(ICON_MS_REMOVE)) {
grid->_cellingGridIdx--;
if (grid->_cellingGridIdx < 0) {
grid->_cellingGridIdx = 0;
}
}
// Enable/disable celling grid
if (ImGui::Button("Apply ceiling grid")) {
if (grid->_useCellingGrid == -1) {
grid->_useCellingGrid = 1;
// grid->createGridMap();
grid->initCellingGrid(grid->_cellingGridIdx);
debug("Enable Celling Grid index: %d", grid->_cellingGridIdx);
engine->_scene->_needChangeScene = SCENE_CEILING_GRID_FADE_2; // tricky to make the fade
} else if (grid->_useCellingGrid == 1) {
grid->_useCellingGrid = -1;
grid->copyMapToCube();
engine->_redraw->_firstTime = true;
debug("Disable Celling Grid index: %d", grid->_cellingGridIdx);
engine->_scene->_needChangeScene = SCENE_CEILING_GRID_FADE_2; // tricky to make the fade
}
}
#endif
ImGui::EndMenu();
}
}
static void debuggerMenu(TwinEEngine *engine) {
if (ImGui::BeginMenu("Debugger")) {
ImGui::Text("Timer: %i", (int)engine->timerRef);
if (ImGui::MenuItem("Logs")) {
engine->_debugState->_loggerWindow = true;
}
if (ImGui::MenuItem("Texts")) {
engine->_debugState->_menuTextWindow = true;
}
if (ImGui::MenuItem("Holomap flags")) {
engine->_debugState->_holomapFlagsWindow = true;
}
if (ImGui::MenuItem("Game flags")) {
engine->_debugState->_gameFlagsWindow = true;
}
if (ImGui::MenuItem("Scene details")) {
engine->_debugState->_sceneDetailsWindow = true;
}
if (ImGui::MenuItem("Scene flags")) {
engine->_debugState->_sceneFlagsWindow = true;
}
if (ImGui::MenuItem("Actor details")) {
engine->_debugState->_actorDetailsWindow = true;
}
if (ImGui::MenuItem("Frame time")) {
engine->_debugState->_frameTimeWindow = true;
}
ImGui::SeparatorText("Actions");
if (ImGui::MenuItem("Center actor")) {
ActorStruct *actor = engine->_scene->getActor(OWN_ACTOR_SCENE_INDEX);
actor->_posObj = engine->_grid->_worldCube;
actor->_posObj.y += 16 * SIZE_BRICK_Y;
int32 y = actor->_posObj.y - 1 - SIZE_BRICK_Y;
while (y > 0 && ShapeType::kNone == engine->_grid->worldColBrick(actor->_posObj.x, y, actor->_posObj.z)) {
y -= SIZE_BRICK_Y;
}
actor->_posObj.y = (y + SIZE_BRICK_Y) & ~(SIZE_BRICK_Y - 1);
}
if (ImGui::BeginMenu("Animations")) {
if (ImGui::MenuItem("Found item")) {
engine->_debugState->_playFoundItemAnimation = true;
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Palettes")) {
LifeScriptContext fakeCtx(0, engine->_scene->_sceneHero);
if (ImGui::MenuItem("Show palette")) {
engine->_debugState->_paletteWindow = true;
}
if (ImGui::MenuItem("Dark palette")) {
engine->_scriptLife->lSET_DARK_PAL(engine, fakeCtx);
}
if (ImGui::MenuItem("Normal palette")) {
engine->_scriptLife->lSET_NORMAL_PAL(engine, fakeCtx);
}
#if 0
// TODO: the fade functions are blocking and break the imgui begin/end cycle
if (ImGui::MenuItem("lFADE_PAL_RED")) {
engine->_scriptLife->lFADE_PAL_RED(engine, fakeCtx);
}
if (ImGui::MenuItem("lFADE_ALARM_RED")) {
engine->_scriptLife->lFADE_ALARM_RED(engine, fakeCtx);
}
if (ImGui::MenuItem("lFADE_ALARM_PAL")) {
engine->_scriptLife->lFADE_ALARM_PAL(engine, fakeCtx);
}
if (ImGui::MenuItem("lFADE_RED_PAL")) {
engine->_scriptLife->lFADE_RED_PAL(engine, fakeCtx);
}
if (ImGui::MenuItem("lFADE_RED_ALARM")) {
engine->_scriptLife->lFADE_RED_ALARM(engine, fakeCtx);
}
if (ImGui::MenuItem("lFADE_PAL_ALARM")) {
engine->_scriptLife->lFADE_PAL_ALARM(engine, fakeCtx);
}
#endif
ImGui::EndMenu();
}
if (ImGui::MenuItem("Force Redraw")) {
engine->_redraw->_firstTime = true;
}
ImGui::SeparatorText("Options");
ImGui::Checkbox("Free camera", &engine->_debugState->_useFreeCamera);
ImGui::Checkbox("God mode", &engine->_debugState->_godMode);
sceneSelectionCombo(engine);
ImGui::EndMenu();
}
}
void onImGuiRender() {
if (!debugChannelSet(-1, kDebugImGui)) {
ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange | ImGuiConfigFlags_NoMouse;
return;
}
static int currentActor = 0;
ImGuiIO &io = ImGui::GetIO();
io.ConfigFlags &= ~(ImGuiConfigFlags_NoMouseCursorChange | ImGuiConfigFlags_NoMouse);
ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport(), ImGuiDockNodeFlags_PassthruCentralNode);
TwinEEngine *engine = (TwinEEngine *)g_engine;
if (ImGui::BeginMainMenuBar()) {
debuggerMenu(engine);
gameStateMenu(engine);
gridMenu(engine);
ImGui::EndMainMenuBar();
}
actorDetailsWindow(currentActor, engine);
sceneDetailsWindows(engine);
menuTextsWindow(engine);
holomapFlagsWindow(engine);
gameFlagsWindow(engine);
paletteWindow(engine);
sceneFlagsWindow(engine);
frameTimeWindow(engine);
_logger->draw("Logger", &engine->_debugState->_loggerWindow);
if (engine->_debugState->_openPopup) {
ImGui::OpenPopup(engine->_debugState->_openPopup);
engine->_debugState->_openPopup = nullptr;
}
}
void onImGuiCleanup() {
Common::setLogWatcher(nullptr);
delete _logger;
_logger = nullptr;
}
} // namespace TwinE