///////////////////////////////////////////////////////////////////////////////// // // FILE : NodeViewer.cpp // PURPOSE : Allows nodes to be added and edited around the map and saved out // AUTHOR : Derek Payne // STARTED : 27/05/2010 // // UPDATED : 11/09/2010 // AUTHOR : Adam Munson // ///////////////////////////////////////////////////////////////////////////////// #if __BANK #include "NodeViewer.h" #include "BlockView.h" // Used to find current block nodes are created in #include "camera/CamInterface.h" // camera #include "camera/debug/debugdirector.h" #include "camera/debug/FreeCamera.h" #include "camera/helpers/Frame.h" #include "frontend/NewHud.h" #include "ModelInfo/ModelInfo.h" #include "ModelInfo/ModelInfo_Factories.h" #include "modelinfo/PedModelInfo.h" #include "parser/manager.h" // XML #include "parser/tree.h" #include "parser/treenode.h" #include "parsercore/attribute.h" #include "parsercore/element.h" #include "parsercore/stream.h" #include "Peds/PedFactory.h" // for Scalejaxx ped creation #include "Peds/PedIntelligence.h" #include "Peds/ped.h" #include "physics/WorldProbe/worldprobe.h" // findground pos #include "scene/world/gameWorld.h" #include "streaming/streaming.h" #include "system/controlmgr.h" // controlmgr #include "system/FileMgr.h" #include "system/pad.h" // pad #include "task/General/TaskBasic.h" #include "text/text.h" #include "text/TextConversion.h" #define NOTE_NODES_FILE_VERSION (1) #define NOTE_NODES_VIEWER_GROUP_NAME "Node Viewer" #define NOTE_NODES_DEBUG_BANK_NAME "Debug" #define NOTE_NODES_POINT_NODE_SIZE (1.65f) #define NOTE_NODES_BOX_NODE_SIZE (1.4f) #define NOTE_NODES_NODE_ALPHA (170) #define NOTE_NODES_EQUIDISTANT_DROP_DISTANCE (50.0f) #define NOTE_NODES_MAX_NODE_SIZE (12.0f) #define NOTE_NODES_MIN_NODE_SIZE (1.0f) atArray CNodeViewer::sm_NodeSets; atArray CNodeViewer::sm_Colours; atArray CNodeViewer::sm_ScalejaxxPeds; static const char *s_NodeColour[] = { "Default (Blue)", "Green", "Purple", "Yellow", "Orange", "Brown", "Black" }; static const char *s_NodeType[] = { "Point", "Link", "Box", "Arrow", "ScaleJaxx" }; float CNodeViewer::sm_DistanceBetweenDrops; float CNodeViewer::sm_NodeSize; float CNodeViewer::sm_DrawDistance; bkGroup* CNodeViewer::sm_BankGroupMain = NULL; bkGroup* CNodeViewer::sm_BankEditControl = NULL; CNodeViewer::eEditorLevel CNodeViewer::sm_EditorLevel; s32 CNodeViewer::sm_CurrentSet; s32 CNodeViewer::sm_CurrentSelected; s32 CNodeViewer::sm_TextFieldCursor; s32 CNodeViewer::sm_FirstLinkNode; s32 CNodeViewer::sm_FirstLinkSet; s32 CNodeViewer::sm_FirstLinkType; s32 CNodeViewer::sm_ColourSelection; s32 CNodeViewer::sm_NodeLinkType; s32 CNodeViewer::sm_OriginalKeyboardMode; char CNodeViewer::sm_TextField[NOTE_NODES_CHAR_ARRAY_LENGTH]; char CNodeViewer::sm_GlobalName[NOTE_NODES_CHAR_ARRAY_LENGTH]; char CNodeViewer::sm_GlobalSetName[NOTE_NODES_CHAR_ARRAY_LENGTH]; char CNodeViewer::sm_XmlFilename[128]; atString CNodeViewer::sm_XmlSaveLocation; atString CNodeViewer::sm_XmlAutosaveFilename; bool CNodeViewer::sm_IsActive = false; bool CNodeViewer::sm_IsCreatingLink; bool CNodeViewer::sm_IsEditMode = true; bool CNodeViewer::sm_IsEquidistantDrop; bool CNodeViewer::sm_IsShowAllNodesAndSets; ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::SetAllNodesEmpty() // PURPOSE: clears all the nodes arrays ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::SetAllNodesEmpty() { // Reset the node arrays for (s32 s = 0; s < sm_NodeSets.GetCount(); s++) { for (s32 n = 0; n < sm_NodeSets[s].nodes.GetCount(); n++) { sm_NodeSets[s].nodes.Reset(); } } sm_NodeSets.Reset(); sm_NodeSets.Reserve(NOTE_NODES_MAX_NUM_SETS); char buffer[16]; for (s32 s = 0; s < NOTE_NODES_MAX_NUM_SETS; s++) { NoteNodeSet newNodeSet; buffer[0] = '\0'; formatf(buffer, 16, "Set %d", s+1); newNodeSet.name = buffer; newNodeSet.creation_number = -1; newNodeSet.node_creation_number = 0; newNodeSet.open = false; newNodeSet.nodes.Reset(); sm_NodeSets.Append() = newNodeSet; } // The indexes are cleared above, go through array if it has any // entries and delete the peds from the world. for(s32 i = 0; i < sm_ScalejaxxPeds.GetCount(); i++) { CPedFactory::GetFactory()->DestroyPed(sm_ScalejaxxPeds[i]); } sm_ScalejaxxPeds.Reset(); sm_CurrentSet = 0; sm_CurrentSelected = 0; sm_IsEquidistantDrop = false; sm_DistanceBetweenDrops = NOTE_NODES_EQUIDISTANT_DROP_DISTANCE; sm_GlobalName[0] = '\0'; sm_GlobalSetName[0] = '\0'; sm_TextField[0] = '\0'; sm_IsCreatingLink = false; sm_FirstLinkNode = -1; sm_FirstLinkSet = -1; } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::Init() // PURPOSE: Initialises the node viewer at start of game. // Note Init() is called per level. ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::Init(unsigned /*initMode*/) { sm_IsActive = false; sm_EditorLevel = EDITOR_LOADING; } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::Activate() // PURPOSE: Adds all bank widgets and clears values, or changes level state and // saves and closes ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::Activate() { bkBank *bank = BANKMGR.FindBank(NOTE_NODES_DEBUG_BANK_NAME); camDebugDirector& debugDirector = camInterface::GetDebugDirector(); if (debugDirector.IsFreeCamActive() == false) return; if (sm_IsActive) { if (bank && sm_BankGroupMain) { if(sm_IsEditMode == true) { // Only add the widgets if they aren't there already if(sm_BankEditControl == NULL) { //Initialise values sm_CurrentSet = 0; sm_CurrentSelected = 0; sm_FirstLinkNode = -1; sm_FirstLinkSet = -1; sm_FirstLinkType = -1; sm_ColourSelection = CNodeViewer::COLOUR_DEFAULT; sm_NodeLinkType = TYPE_POINT_NODE; sm_OriginalKeyboardMode = KEYBOARD_MODE_GAME; sm_DistanceBetweenDrops = NOTE_NODES_EQUIDISTANT_DROP_DISTANCE; sm_NodeSize = NOTE_NODES_POINT_NODE_SIZE; sm_DrawDistance = 250.0f; sm_IsCreatingLink = false; sm_IsEquidistantDrop = false; sm_IsShowAllNodesAndSets = false; bank->SetCurrentGroup(*sm_BankGroupMain); sm_BankEditControl = bank->PushGroup("Edit", true); // Set as the main group so the Edit Control group is attached to that rather // than the debug bank bank->AddToggle("Edit Mode", &CNodeViewer::sm_IsEditMode, &CNodeViewer::Activate); bank->AddSlider("Current Set", &CNodeViewer::sm_CurrentSet, 0, NOTE_NODES_MAX_NUM_SETS-1, 1, &CNodeViewer::ResetNodeSet); bank->AddText("Current Set Name", sm_GlobalSetName, sizeof(sm_GlobalSetName), false, &CNodeViewer::RenameNodeSet); bank->AddToggle("Show all sets", &CNodeViewer::sm_IsShowAllNodesAndSets); bank->AddButton("Add New Node", &CNodeViewer::AddNewNode); bank->AddButton("Delete Selected Node", &CNodeViewer::DeleteNode); bank->AddCombo("Node Type", &CNodeViewer::sm_NodeLinkType, TYPE_TOTAL, s_NodeType); bank->AddButton("Set Node to Type", &CNodeViewer::SetNodeType); bank->AddCombo("Node Colour", &CNodeViewer::sm_ColourSelection, COLOUR_CHOICE_TOTAL, s_NodeColour); bank->AddButton("Change Node Colour", &CNodeViewer::SetNodeColour); bank->AddButton("Move Node", &CNodeViewer::SetNodePosition); bank->AddButton("Set Node to Ground", &CNodeViewer::SetNodeToGround); bank->AddSlider("Node Size", &CNodeViewer::sm_NodeSize, NOTE_NODES_MIN_NODE_SIZE, NOTE_NODES_MAX_NODE_SIZE, 0.1f, &CNodeViewer::SetNodeSize); bank->AddText("Rename Node", sm_GlobalName, sizeof(sm_GlobalName), false, &CNodeViewer::SetNodeName); bank->AddButton("Select Closest Node", &CNodeViewer::SetClosestNode); bank->AddButton("Warp to Closest Node", &CNodeViewer::SetCameraToClosestNode); bank->AddSlider("Current Node", &CNodeViewer::sm_CurrentSelected, -1, NOTE_NODES_MAX_NUM_NODES_PER_SET-1, 1, &CNodeViewer::LimitChangedNode); bank->AddToggle("Equidistant Drop", &CNodeViewer::sm_IsEquidistantDrop); bank->AddSlider("Distance Between Drops", &CNodeViewer::sm_DistanceBetweenDrops, 20.0f, 200.0f, 1.0f); bank->AddSlider("Draw Distance", &CNodeViewer::sm_DrawDistance, 100.0f, 500.0f, 10.0f); bank->AddButton("Load XML File (File Dialog)", &CNodeViewer::LoadFile); bank->AddText("XML Save Filename", sm_XmlFilename, sizeof(sm_XmlFilename)); bank->AddButton("Save Changes", &CNodeViewer::SaveXmlFile); bank->PopGroup(); bank->UnSetCurrentGroup(*sm_BankGroupMain); sm_XmlAutosaveFilename = "common:/data/node_routes/autosave.xml"; sm_XmlSaveLocation = "common:/data/node_routes/"; // Save location // Make sure local folder exists for autosave file Assert(ASSET.CreateLeadingPath(sm_XmlAutosaveFilename )); safecpy(sm_XmlFilename, ""); // Default save location SetColours(); SetAllNodesEmpty(); } sm_EditorLevel = EDITOR_EDIT; } else { sm_EditorLevel = EDITOR_DISPLAY; } } sm_OriginalKeyboardMode = CControlMgr::GetKeyboard().GetKeyboardMode(); // Commented out to stop database loading for now - needs changing /* // load from database: DebugNodeSys::Init(); for (s32 iCount = 0; iCount < NOTE_NODES_MAX_NUM_SETS; iCount++) { if (!DebugNodeSys::LoadSet(0, iCount, &sm_NodeSets[iCount], sizeof(DebugNodeSet))) Displayf("Node Viewer: Failed to load Set %d\n", iCount); else Displayf("Node Viewer: Successfully loaded Set %d\n", iCount); }*/ } else { sm_EditorLevel = EDITOR_LOADING; SaveFile(true); // Autosave if (bank && sm_BankEditControl && sm_BankGroupMain) { bank->SetCurrentGroup(*sm_BankGroupMain); bank->DeleteGroup(*sm_BankEditControl); bank->UnSetCurrentGroup(*sm_BankGroupMain); sm_BankEditControl = NULL; } SetAllNodesEmpty(); ResetNodeSet(); sm_Colours.Reset(); //DebugNodeSys::Shutdown(); CControlMgr::GetKeyboard().SetCurrentMode(sm_OriginalKeyboardMode); } } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::Shutdown() // PURPOSE: Shuts down the whole of the node viewer // Note ShutdownLevel() is called per level finish. ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::Shutdown(unsigned /*shutdownMode*/) { if (!sm_IsActive) // only shut down if it has been active return; sm_IsActive = false; sm_Colours.Reset(); sm_ScalejaxxPeds.Reset(); // Reset the node arrays for (s32 s = 0; s < sm_NodeSets.GetCount(); s++) { for (s32 n = 0; n < sm_NodeSets[s].nodes.GetCount(); n++) { sm_NodeSets[s].nodes.Reset(); } } sm_NodeSets.Reset(); Activate(); } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::Update() // PURPOSE: main update for the node viewer ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::Update() { CPad& pPad = CControlMgr::GetDebugPad(); camDebugDirector& debugDirector = camInterface::GetDebugDirector(); // check to switch on/off if START & SELECT are pressed in Debug cam/pad: if (debugDirector.IsFreeCamActive() && pPad.StartJustDown() && pPad.SelectJustDown() && // Added as it was possible to turn off node viewer while in keyboard input sm_EditorLevel != EDITOR_TEXT_INPUT) { sm_IsActive = !sm_IsActive; sm_IsEditMode = true; Activate(); return; } // // check to see if debug cam is still active, if not, shut down Node Viewer: // if (!debugDirector.IsFreeCamActive()) { Shutdown(0); return; } if(!sm_IsActive) return; // Disable some of the free cam controls while using node viewer camFreeCamera* freeCamera = camInterface::GetDebugDirector().GetFreeCam(); Assert(freeCamera); freeCamera->SetDPadDownEnabled(false); freeCamera->SetDPadLeftEnabled(false); freeCamera->SetDPadRightEnabled(false); freeCamera->SetDPadUpEnabled(false); freeCamera->SetLeftShoulder1Enabled(false); freeCamera->SetLeftShoulder2Enabled(false); freeCamera->SetRightShoulder1Enabled(false); freeCamera->SetRightShoulder2Enabled(false); // // check pad user input: // switch (sm_EditorLevel) { case EDITOR_EDIT: // at the dropping nodes level { if (pPad.ButtonCircleJustDown()) { sm_IsActive = false; sm_IsEditMode = false; Activate(); break; } if (pPad.RightShoulder1JustDown()) { AddNewNode(); } if (pPad.LeftShoulder1JustDown()) { DeleteNode(); } if (pPad.DPadLeftJustDown()) { SetPreviousNode(); } if (pPad.DPadRightJustDown()) { SetNextNode(); } if (pPad.DPadUpJustDown()) { if (sm_CurrentSet > 0) sm_CurrentSet--; ResetNodeSet(); } if (pPad.DPadDownJustDown()) { if (sm_CurrentSet < NOTE_NODES_MAX_NUM_SETS-1) sm_CurrentSet++; ResetNodeSet(); } if (pPad.RightShoulder2JustDown()) { SetNextLinkType(); } if (pPad.ButtonTriangleJustDown()) { SetNodeType(); } if (pPad.LeftShoulder2JustDown()) { SetNodeToGround(); } if (pPad.ShockButtonRJustDown()) { sm_IsShowAllNodesAndSets = !sm_IsShowAllNodesAndSets; SetScalejaxxVisibility(); } if (pPad.StartJustDown()) { // Only enter text editor if a proper node is selected if(sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].type != TYPE_FREE_NODE) { EnterExitTextEditor(); } } // auto drop a node if sm_IsEquidistantDrop is on: if (sm_IsEquidistantDrop) { if (sm_NodeSets[sm_CurrentSet].node_creation_number > 0) { const Vector3 currentPos = GetDebugCamPos(); const Vector3 lastNodePos = sm_NodeSets[sm_CurrentSet].nodes[sm_NodeSets[sm_CurrentSet].node_creation_number-1].coord; // previous node Vector3 vecDiff = currentPos - lastNodePos; float distanceFromLastNode = vecDiff.Mag(); if (distanceFromLastNode > sm_DistanceBetweenDrops) { AddNewNode(); } } } break; } case EDITOR_LOADING: case EDITOR_DISPLAY: { if (pPad.ButtonCircleJustDown()) { sm_IsActive = false; sm_IsEditMode = false; Activate(); } break; } case EDITOR_TEXT_INPUT: { if (pPad.ButtonCircleJustDown()) { //Properly shut down the text editor EnterExitTextEditor(); sm_IsActive = false; sm_IsEditMode = false; Activate(); break; } ProcessTextEditorInput(); break; } default: { Assertf(0, "NodeViewer: Invalid editor mode!"); break; } } } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::Render() // PURPOSE: renders the nodes ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::Render() { if (!sm_IsActive) return; // // add any helper messages: // CTextLayout MenuTextLayout; #define TEXT_SPACING (0.04f) MenuTextLayout.SetOrientation(FONT_LEFT); MenuTextLayout.SetColor(CRGBA(225,225,225,255)); Vector2 textPos = Vector2(0.05f, 0.05f); Vector2 textSize = Vector2(0.33f, 0.4785f); Vector2 textWrap = Vector2(0.05f, 0.7f); CHudTools::AdjustForWidescreen(WIDESCREEN_FORMAT_SIZE_ONLY, NULL, &textSize, NULL); MenuTextLayout.SetScale(&textSize); MenuTextLayout.SetWrap(&textWrap); // // title: // MenuTextLayout.Render(textPos, "~bold~~italic~N O D E V I E W E R"); textPos.y += (TEXT_SPACING * 2); if (sm_EditorLevel == EDITOR_LOADING) // quick display whilst it loads { MenuTextLayout.Render(textPos, "Loading, please wait..."); CText::Flush(); return; } switch (sm_EditorLevel) { case EDITOR_EDIT: // at the dropping nodes level { // // controls: // MenuTextLayout.Render(textPos, "~PAD_RB~ Add new node" ); textPos.y += TEXT_SPACING; MenuTextLayout.Render(textPos, "~PAD_LB~ Delete Node"); textPos.y += TEXT_SPACING; MenuTextLayout.Render(textPos, "~PAD_DPAD_UP~ ~PAD_DPAD_DOWN~ Select Set"); textPos.y += TEXT_SPACING; MenuTextLayout.Render(textPos, "~PAD_DPAD_LEFT~ ~PAD_DPAD_RIGHT~ Select Node"); textPos.y += TEXT_SPACING; MenuTextLayout.Render(textPos, "~PAD_LT~ Set to Ground"); textPos.y += TEXT_SPACING; MenuTextLayout.Render(textPos, "~PAD_RT~ Change Node Type"); textPos.y += TEXT_SPACING; char const * debugText = NULL; if (sm_IsCreatingLink == false && sm_NodeLinkType == TYPE_LINK_FIRST_NODE) { debugText = "~PAD_Y~ Link (Start)"; } else if (sm_IsCreatingLink == false && sm_NodeLinkType == TYPE_BOX_FIRST_NODE) { debugText = "~PAD_Y~ Box (Start)"; } else if (sm_IsCreatingLink == false && sm_NodeLinkType == TYPE_ARROW) { debugText = "~PAD_Y~ Arrow (Start)"; } else if (sm_IsCreatingLink && sm_NodeLinkType == TYPE_LINK_FIRST_NODE) { debugText = "~PAD_Y~ Link (Finish)"; } else if (sm_IsCreatingLink && sm_NodeLinkType == TYPE_BOX_FIRST_NODE) { debugText = "~PAD_Y~ Box (Finish)"; } else if (sm_IsCreatingLink && sm_NodeLinkType == TYPE_ARROW) { debugText = "~PAD_Y~ Arrow (Finish)"; } else if (sm_NodeLinkType == TYPE_SCALEJAXX) { debugText = "~PAD_Y~ Scalejaxx"; } else { debugText = "~PAD_Y~ Point"; } MenuTextLayout.Render(textPos, debugText ); textPos.y += TEXT_SPACING; MenuTextLayout.Render(textPos, "~PAD_START~ Rename Node"); textPos.y += TEXT_SPACING; debugText = sm_IsShowAllNodesAndSets ? "~PAD_RSTICK_ALL~ Show active Set only" : "~PAD_RSTICK_ALL~ Show all Sets"; MenuTextLayout.Render(textPos, debugText); textPos.y += TEXT_SPACING; MenuTextLayout.Render(textPos, "~PAD_START~ ~PAD_BACK~ Quit"); textPos.y += TEXT_SPACING * 3; // // stats: // char menuString[300] = {0}; formatf(menuString, 300, "Current Set: %d Name: %s", sm_CurrentSet+1, sm_NodeSets[sm_CurrentSet].name.c_str()); MenuTextLayout.Render(textPos, menuString); textPos.y += TEXT_SPACING; if (sm_NodeSets[sm_CurrentSet].node_creation_number != 0) { formatf(menuString, 300, "Current node: %d/%d", sm_CurrentSelected+1, sm_NodeSets[sm_CurrentSet].node_creation_number); MenuTextLayout.Render(textPos, menuString); textPos.y += TEXT_SPACING; } break; } case EDITOR_TEXT_INPUT: { MenuTextLayout.Render(textPos, "RENAMING NODE (Press RET to finish editing)"); textPos.y += TEXT_SPACING * 3; char displayString[280]; formatf(displayString, 280, "New Node Name: %s", sm_GlobalName); MenuTextLayout.Render(textPos, displayString); textPos.y += TEXT_SPACING; break; } case EDITOR_DISPLAY: { break; } default: { Assertf(0, "NodeViewer: Invalid editor mode!"); break; } } CText::Flush(); // render shadow on ground (under camera): Vector3 cameraPos = GetDebugCamPos(); bool hasFoundGround = false; float groundPos = WorldProbe::FindGroundZFor3DCoord(TOP_SURFACE, cameraPos.x, cameraPos.y, cameraPos.z, &hasFoundGround); if (!hasFoundGround) groundPos = cameraPos.z - 10.0f; // Shadow Vector3 groundVec = Vector3(cameraPos.x, cameraPos.y, groundPos); grcDebugDraw::Sphere(groundVec, 2.0f, CRGBA(0,0,0,185)); Color32 textColour(0, 0, 255, NOTE_NODES_NODE_ALPHA); // render current nodes: for (s32 s = 0; s < sm_NodeSets.GetCount(); s++) { if (!sm_IsShowAllNodesAndSets && s != sm_CurrentSet) continue; for (s32 n = 0; n < sm_NodeSets[s].node_creation_number; n++) { if (sm_NodeSets[s].nodes[n].type != TYPE_FREE_NODE) { // Only draw if close to camera const Vector3 distanceFromCam = sm_NodeSets[s].nodes[n].coord - cameraPos; //(static_cast(sm_NodeSets[s].nodes[n].coord)) - cameraPos; if (distanceFromCam.Mag() < sm_DrawDistance) { const s32 nodeLinkIndex = sm_NodeSets[s].nodes[n].link; // Scalejaxx need special treatment - don't draw a sphere if(sm_NodeSets[s].nodes[n].type == TYPE_SCALEJAXX && n == sm_CurrentSelected && s == sm_CurrentSet) { grcDebugDraw::Cross(RCC_VEC3V(sm_NodeSets[s].nodes[n].coord), sm_NodeSets[s].nodes[n].size, sm_Colours[COLOUR_SELECTED]); } else if(sm_NodeSets[s].nodes[n].type == TYPE_SCALEJAXX) { // Same as above here - previously empty - until the ped creation has been implemented grcDebugDraw::Cross(RCC_VEC3V(sm_NodeSets[s].nodes[n].coord), sm_NodeSets[s].nodes[n].size, sm_Colours[sm_NodeSets[s].nodes[n].colour]); } // If we're in display mode, use it's colour else if (sm_IsEditMode == false) { grcDebugDraw::Sphere(sm_NodeSets[s].nodes[n].coord, sm_NodeSets[s].nodes[n].size, sm_Colours[sm_NodeSets[s].nodes[n].colour]); } // If in a different set to the active set, use a set colour else if (sm_IsShowAllNodesAndSets && s != sm_CurrentSet) { grcDebugDraw::Sphere(sm_NodeSets[s].nodes[n].coord, sm_NodeSets[s].nodes[n].size, sm_Colours[COLOUR_INACTIVE_SET]); } else if(n != sm_CurrentSelected) { grcDebugDraw::Sphere(sm_NodeSets[s].nodes[n].coord, sm_NodeSets[s].nodes[n].size, sm_Colours[sm_NodeSets[s].nodes[n].colour]); } // If it is selected, force it to be red else { grcDebugDraw::Sphere(sm_NodeSets[s].nodes[n].coord, sm_NodeSets[s].nodes[n].size, sm_Colours[COLOUR_SELECTED]); } // Only draw if it's a completed link if (nodeLinkIndex != -1) { if (sm_NodeSets[s].nodes[n].type == TYPE_BOX_FIRST_NODE) { grcDebugDraw::BoxAxisAligned(RCC_VEC3V(sm_NodeSets[s].nodes[n].coord), RCC_VEC3V(sm_NodeSets[s].nodes[nodeLinkIndex].coord), sm_Colours[sm_NodeSets[s].nodes[n].colour]); } else if (sm_NodeSets[s].nodes[n].type == TYPE_LINK_FIRST_NODE) { grcDebugDraw::Line(RCC_VEC3V(sm_NodeSets[s].nodes[n].coord), RCC_VEC3V(sm_NodeSets[s].nodes[nodeLinkIndex].coord), sm_Colours[sm_NodeSets[s].nodes[n].colour]); } else if (sm_NodeSets[s].nodes[n].type == TYPE_ARROW) { grcDebugDraw::Arrow(RCC_VEC3V(sm_NodeSets[s].nodes[n].coord), RCC_VEC3V(sm_NodeSets[s].nodes[nodeLinkIndex].coord), 15.0f, sm_Colours[sm_NodeSets[s].nodes[n].colour]); } } Vector3 nodeTextPos = sm_NodeSets[s].nodes[n].coord; nodeTextPos.z += (sm_NodeSets[s].nodes[n].size * 1.3f); grcDebugDraw::Text(nodeTextPos, textColour, sm_NodeSets[s].nodes[n].label); } } } } } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::SetNodeName() // PURPOSE: renames the node ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::SetNodeName() { if (!sm_IsActive) return; char nodeName[NOTE_NODES_CHAR_ARRAY_LENGTH] = ""; if (sm_GlobalName[0] == '\0') formatf(nodeName, NOTE_NODES_CHAR_ARRAY_LENGTH, "Node %d (%d)", sm_CurrentSelected, sm_CurrentSet + 1); else formatf(nodeName, NOTE_NODES_CHAR_ARRAY_LENGTH, "%s (%d)", sm_GlobalName, sm_CurrentSet + 1); sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].label = nodeName; } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::RenameNodeSet() // PURPOSE: renames the node set ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::RenameNodeSet() { if (!sm_IsActive) return; char setName[NOTE_NODES_CHAR_ARRAY_LENGTH] = ""; if (sm_GlobalSetName[0] == '\0') formatf(setName, NOTE_NODES_CHAR_ARRAY_LENGTH, "Node Set %d", sm_CurrentSet+1); else formatf(setName, NOTE_NODES_CHAR_ARRAY_LENGTH, "%s", sm_GlobalSetName); sm_NodeSets[sm_CurrentSet].name = setName; SaveFile(true); } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::GetDebugCamPos() // PURPOSE: returns the camera position ///////////////////////////////////////////////////////////////////////////////////// Vector3 CNodeViewer::GetDebugCamPos() { Vector3 cameraPos(0,0,0); camDebugDirector& debugDirector = camInterface::GetDebugDirector(); const bool isFreeCamActive = debugDirector.IsFreeCamActive(); if (isFreeCamActive) { const camFrame& freeCamFrame = debugDirector.GetFreeCamFrame(); cameraPos = freeCamFrame.GetPosition(); } return cameraPos; } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::AddNewNode() // PURPOSE: adds 1 new node to the current set ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::AddNewNode() { if (!sm_IsActive) return; if (sm_NodeSets[sm_CurrentSet].node_creation_number == NOTE_NODES_MAX_NUM_NODES_PER_SET) return; char buffer[NOTE_NODES_CHAR_ARRAY_LENGTH] = ""; // if this is the 1st node we create on this set, then set it as in use if (sm_NodeSets[sm_CurrentSet].node_creation_number == 0) { sm_NodeSets[sm_CurrentSet].open = true; sm_NodeSets[sm_CurrentSet].creation_number = sm_CurrentSet; // store the set number ResetNodeSet(); } const Vector3 cameraPos = GetDebugCamPos(); NoteNode newNoteNode; newNoteNode.creation_number = sm_CurrentSet; // store set this node was created in newNoteNode.coord = cameraPos; // set node position as camera position newNoteNode.type = TYPE_POINT_NODE; newNoteNode.size = NOTE_NODES_POINT_NODE_SIZE; newNoteNode.colour = sm_ColourSelection; newNoteNode.link = -1; newNoteNode.scalejaxxIndex = -1; buffer[0] = '\0'; formatf(buffer, NOTE_NODES_CHAR_ARRAY_LENGTH, "Node %d (%d)", sm_NodeSets[sm_CurrentSet].node_creation_number + 1, sm_CurrentSet + 1); newNoteNode.label = buffer; // Find the block number to get the name s32 blockNum = CBlockView::GetCurrentBlockInside(cameraPos); const char* blockName = CBlockView::GetBlockName(blockNum); newNoteNode.blockName = blockName; sm_NodeSets[sm_CurrentSet].nodes.PushAndGrow(newNoteNode); sm_CurrentSelected = sm_NodeSets[sm_CurrentSet].node_creation_number; // set the new node as the currently selected one if (sm_NodeSets[sm_CurrentSet].node_creation_number <= NOTE_NODES_MAX_NUM_NODES_PER_SET -1) sm_NodeSets[sm_CurrentSet].node_creation_number++; // move onto next node else { Assertf(0, "NodeViewer: Exceeded max nodes (%d)", NOTE_NODES_MAX_NUM_NODES_PER_SET); } SaveFile(true); } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::DeleteNode() // PURPOSE: removes node ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::DeleteNode() { if (!sm_IsActive) return; if (sm_CurrentSelected == -1) return; if (sm_NodeSets[sm_CurrentSet].node_creation_number <= 0) return; if(sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].type == TYPE_SCALEJAXX) { DeleteScalejaxx(); sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].scalejaxxIndex = -1; } // delete this node sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].type = TYPE_FREE_NODE; sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].link = -1; for (s32 n = 0; n < sm_NodeSets[sm_CurrentSet].nodes.GetCount(); n++) { // Loop through all nodes in set to see if they point to the node just deleted // and remove link if so if (sm_NodeSets[sm_CurrentSet].nodes[n].link == sm_CurrentSelected) { sm_NodeSets[sm_CurrentSet].nodes[n].link = -1; sm_NodeSets[sm_CurrentSet].nodes[n].type = TYPE_POINT_NODE; } // If a node points to a node in a higher index to the one just removed, // decrement the link by 1 (the node positions are moved down later to // fill in the gap by the newly deleted node else if (sm_NodeSets[sm_CurrentSet].nodes[n].link > sm_CurrentSelected) { sm_NodeSets[sm_CurrentSet].nodes[n].link -= 1; } } sm_NodeSets[sm_CurrentSet].nodes.Delete(sm_CurrentSelected); // adjust the counters: sm_NodeSets[sm_CurrentSet].node_creation_number--; sm_CurrentSelected -= 1; LimitChangedNode(); // if this is the 1st node we delete on this set, then set it as no longer in use as there are no active nodes anymore here if (sm_NodeSets[sm_CurrentSet].node_creation_number == 0) sm_NodeSets[sm_CurrentSet].open = false; SaveFile(true); } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::LimitChangedNode() // PURPOSE: deals with wrapping and limiting the adjusted selected node within the // active nodes we have ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::LimitChangedNode() { if (!sm_IsActive) return; if (sm_CurrentSelected < 0) { if (sm_NodeSets[sm_CurrentSet].node_creation_number != 0) sm_CurrentSelected = sm_NodeSets[sm_CurrentSet].node_creation_number-1; // wrap round to end else sm_CurrentSelected = 0; } if (sm_CurrentSelected >= sm_NodeSets[sm_CurrentSet].node_creation_number || sm_CurrentSelected >= NOTE_NODES_MAX_NUM_NODES_PER_SET) sm_CurrentSelected = 0; // wrap round to beginning } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::SetNodeSize() // PURPOSE: Change the size of a node (only used for point nodes) ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::SetNodeSize() { if (!sm_IsActive) return; sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].size = sm_NodeSize; } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::SetNextLinkType() // PURPOSE: Change the type of node/link to be used later ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::SetNextLinkType() { if(!sm_IsActive) return; // Don't allow the type to be changed if in the middle of creating a link if(sm_IsCreatingLink) return; sm_NodeLinkType++; // If past the last type, loop to start if(sm_NodeLinkType == TYPE_TOTAL) { sm_NodeLinkType = 0; } } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::SetNextNode() // PURPOSE: moves on one node ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::SetNextNode() { if (!sm_IsActive) return; sm_CurrentSelected++; LimitChangedNode(); if (sm_NodeSets[sm_CurrentSet].nodes.GetCount() != 0) sm_NodeSize = sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].size; } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::SetPreviousNode() // PURPOSE: goes back one node ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::SetPreviousNode() { if (!sm_IsActive) return; sm_CurrentSelected--; LimitChangedNode(); if (sm_NodeSets[sm_CurrentSet].nodes.GetCount() != 0) sm_NodeSize = sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].size; } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::ResetNodeSet() // PURPOSE: sets the inital values when we go to a new set ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::ResetNodeSet() { if (!sm_IsActive) return; sm_CurrentSelected = 0; safecpy(sm_GlobalSetName, sm_NodeSets[sm_CurrentSet].name); // if this set is not currently open, then display it as empty: if (!sm_NodeSets[sm_CurrentSet].open) { strcat(sm_GlobalSetName, " - EMPTY"); } SetScalejaxxVisibility(); } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::SetScalejaxxVisibility() // PURPOSE: sets a Scalejaxx's visibility depending on sm_IsShowAllNodesAndSets and // the current set in use. ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::SetScalejaxxVisibility() { if (!sm_IsActive) return; if(sm_ScalejaxxPeds.GetCount() > 0) { // Disable drawing on Scalejaxx's not in this set - to save checking every frame for(s32 s = 0; s < sm_NodeSets.GetCount(); s++) { for(s32 n = 0; n < sm_NodeSets[s].nodes.GetCount(); n++) { if ( sm_NodeSets[s].nodes[n].type == TYPE_SCALEJAXX) { const s32 index = sm_NodeSets[s].nodes[n].scalejaxxIndex; if(index != -1) { if(sm_IsShowAllNodesAndSets == false && sm_CurrentSet != s) { sm_ScalejaxxPeds[index]->SetIsVisibleForModule(SETISVISIBLE_MODULE_DEBUG, false); } else { sm_ScalejaxxPeds[index]->SetIsVisibleForModule(SETISVISIBLE_MODULE_DEBUG, true); } } } } } } } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::EnterExitTextEditor() // PURPOSE: enter and exit the text editor ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::EnterExitTextEditor() { if (!sm_IsActive) return; if (sm_EditorLevel == EDITOR_TEXT_INPUT) { safecpy(sm_GlobalName, sm_TextField); sm_TextField[0] = '\0'; sm_TextFieldCursor = 0; SetNodeName(); CControlMgr::GetKeyboard().SetCurrentMode(sm_OriginalKeyboardMode); sm_EditorLevel = EDITOR_EDIT; } else if (sm_EditorLevel == EDITOR_EDIT) { sm_OriginalKeyboardMode = CControlMgr::GetKeyboard().GetKeyboardMode(); // store original mode CControlMgr::GetKeyboard().SetCurrentMode(KEYBOARD_MODE_GAME); sm_EditorLevel = EDITOR_TEXT_INPUT; } } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::ProcessTextEditorInput() // PURPOSE: processes the text editior input ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::ProcessTextEditorInput() { // switch off on RETURN if (CControlMgr::GetKeyboard().GetKeyJustDown(KEY_RETURN, KEYBOARD_MODE_GAME)) { EnterExitTextEditor(); return; } // backspace if (CControlMgr::GetKeyboard().GetKeyJustDown(KEY_BACK, KEYBOARD_MODE_GAME)) { if (sm_TextFieldCursor > 0) sm_TextFieldCursor--; sm_TextField[sm_TextFieldCursor] = '\0'; } else // actual key { const char* pKeysPressed = CControlMgr::GetKeyboard().GetKeyPressedBuffer(KEYBOARD_MODE_GAME); if (pKeysPressed != NULL) { sm_TextField[sm_TextFieldCursor] = *pKeysPressed; if (sm_TextFieldCursor < 253) sm_TextFieldCursor++; sm_TextField[sm_TextFieldCursor] = '\0'; } } formatf(sm_GlobalName, "%s_", sm_TextField); SetNodeName(); // do the rename } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::SetNodeToGround() // PURPOSE: sets the z pos of the node to be the ground position ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::SetNodeToGround() { if (!sm_IsActive) return; if (sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].type == TYPE_FREE_NODE) return; Vector3 nodePos = sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].coord; bool hasFoundGround = false; float groundPos = WorldProbe::FindGroundZFor3DCoord(TOP_SURFACE, nodePos.x, nodePos.y, nodePos.z, &hasFoundGround); if (hasFoundGround) { // Move off the ground by half the sphere height, to stop half being below the floor groundPos += (sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].size / 2); sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].coord.z = groundPos; // set z pos to ground level /*if(sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].type == TYPE_SCALEJAXX) { SetScalejaxxPosition(sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].coord); }*/ } SaveFile(true); } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::SetNodePosition() // PURPOSE: moves the current selected node to current cam position ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::SetNodePosition() { if (!sm_IsActive) return; if (sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].type != TYPE_FREE_NODE) { sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].coord = GetDebugCamPos(); if(sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].type == TYPE_SCALEJAXX) { SetScalejaxxPosition(GetDebugCamPos()); } } SaveFile(true); } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::SetNodeType() // PURPOSE: This is a two-stage process which depends on the sm_IsCreatingBox flag. // If this is the first node to be used to create the link, if it's already // a start node for a link or box it will clear that first. If it's the // second node for a link or box then it completes the link. // NOTES: Links are stored one way - the first node is given the index of the // second node, but the second node doesn't store the index of the first node // to allow chaining in any order, and for nodes to be linked to multiple // times if desired (but only one link can originate from a node). ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::SetNodeType() { if (!sm_IsActive) return; // Don't create a link if no nodes yet! if (sm_NodeSets[sm_CurrentSet].node_creation_number == 0) return; // This is to prevent RAG use trying to change when in middle of creating a link // which is prevented in the Update() function for controller presses too. if (sm_IsCreatingLink && sm_NodeLinkType != sm_FirstLinkType) { Displayf("Link type changed while a link was being created - cancel the link or re-select the same type again"); return; } // If a scalejaxx requested, only add if it's not a scalejaxx already if (sm_NodeLinkType == TYPE_SCALEJAXX && sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].type != TYPE_SCALEJAXX) { sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].type = sm_NodeLinkType; CreateScalejaxx(); return; } // Node is already a scalejaxx - do nothing else if(sm_NodeLinkType == TYPE_SCALEJAXX) { return; } if (sm_NodeLinkType == TYPE_POINT_NODE) { // If node is currently a scalejaxx, it needs deleting from the array and the // manager if(sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].type == TYPE_SCALEJAXX) { DeleteScalejaxx(); sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].scalejaxxIndex = -1; } // Set to point node, leaving whatever size value it may have had previously sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].type = sm_NodeLinkType; sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].link = -1; return; } if (sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].type != TYPE_FREE_NODE) { // This is the first node in a pair if (sm_IsCreatingLink == false) { // If node is currently a scalejaxx, it needs deleting from the array and the // manager if(sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].type == TYPE_SCALEJAXX) { DeleteScalejaxx(); sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].scalejaxxIndex = -1; } // Get rid of current link sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].link = -1; sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].type = sm_NodeLinkType; sm_FirstLinkNode = sm_CurrentSelected; sm_FirstLinkType = sm_NodeLinkType; sm_FirstLinkSet = sm_CurrentSet; sm_IsCreatingLink = true; } else { // If we're trying to complete a link with 2 different types, or // if the same node as the start box node, then just cancel creating if (sm_CurrentSelected == sm_FirstLinkNode || sm_FirstLinkType != sm_NodeLinkType) { sm_FirstLinkNode = -1; sm_FirstLinkSet = -1; sm_FirstLinkType = TYPE_FREE_NODE; sm_IsCreatingLink = false; } else { // Used to avoid nodes being linked each way const s32 nodeExistingLink = sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].link; // Make sure the first link node is in same set and so on if (sm_CurrentSet == sm_FirstLinkSet && sm_CurrentSet != -1 && sm_FirstLinkNode != -1 && nodeExistingLink != sm_FirstLinkNode) { sm_NodeSets[sm_FirstLinkSet].nodes[sm_FirstLinkNode].link = sm_CurrentSelected; sm_FirstLinkNode = -1; sm_FirstLinkSet = -1; sm_IsCreatingLink = false; } } } } SaveFile(true); } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::SetClosestNode() // PURPOSE: selects the closest node to the camera ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::SetClosestNode() { if (!sm_IsActive) return; if (sm_CurrentSelected == -1) return; const Vector3 camPos = GetDebugCamPos(); Vector3 vecBetween; float distBetween; // Find closest Node, just check current set if it's the only one // visible currently. s32 closestNode = -1; float closestDist = 100000.0f; if (sm_IsShowAllNodesAndSets == false) { for(s32 n = 0; n < sm_NodeSets[sm_CurrentSet].nodes.GetCount(); n++) { if(sm_NodeSets[sm_CurrentSet].nodes[n].type != TYPE_FREE_NODE) { vecBetween = sm_NodeSets[sm_CurrentSet].nodes[n].coord - camPos; distBetween = vecBetween.Mag(); if (distBetween < closestDist) { closestDist = distBetween; closestNode = n; } } } } // Otherwise check all sets and nodes to find the closest else { s32 closestNodeSet = -1; for (s32 s = 0; s < sm_NodeSets.GetCount(); s++) { for (s32 n = 0; n < sm_NodeSets[s].nodes.GetCount(); n++) { if(sm_NodeSets[s].nodes[n].type != TYPE_FREE_NODE) { vecBetween = sm_NodeSets[s].nodes[n].coord - camPos; distBetween = vecBetween.Mag(); if (distBetween < closestDist) { closestDist = distBetween; closestNode = n; closestNodeSet = s; } } } } if (closestNodeSet != -1) { sm_CurrentSet = closestNodeSet; ResetNodeSet(); } } // Update if the node has changed if (closestNode != -1) { sm_CurrentSelected = closestNode; } } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::SetColours() // PURPOSE: populates the sm_Colours array ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::SetColours() { sm_Colours.Reserve(COLOUR_TOTAL); // Same order as the eNodeColour enum sm_Colours.Append()= Color32(0, 0, 255, NOTE_NODES_NODE_ALPHA); // Default (Blue) sm_Colours.Append()= Color32(0, 255, 0, NOTE_NODES_NODE_ALPHA); // Green sm_Colours.Append()= Color32(128, 0, 128, NOTE_NODES_NODE_ALPHA); // Purple sm_Colours.Append()= Color32(255, 255, 0, NOTE_NODES_NODE_ALPHA); // Yellow sm_Colours.Append()= Color32(255, 128, 0, NOTE_NODES_NODE_ALPHA); // Orange sm_Colours.Append()= Color32(140, 70, 20, NOTE_NODES_NODE_ALPHA); // Brown sm_Colours.Append()= Color32(0, 0, 0, NOTE_NODES_NODE_ALPHA); // Black sm_Colours.Append()= Color32(255, 0, 0, NOTE_NODES_NODE_ALPHA); // Selected (Red) sm_Colours.Append()= Color32(255, 255, 255, NOTE_NODES_NODE_ALPHA); // Inactive Set (White) } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::SetNodeColour() // PURPOSE: changes the currently selected node's colour (visible once it's not // selected) ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::SetNodeColour() { sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].colour = sm_ColourSelection; SaveFile(true); } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::SetCameraToClosestNode() // PURPOSE: moves the camera to the ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::SetCameraToClosestNode() { if(!sm_IsActive) return; // Find the closest node to the current camera position SetClosestNode(); if(sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].type != TYPE_FREE_NODE) { // Warp the camera to it. camDebugDirector& debugDirector = camInterface::GetDebugDirector(); const bool freeCamActive = debugDirector.IsFreeCamActive(); if(freeCamActive) { camFrame& freeCamFrame = debugDirector.GetFreeCamFrameNonConst(); freeCamFrame.SetPosition(sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].coord); } } } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::CreateScalejaxx() // PURPOSE: Creates and adds a new scalejaxx to the array of peds, and gives the // node an index of the item in the array to be used later if deleted. ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::CreateScalejaxx() { Matrix34 tempMat; tempMat.Identity(); tempMat.d = sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].coord; // Always use the business model, if that's not found, use something from a set index // (need to make sure that's it's not too low otherwise it will be a cutscene character // or something else which might crash fwModelId pedModelId = CModelInfo::GetModelIdFromName("a_m_y_business_01"); if(!pedModelId.IsValid()) { Displayf("Couldn't load a_m_y_business_01."); Assert(false); return; } const CControlledByInfo localAiControl(false, false); // ensure that the model is loaded and ready for drawing for this ped if (!CModelInfo::HaveAssetsLoaded(pedModelId)) { CModelInfo::RequestAssets(pedModelId, STRFLAG_FORCE_LOAD|STRFLAG_PRIORITY_LOAD); CStreaming::LoadAllRequestedObjects(true); } if (!CModelInfo::HaveAssetsLoaded(pedModelId)) { Displayf("Ped has not successfully streamed. Cannot create at this point."); return; } RegdPed& pPedVarDebugPed = sm_ScalejaxxPeds.Grow(); pPedVarDebugPed = CPedFactory::GetFactory()->CreatePed(localAiControl, pedModelId, &tempMat, true, true, false); Assertf(pPedVarDebugPed, "Node Viewer: Couldn't create a debug ped scalejaxx!"); pPedVarDebugPed->SetIsStanding(true); pPedVarDebugPed->SetRagdollState(RAGDOLL_STATE_ANIM_LOCKED); pPedVarDebugPed->GetPedIntelligence()->GetTaskManager()->SetTask(PED_TASK_TREE_PRIMARY, rage_new CTaskDoNothing(-1), PED_TASK_PRIORITY_DEFAULT); pPedVarDebugPed->SetBlockingOfNonTemporaryEvents( true); pPedVarDebugPed->SetFixedPhysics(true); pPedVarDebugPed->m_nPhysicalFlags.bNotDamagedByAnything = true; // Add and disable physics on the ped CGameWorld::Add(pPedVarDebugPed, CGameWorld::OUTSIDE, true); const s32 index = sm_ScalejaxxPeds.GetCount() - 1; if(index != -1) { sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].scalejaxxIndex = index; } else { sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].type = TYPE_POINT_NODE; sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].scalejaxxIndex = -1; } SaveFile(true); } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::DeleteScalejaxx() // PURPOSE: Deletes a scalejaxx from the game world as well as the array holding the // scalejaxx's, then adjusts any other scalejaxx nodes in this set if needed. ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::DeleteScalejaxx() { const s32 indexToRemove = sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].scalejaxxIndex; if(indexToRemove == -1) return; // Remove the ped from the game world and the array CPedFactory::GetFactory()->DestroyPed(sm_ScalejaxxPeds[indexToRemove]); sm_ScalejaxxPeds.Delete(indexToRemove); // Need to go through the rest of the nodes in this set to see if any of them are // scalejaxx's, and if they link to one above the one just deleted, shift down their // index one since one has just been removed from the array. for(s32 s = 0; s < sm_NodeSets.GetCount(); s++) { for(s32 n = 0; n < sm_NodeSets[s].nodes.GetCount(); n++) { if(sm_NodeSets[s].nodes[n].scalejaxxIndex != -1) { if(sm_NodeSets[s].nodes[n].scalejaxxIndex > indexToRemove) { sm_NodeSets[s].nodes[n].scalejaxxIndex--; } } } } sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].scalejaxxIndex = -1; SaveFile(true); } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::SetScalejaxxPosition() // PURPOSE: Changes the position of the scalejaxx ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::SetScalejaxxPosition(const Vector3 &newPos) { if (!sm_IsActive) return; s32 scalejaxxToMove = sm_NodeSets[sm_CurrentSet].nodes[sm_CurrentSelected].scalejaxxIndex; if (scalejaxxToMove < sm_ScalejaxxPeds.GetCount()) { sm_ScalejaxxPeds[scalejaxxToMove]->Teleport(newPos, 0.0f); } SaveFile(true); } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::LoadFile() // PURPOSE: loads the nodes from an xml file ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::LoadFile() { char fileDest[NOTE_NODES_CHAR_ARRAY_LENGTH]; safecpy(fileDest, sm_XmlSaveLocation.c_str()); if(!BANKMGR.OpenFile(fileDest, NOTE_NODES_CHAR_ARRAY_LENGTH, "*.xml", false, "NodeViewer file (*.xml)")) { Errorf("NodeViewer failed to open file!"); return; } SetAllNodesEmpty(); // ensure everything is cleared // Attempt to parse the note nodes XML data file and put our data into an // XML DOM (Document Object Model) tree. parTree* pTree = PARSER.LoadTree(fileDest, "xml"); if(!pTree) { Errorf("Failed to load note nodes file '%s.xml'", fileDest); } // Try to get the root DOM node. const parTreeNode* pRootNode = pTree->GetRoot(); Assert(pRootNode); Assert(stricmp(pRootNode->GetElement().GetName(), "NoteNodes") == 0); // Make sure we have the correct version. int version = pRootNode->GetElement().FindAttributeIntValue("version", 0); Assert(version == NOTE_NODES_FILE_VERSION); if(version != NOTE_NODES_FILE_VERSION) { // Free all the memory created for the DOM tree (and its children). delete pTree; return; } // Go over any and all the DOM child trees and DOM nodes off of the root // DOM node. parTreeNode::ChildNodeIterator i = pRootNode->BeginChildren(); char tempBuff[NOTE_NODES_CHAR_ARRAY_LENGTH] = ""; s32 setNumber = 0; // Counter for set index for(; i != (*i)->EndChildren(); ++i) { Assert(stricmp((*i)->GetElement().GetName(), "Set") == 0); s32 nodeNumber = 0; // Add the note node to our local data. // These are of the form: // const char* tempTitle = (*i)->GetElement().FindAttributeStringValue("name", NULL, tempBuff, NOTE_NODES_CHAR_ARRAY_LENGTH); sm_NodeSets[setNumber].name = tempTitle; sm_NodeSets[setNumber].creation_number = (*i)->GetElement().FindAttributeIntValue("creation_number", -1); sm_NodeSets[setNumber].node_creation_number = (*i)->GetElement().FindAttributeIntValue("node_creation_number", 0); sm_NodeSets[setNumber].open = (*i)->GetElement().FindAttributeBoolValue("open", false); sm_NodeSets[setNumber].nodes.Reset(); sm_NodeSets[setNumber].nodes.Reserve(sm_NodeSets[setNumber].node_creation_number); parTreeNode::ChildNodeIterator j = (*i)->BeginChildren(); for (; j != (*i)->EndChildren(); ++j) { Assert(stricmp((*j)->GetElement().GetName(), "Node") == 0); NoteNode newNoteNode; tempBuff[0] = '\0'; const char* tempLabel = (*j)->GetElement().FindAttributeStringValue("label", NULL, tempBuff, NOTE_NODES_CHAR_ARRAY_LENGTH); newNoteNode.label = tempLabel; newNoteNode.creation_number = (*j)->GetElement().FindAttributeIntValue("creation_number", -1); newNoteNode.link = (*j)->GetElement().FindAttributeIntValue("link", -1); newNoteNode.type = (*j)->GetElement().FindAttributeIntValue("type", TYPE_FREE_NODE); newNoteNode.coord.x = (*j)->GetElement().FindAttributeFloatValue("x", 0.0f); newNoteNode.coord.y = (*j)->GetElement().FindAttributeFloatValue("y", 0.0f); newNoteNode.coord.z = (*j)->GetElement().FindAttributeFloatValue("z", 0.0f); newNoteNode.size = (*j)->GetElement().FindAttributeFloatValue("size", NOTE_NODES_POINT_NODE_SIZE); newNoteNode.colour = (*j)->GetElement().FindAttributeIntValue("colour", COLOUR_DEFAULT); tempBuff[0] = '\0'; const char* tempBlockName = (*j)->GetElement().FindAttributeStringValue("blockName", NULL, tempBuff, NOTE_NODES_CHAR_ARRAY_LENGTH); Displayf("blockname is: %s", tempBlockName); if ( tempBlockName != NULL) { Displayf("Not null"); newNoteNode.blockName = tempBlockName; } // If the node doesn't have a block name already then find out what block it is in // for backwards compatibility else if ( !tempBlockName || tempBlockName == NULL || strcmp(tempBlockName, "") == 0 ) { Displayf("Getting block"); s32 blockNum = CBlockView::GetCurrentBlockInside(newNoteNode.coord); tempBlockName = CBlockView::GetBlockName(blockNum); } Displayf("blockname is now: %s", tempBlockName); newNoteNode.blockName = tempBlockName; sm_NodeSets[setNumber].nodes.Append() = newNoteNode; if(sm_NodeSets[setNumber].nodes[nodeNumber].type == TYPE_SCALEJAXX) { sm_CurrentSet = setNumber; sm_CurrentSelected = nodeNumber; CreateScalejaxx(); } else { newNoteNode.scalejaxxIndex = -1; } nodeNumber++; } setNumber++; } ResetNodeSet(); SetCameraToClosestNode(); // Free all the memory created for the DOM tree (and its children). delete pTree; } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::SaveXmlFile() // PURPOSE: Callback function for user-requested save from RAG // ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::SaveXmlFile() { SaveFile(false); } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::SaveFile(bool isAutoSave) // PURPOSE: saves the nodes to the database // NOTES: The scalejaxx index is not saved since the array is reset when loading // a file, and repopulated then. To make it simpler, it will just get a new // index when added to the array again. ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::SaveFile(bool isAutoSave) { parTree *pTree = rage_new parTree(); Assert(pTree); parTreeNode* pRootNode = pTree->CreateRoot(); Assert(pRootNode); parElement& rootElm = pRootNode->GetElement(); rootElm.SetName("NoteNodes"); rootElm.AddAttribute("version", NOTE_NODES_FILE_VERSION, false); pTree->SetRoot(pRootNode); for (s32 s = 0; s < sm_NodeSets.GetCount(); s++) { // Add the note node to the DOM tree. parTreeNode* pNoteNodeXMLNodeSet = parTreeNode::CreateStdLeaf("Set", "", false); Assert(pNoteNodeXMLNodeSet); pNoteNodeXMLNodeSet->GetElement().AddAttribute("name", sm_NodeSets[s].name, false); pNoteNodeXMLNodeSet->GetElement().AddAttribute("creation_number", sm_NodeSets[s].creation_number, false); pNoteNodeXMLNodeSet->GetElement().AddAttribute("node_creation_number", sm_NodeSets[s].node_creation_number, false); pNoteNodeXMLNodeSet->GetElement().AddAttribute("open", sm_NodeSets[s].open, false); pNoteNodeXMLNodeSet->AppendAsChildOf(pRootNode); for (s32 n = 0; n < sm_NodeSets[s].nodes.GetCount(); n++) { if(sm_NodeSets[s].nodes[n].type != TYPE_FREE_NODE) { // Add the note node to the DOM tree. parTreeNode* pNoteNodeXMLNode = parTreeNode::CreateStdLeaf("Node", "", false); Assert(pNoteNodeXMLNode); pNoteNodeXMLNode->GetElement().AddAttribute("label", sm_NodeSets[s].nodes[n].label, false); pNoteNodeXMLNode->GetElement().AddAttribute("creation_number", sm_NodeSets[s].nodes[n].creation_number, false); pNoteNodeXMLNode->GetElement().AddAttribute("link", sm_NodeSets[s].nodes[n].link, false); pNoteNodeXMLNode->GetElement().AddAttribute("type", sm_NodeSets[s].nodes[n].type, false); pNoteNodeXMLNode->GetElement().AddAttribute("x", sm_NodeSets[s].nodes[n].coord.x, false); pNoteNodeXMLNode->GetElement().AddAttribute("y", sm_NodeSets[s].nodes[n].coord.y, false); pNoteNodeXMLNode->GetElement().AddAttribute("z", sm_NodeSets[s].nodes[n].coord.z, false); pNoteNodeXMLNode->GetElement().AddAttribute("size", sm_NodeSets[s].nodes[n].size, false); pNoteNodeXMLNode->GetElement().AddAttribute("colour", sm_NodeSets[s].nodes[n].colour, false); pNoteNodeXMLNode->GetElement().AddAttribute("blockName", sm_NodeSets[s].nodes[n].blockName, false); pNoteNodeXMLNode->AppendAsChildOf(pNoteNodeXMLNodeSet); } } } bool isSaved; char fileName[NOTE_NODES_CHAR_ARRAY_LENGTH] = "\0"; // Just the filename is given by the user, the location is // always to the NoteNodes folder if(isAutoSave == false) { safecpy(fileName, sm_XmlSaveLocation.c_str()); strcat(fileName, sm_XmlFilename); } else { safecpy(fileName, sm_XmlAutosaveFilename.c_str()); } // Save out the XML DOM tree into a file. isSaved = PARSER.SaveTree(fileName, "xml", pTree, parManager::XML); if(!isSaved) { Errorf("Failed to save note nodes file '%s'", fileName); } else { Displayf("NoteNodes file saved to '%s'", fileName); } // Free all the memory created for the DOM tree (and its children). delete pTree; } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CNodeViewer::CreateBankWidgets() // PURPOSE: creates the bank widget ///////////////////////////////////////////////////////////////////////////////////// void CNodeViewer::InitWidgets() { bkBank *bank = BANKMGR.FindBank(NOTE_NODES_DEBUG_BANK_NAME); if(bank) { sm_BankGroupMain = bank->PushGroup(NOTE_NODES_VIEWER_GROUP_NAME, true); bank->AddToggle("Node Viewer Active", &CNodeViewer::sm_IsActive, &CNodeViewer::Activate); bank->PopGroup(); } } #endif // __BANK // eof