If you have comments or questions concerning this source file, discuss them in the forum.
/*
Copyright (c) 2002 Nicolai Haehnle
See the license.txt for details. If that file was not included in the
source distributions, please email <prefect@rtts.org>
*/
// ed_main.cpp - the map editor
#include "editor.h"
#include "rttspanels.h"
#include "gui_panel.h"
#include "gui_grid.h"
#include "gui_inputbox.h"
#include "gui_listbox.h"
#include "gui_radiobutton.h"
#include "gui_checkbox.h"
#include "gui_popupmenu.h"
/*
==============================================================================
PickUnitDlg IMPLEMENTATION
==============================================================================
*/
class PickUnitDlg : public RttsDialog {
public:
GListBox *m_pListBox;
GLabel *m_pSprite;
GButton *m_pOk;
int m_count;
airunit_type_t **m_types;
public:
PickUnitDlg(GPanel *pParent, int count, airunit_type_t **types);
void Refresh();
};
PickUnitDlg::PickUnitDlg(GPanel *pParent, int count, airunit_type_t **types)
: RttsDialog(pParent, 50, 50, 540, 380)
{
GButton *pBtn;
int i;
m_count = count;
m_types = types;
m_pListBox = new GListBox(this, 30, 30, 300, 320);
m_pListBox->SetColor(192, 192, 192);
m_pListBox->SetSelectedColor(255, 255, 0);
for(i = 0; i < count; i++)
m_pListBox->AddString(types[i]->name);
m_pListBox->SelectChanged.Connect(this, &PickUnitDlg::Refresh);
m_pOk = new RttsButton(this, 390, 30, 120, 30, "OK", valign_center|halign_center);
m_pOk->Clicked.Connect(this, &PickUnitDlg::EndModal);
pBtn = new RttsButton(this, 390, 60, 120, 30, "CANCEL",
valign_center|halign_center, -1);
pBtn->Clicked.Connect(this, &PickUnitDlg::EndModal);
m_pSprite = new GLabel(this, 330, 100, 200, 260, 0, valign_center|halign_center);
Refresh();
}
void PickUnitDlg::Refresh()
{
int id;
id = m_pListBox->GetSelectionId();
if (id >= 0) {
m_pSprite->SetSprite(m_types[id]->sprite);
m_pOk->SetOblivious(false);
m_pOk->SetId(id);
} else
m_pOk->SetOblivious(true);
}
/*
==============================================================================
SettingsDlg IMPLEMENTATION
==============================================================================
*/
class SettingsDlg : public RttsDialog {
public:
GInputBox *m_pLevel;
GInputBox *m_pName;
GInputBox *m_pAuthor;
GButton *m_pOk;
public:
SettingsDlg(GPanel *pParent);
void Refresh();
void Ok(int id);
void PokeLevel(int level);
int PeekLevel();
void PokeName(const char *name);
const char *PeekName();
void PokeAuthor(const char *name);
const char *PeekAuthor();
};
SettingsDlg::SettingsDlg(GPanel *pParent)
: RttsDialog(pParent, 100, 100, 440, 280)
{
GLabel *pLabel;
GButton *pBtn;
pLabel = new GLabel(this, 30, 34, 440, 16, "Level:", 0);
pLabel->SetColor(192, 32, 0);
pLabel->AdjustSize();
m_pLevel = new GInputBox(this, 150, 30, 100, 24, 0, valign_center);
m_pLevel->SetColor(192, 192, 192);
m_pLevel->SetMaxLen(4);
m_pLevel->SetModeInteger(1, 9999);
m_pLevel->Changed.Connect(this, &SettingsDlg::Refresh);
pLabel = new GLabel(this, 30, 70, 440, 16, "Level name:", 0);
pLabel->SetColor(192, 32, 0);
pLabel->AdjustSize();
m_pName = new GInputBox(this, 30, 100, 380, 24, 0, valign_center);
m_pName->SetColor(192, 192, 192);
m_pName->SetMaxLen(31);
m_pName->Changed.Connect(this, &SettingsDlg::Refresh);
pLabel = new GLabel(this, 30, 130, 440, 16, "Author:", 0);
pLabel->SetColor(192, 32, 0);
pLabel->AdjustSize();
m_pAuthor = new GInputBox(this, 30, 160, 380, 24, 0, valign_center);
m_pAuthor->SetColor(192, 192, 192);
m_pAuthor->SetMaxLen(31);
m_pAuthor->Changed.Connect(this, &SettingsDlg::Refresh);
m_pOk = new RttsButton(this, 30, 225, 200, 25, "OK", valign_center, 1);
m_pOk->Clicked.Connect(this, &SettingsDlg::Ok);
SetDefaultButton(m_pOk);
pBtn = new RttsButton(this, 210, 225, 200, 25, "CANCEL", valign_center|halign_right, -1);
pBtn->Clicked.Connect(this, &SettingsDlg::EndModal);
Refresh();
}
void SettingsDlg::Refresh()
{
bool valid;
valid = true;
if (!m_pLevel->IsValid())
valid = false;
if (!m_pName->GetTextLength())
valid = false;
if (!m_pAuthor->GetTextLength())
valid = false;
m_pOk->SetOblivious(!valid);
}
void SettingsDlg::Ok(int id)
{
char buf[MAX_OSPATH];
int level;
level = m_pLevel->GetInteger();
snprintf(buf, sizeof(buf), "levels/%04i.%s.lvl", level, m_pName->GetText());
if (L_FileExists(buf)) {
snprintf(buf, sizeof(buf), "Level %04i - %s exists. Continue anyway?",
level, m_pName->GetText());
if (!RttsMessageBox::YesNo(this, buf))
return;
}
EndModal(1);
}
void SettingsDlg::PokeLevel(int level)
{
char buf[16];
snprintf(buf, sizeof(buf), "%i", level+1);
m_pLevel->SetText(buf);
Refresh();
}
int SettingsDlg::PeekLevel()
{
return m_pLevel->GetInteger() - 1;
}
void SettingsDlg::PokeName(const char *name)
{
m_pName->SetText(name);
Refresh();
}
const char *SettingsDlg::PeekName()
{
return m_pName->GetText();
}
void SettingsDlg::PokeAuthor(const char *name)
{
m_pAuthor->SetText(name);
Refresh();
}
const char *SettingsDlg::PeekAuthor()
{
return m_pAuthor->GetText();
}
/*
==============================================================================
LoadDlg IMPLEMENTATION
==============================================================================
*/
class LoadDlg : public RttsDialog {
public:
GListBox *m_pListBox;
GButton *m_pOk;
public:
LoadDlg(GPanel *pParent);
void Refresh();
const char *PeekFilename();
};
LoadDlg::LoadDlg(GPanel *pParent)
: RttsDialog(pParent, 100, 50, 440, 380)
{
GButton *pBtn;
char **files;
int count, i;
m_pListBox = new GListBox(this, 30, 30, 380, 290);
m_pListBox->SetColor(192, 192, 192);
m_pListBox->SetSelectedColor(255, 255, 0);
count = L_FindFiles("levels", "*.lvl", &files);
for(i = 0; i < count; i++)
m_pListBox->AddString(files[i]);
L_FreeFindFiles(files);
m_pListBox->SelectChanged.Connect(this, &LoadDlg::Refresh);
m_pOk = new RttsButton(this, 30, 330, 0, 30, "OK", valign_center, 1);
m_pOk->Clicked.Connect(this, &LoadDlg::EndModal);
SetDefaultButton(m_pOk);
pBtn = new RttsButton(this, 350, 330, 0, 30, "CANCEL", valign_center|halign_right, -1);
pBtn->Clicked.Connect(this, &LoadDlg::EndModal);
Refresh();
}
void LoadDlg::Refresh()
{
bool valid;
valid = true;
if (!m_pListBox->GetSelection())
valid = false;
m_pOk->SetOblivious(!valid);
}
const char *LoadDlg::PeekFilename()
{
return m_pListBox->GetSelection();
}
/*
==============================================================================
ResizeDlg IMPLEMENTATION
==============================================================================
*/
class ResizeDlg : public RttsDialog {
public:
GRadioButtonGroup m_remove;
GRadioButtonGroup m_start;
GInputBox *m_pLines;
public:
ResizeDlg(GPanel *pParent);
void Ok(int id);
int PeekLines() { return m_pLines->GetInteger(); }
bool PeekRemove() { return m_remove.Get() ? true : false; }
bool PeekStart() { return m_start.Get() ? true : false; }
};
ResizeDlg::ResizeDlg(GPanel *pParent)
: RttsDialog(pParent, 100, 100, 440, 280)
{
GLabel *pLabel;
GRadioButton *radio;
GButton *btn;
pLabel = new GLabel(this, 30, 30, 200, 24, "# of lines:", valign_center);
m_pLines = new GInputBox(this, 230, 30, 150, 24, "0", valign_center);
m_pLines->SetModeInteger(0, 9999);
// Remove group
radio = new RttsRadioButton(this, 30, 65, 380, 24, "Add to level", valign_center);
m_remove.Add(radio);
radio = new RttsRadioButton(this, 30, 90, 380, 24, "Remove from level", valign_center);
m_remove.Add(radio);
m_remove.Set(0);
// Start group
radio = new RttsRadioButton(this, 30, 130, 380, 24, "at end of level", valign_center);
m_start.Add(radio);
radio = new RttsRadioButton(this, 30, 155, 380, 24, "at start of level", valign_center);
m_start.Add(radio);
m_start.Set(0);
// Ok/Cancel buttons
btn = new RttsButton(this, 30, 220, 0, 30, "OK", valign_center, 1);
btn->Clicked.Connect(this, &ResizeDlg::Ok);
SetDefaultButton(btn);
btn = new RttsButton(this, 410, 220, 0, 30, "CANCEL", valign_center|halign_right, -1);
btn->Clicked.Connect(this, &ResizeDlg::EndModal);
}
void ResizeDlg::Ok(int id)
{
bool valid;
valid = true;
if (!m_pLines->IsValid())
valid = false;
if (!valid) {
RttsMessageBox::Ok(this, "Please check all fields");
return;
}
EndModal(1);
}
/*
==============================================================================
OptionsDlg IMPLEMENTATION
==============================================================================
*/
#define OPT_NONE 0
#define OPT_QUIT 1
#define OPT_TEST 2
class OptionsDlg : public RttsDialog {
public:
Editor *editor;
public:
OptionsDlg(Editor *pParent);
void ClearMap(int id);
void Settings(int id);
void LoadMap(int id);
void SaveMap(int id);
void ResizeMap(int id);
};
/*
==============
OptionsDlg::OptionsDlg
Builds the options dialog with all its elements
==============
*/
OptionsDlg::OptionsDlg(Editor *pParent)
: RttsDialog(pParent, 150, 50, 300, 380)
{
GButton *btn;
editor = pParent;
btn = new RttsButton(this, 30, 30, 260, 30, "Test map", valign_center|halign_center, OPT_TEST);
btn->Clicked.Connect(this, &OptionsDlg::EndModal);
btn = new RttsButton(this, 30, 60, 260, 30, "New", valign_center|halign_center, 0);
btn->Clicked.Connect(this, &OptionsDlg::ClearMap);
btn = new RttsButton(this, 30, 90, 260, 30, "Load", valign_center|halign_center, 0);
btn->Clicked.Connect(this, &OptionsDlg::LoadMap);
btn = new RttsButton(this, 30, 120, 260, 30, "Save", valign_center|halign_center, 0);
btn->Clicked.Connect(this, &OptionsDlg::SaveMap);
btn = new RttsButton(this, 30, 150, 260, 30, "Settings", valign_center|halign_center, 0);
btn->Clicked.Connect(this, &OptionsDlg::Settings);
btn = new RttsButton(this, 30, 180, 260, 30, "Resize", valign_center|halign_center, 0);
btn->Clicked.Connect(this, &OptionsDlg::ResizeMap);
btn = new RttsButton(this, 30, 210, 260, 30, "Quit", valign_center|halign_center, OPT_QUIT);
btn->Clicked.Connect(this, &OptionsDlg::EndModal);
btn = new RttsButton(this, 30, 240, 260, 30, "Return to editor",
valign_center|halign_center, OPT_NONE);
btn->Clicked.Connect(this, &OptionsDlg::EndModal);
}
/*
==============
OptionsDlg::ClearMap
Completely wipe the map
==============
*/
void OptionsDlg::ClearMap(int id)
{
if (editor->m_bDirty) {
if (!RttsMessageBox::YesNo(this, "This will destroy all changes. Continue?"))
return;
}
editor->m_level.Clear();
editor->m_level.Resize(true, 10);
editor->m_level.SetDistance(0);
editor->m_bDirty = false;
EndModal(OPT_NONE);
}
/*
==============
OptionsDlg::Settings
Bring up the settings dialog
==============
*/
void OptionsDlg::Settings(int id)
{
SettingsDlg dlg(this);
int code;
int baselevel;
baselevel = editor->m_level.m_iBaseLevel;
if (baselevel >= 0)
dlg.PokeLevel(baselevel);
dlg.PokeName(editor->m_level.m_szName);
dlg.PokeAuthor(editor->m_level.m_szAuthor);
code = dlg.Run();
if (code > 0) {
editor->m_level.m_iBaseLevel = dlg.PeekLevel();
xstrcpy(editor->m_level.m_szName, sizeof(editor->m_level.m_szName),
dlg.PeekName());
xstrcpy(editor->m_level.m_szAuthor, sizeof(editor->m_level.m_szAuthor),
dlg.PeekAuthor());
}
if (id < 0)
EndModal(OPT_NONE);
}
/*
==============
OptionsDlg::SaveMap
Save the current map
==============
*/
void OptionsDlg::SaveMap(int id)
{
bool valid;
if (editor->m_level.m_iBaseLevel < 0 || !editor->m_level.m_szName[0] ||
!editor->m_level.m_szAuthor[0])
Settings(1);
if (editor->m_level.m_iBaseLevel < 0 || !editor->m_level.m_szName[0] ||
!editor->m_level.m_szAuthor[0])
return;
try {
char buf[512];
valid = editor->m_level.Save();
editor->m_bDirty = false;
snprintf(buf, sizeof(buf), "Saved map %04i - %s (%f screens)",
editor->m_level.m_iBaseLevel+1, editor->m_level.m_szName,
editor->m_level.m_iMapLength / 15.0);
if (!valid)
xstrcat(buf, sizeof(buf), "\nMap is not playable.");
RttsMessageBox::Ok(this, buf);
} catch(LError &err) {
char buf[512];
snprintf(buf, sizeof(buf), "Save failed: %s", err.Get());
RttsMessageBox::Ok(this, buf);
}
EndModal(OPT_NONE);
}
/*
==============
OptionsDlg::LoadMap
Bring up the select map dialog and load the selected map
==============
*/
void OptionsDlg::LoadMap(int id)
{
LoadDlg dlg(this);
int code;
if (editor->m_bDirty) {
if (!RttsMessageBox::YesNo(this, "This will destroy all changes. Continue?"))
return;
}
code = dlg.Run();
if (code > 0) {
try {
char buf[256];
bool playable;
snprintf(buf, sizeof(buf), "levels/%s", dlg.PeekFilename());
playable = editor->m_level.Load(buf);
snprintf(buf, sizeof(buf), "Loaded map %04i - %s by %s",
editor->m_level.m_iBaseLevel+1, editor->m_level.m_szName,
editor->m_level.m_szAuthor);
if (!playable)
xstrcat(buf, sizeof(buf), ". Map is not playable.");
RttsMessageBox::Ok(this, buf);
editor->m_bDirty = false;
} catch(LError &err) {
char buf[256];
snprintf(buf, sizeof(buf), "Load failed: %s", err.Get());
RttsMessageBox::Ok(this, buf);
}
}
EndModal(OPT_NONE);
}
/*
==============
OptionsDlg::ResizeMap
Prompts the user for # of lines
==============
*/
void OptionsDlg::ResizeMap(int id)
{
ResizeDlg dlg(this);
int code;
bool start;
int lines;
code = dlg.Run();
if (code > 0) {
lines = dlg.PeekLines();
if (dlg.PeekRemove())
lines = -lines;
start = dlg.PeekStart();
editor->m_level.Resize(start, lines);
editor->m_bDirty = true;
}
EndModal(OPT_NONE);
}
/*
==============================================================================
TestRunDlg IMPLEMENTATION
==============================================================================
*/
class TestRunDlg : public RttsDialog {
public:
GCheckBox *m_pWeapons;
GCheckBox *m_pFromCurpos;
GRadioButtonGroup m_level;
public:
TestRunDlg(GPanel *pParent);
void PeekPlayer(player_t *plr);
int PeekLevel();
inline bool PeekFromCurpos() { return m_pFromCurpos->m_bOn; }
};
TestRunDlg::TestRunDlg(GPanel *pParent)
: RttsDialog(pParent, 100, 100, 440, 240)
{
GButton *btn;
GRadioButton *rb;
m_pWeapons = new RttsCheckBox(this, 30, 30, 0, 0, "Additional weapons");
rb = new RttsRadioButton(this, 30, 60, 0, 0, "Easy");
m_level.Add(rb);
rb = new RttsRadioButton(this, 30, 80, 0, 0, "Medium");
m_level.Add(rb);
rb = new RttsRadioButton(this, 30, 100, 0, 0, "Hard");
m_level.Add(rb);
m_level.Set(0);
m_pFromCurpos = new RttsCheckBox(this, 30, 130, 0, 0, "Start from current pos");
m_pFromCurpos->SetOn(true);
btn = new RttsButton(this, 30, 200, 0, 0, "PLAY", halign_left, 1);
btn->Clicked.Connect(this, &TestRunDlg::EndModal);
SetDefaultButton(btn);
btn = new RttsButton(this, 410, 200, 0, 0, "CANCEL", halign_right, -1);
btn->Clicked.Connect(this, &TestRunDlg::EndModal);
}
void TestRunDlg::PeekPlayer(player_t *plr)
{
int i;
for(i = 0; i < NUM_WEAP; i++)
plr->weapons[i] = m_pWeapons->m_bOn ? 1 : 0;
}
int TestRunDlg::PeekLevel()
{
return m_level.Get();
}
/*
==============================================================================
Editor IMPLEMENTATION
==============================================================================
*/
/*
==============
MakeEditor
Avoids having to put the Editor declaration in a public header file
==============
*/
GPanel *MakeEditor()
{
return new Editor;
}
/*
==============
Editor::Editor
==============
*/
Editor::Editor()
: GPanel(0, 0, 0, 640, 480), m_level(&m_tilesets.m_tiletypes)
{
GButton *pBtn;
SetCanFocus(true);
m_bDirty = false;
m_level.SetEditMode(true);
m_level.Resize(true, 10);
m_level.SetDistance(0);
m_iMode = -1;
m_numunittypes = 0;
m_unittypes = 0;
LoadUnitTypes();
m_tilesets.Load();
m_flScrollSpeed = 0;
m_iScrollTime = 0;
pBtn = new RttsButton(this, 0, 60, 0, 25, "OPTIONS", 0);
pBtn->Clicked.Connect(this, &Editor::Options);
m_pModeButton = new RttsButton(this, 640, 60, 0, 25, "Mode: Tile", halign_right);
m_pModeButton->Clicked.Connect(this, &Editor::ToggleMode);
pBtn = new RttsButton(this, 640, 85, 0, 25, "Select", halign_right);
pBtn->Clicked.Connect(this, &Editor::Mode_Pick);
SetTLM(tlm_tile);
}
/*
==============
Editor::~Editor
==============
*/
Editor::~Editor()
{
int i;
LeaveTLM();
for(i = 0; i < m_numunittypes; i++)
Air_FreeType(m_unittypes[i]);
L_Free(m_unittypes);
}
/*
==============
Editor::LoadUnitTypes
Load all the air/*.txt data
==============
*/
void Editor::LoadUnitTypes()
{
char **names;
char *p;
int count;
count = L_FindFiles("air", "*.txt", &names);
m_unittypes = 0;
m_numunittypes = 0;
try
{
m_unittypes = (airunit_type_t **)L_Malloc(
sizeof(airunit_type_t *)*count, TAG_EDITOR);
for(m_numunittypes = 0; m_numunittypes < count; m_numunittypes++) {
p = strstr(names[m_numunittypes], ".txt");
if (p)
*p = 0;
m_unittypes[m_numunittypes] = Air_GetType(names[m_numunittypes]);
}
}
catch(...)
{
if (m_unittypes) {
while(m_numunittypes--)
Air_FreeType(m_unittypes[m_numunittypes]);
L_Free(m_unittypes);
}
L_FreeFindFiles(names);
throw;
}
L_FreeFindFiles(names);
}
/*
==============
Editor::SetTLM
Change the top-level editor mode
==============
*/
void Editor::SetTLM(int mode)
{
LeaveTLM();
m_iMode = mode;
// Initializes TLM-specific member variables
switch(m_iMode) {
case tlm_tile: Tile_Enter(); m_pModeButton->SetText("Mode: Tile"); break;
case tlm_unit: Unit_Enter(); m_pModeButton->SetText("Mode: Units"); break;
}
m_pModeButton->AdjustSize();
}
/*
===============
Editor::LeaveTLM
Cleanup TLM-specific member variables
===============
*/
void Editor::LeaveTLM()
{
switch(m_iMode) {
case tlm_tile: Tile_Leave(); break;
case tlm_unit: Unit_Leave(); break;
}
}
/*
===============
Editor::SnapToGrid
Snap coordinates to a 16x16 grid
===============
*/
void Editor::SnapToGrid(float *px, float *py)
{
float grid = 16.0;
*px = grid * (int)(*px / grid);
*py = grid * (int)(*py / grid);
}
/*
==============
Editor::EditorQuit
Check whether the map is dirty and ask for saving if needed.
==============
*/
void Editor::EditorQuit(int id)
{
if (m_bDirty) {
if (!RttsMessageBox::YesNo(this, "Quit the editor without saving?"))
return;
}
EndModal(0);
}
/*
==============
Editor::Options
Bring up the Options dialog
==============
*/
void Editor::Options(int id)
{
OptionsDlg opt(this);
int code;
// ouch!
unit_Selected.clear();
code = opt.Run();
if (code == OPT_QUIT)
EditorQuit(0);
else if (code == OPT_TEST)
TestRun();
}
/*
===============
Editor::Mode_Pick
Call the mode-specific "select item" function
===============
*/
void Editor::Mode_Pick(int id)
{
switch(m_iMode) {
case tlm_tile: Tile_Pick(); break;
case tlm_unit: Unit_Pick(); break;
}
}
/*
===============
Editor::ToggleMode
Cycle through editor modes
===============
*/
void Editor::ToggleMode(int id)
{
switch(m_iMode) {
default:
case tlm_tile: SetTLM(tlm_unit); break;
case tlm_unit: SetTLM(tlm_tile); break;
}
}
/*
==============
Editor::TestRun
Load the level into the engine and get playing
==============
*/
void Editor::TestRun()
{
EEngine engine;
player_t plr;
m_level.SetEditMode(false);
engine.SetLevel(&m_level, true);
memset(&plr, 0, sizeof(plr));
xstrcpy(plr.name, sizeof(plr.name), "Editor");
xstrcpy(plr.callsign, sizeof(plr.callsign), "1337");
plr.level = 0;
plr.health = 100;
TestRunDlg dlg(this);
int ret;
ret = dlg.Run();
if (ret > 0)
{
engine.SetLevelAdjust(-1, dlg.PeekLevel());
dlg.PeekPlayer(&plr);
engine.PokePlayer(&plr, config->GetItem("controls"));
if (!dlg.PeekFromCurpos())
m_level.SetDistance(0);
engine.Run();
}
m_level.SetEditMode(true);
}
/*
==============
Editor::Scroll
Perform actual time-based scrolling with acceleration
==============
*/
void Editor::Scroll(float speed)
{
float f;
float dist;
if (!speed) {
m_flScrollSpeed = 0;
m_iScrollTime = 0;
return;
}
if (speed * m_flScrollSpeed < 0) { // "dot product" ;)
m_flScrollSpeed = 0;
m_iScrollTime = 0;
}
if (m_iScrollTime > 4000)
m_iScrollTime = 4000;
f = m_iScrollTime / 500.0;
m_flScrollSpeed = speed * (1 + f);
m_iScrollTime += hal->frametime;
dist = m_level.m_flDistance + m_flScrollSpeed * hal->frametime / 1000.0;
if (m_flScrollSpeed < 0) {
if (m_level.m_flDistance <= 0)
return;
if (dist < 0)
dist = 0;
} else {
if (m_level.m_flDistance >= m_level.m_iMapLength-15.0)
return;
if (dist > m_level.m_iMapLength-15)
dist = m_level.m_iMapLength-15;
}
m_level.SetDistance(dist);
}
/*
==============
Editor::Logic
As long as the left mouse button is held down, the user can just
"paint" tiles
==============
*/
void Editor::Logic()
{
byte *keystate;
int btns, mx, my;
float speed;
btns = hal->GetMouseState(&mx, &my);
keystate = hal->GetKeyState();
switch(m_iMode) {
case tlm_tile: Tile_Logic(keystate, btns, mx, my); break;
}
speed = 0;
if (my < 40)
speed += 5;
else if (my > 440)
speed -= 5;
if (keystate[KEY_UP])
speed += 5;
if (keystate[KEY_DOWN])
speed -= 5;
Scroll(speed);
}
/*
==============
Editor::DrawAirJobs
Draw all airjobs including selection boxes etc...
==============
*/
void Editor::DrawAirJobs()
{
airjob_it aj;
float x, y;
float sx, sy;
int a;
if (m_iMode == tlm_unit)
a = 255;
else
a = 128;
// draw shadows
hal->SetAlphaOnly(true);
for(aj = m_level.m_AirJobs.begin(); aj != m_level.m_AirJobs.end(); aj++) {
spr_frame_t *frame = &aj->type->sprite->base.frames[0];
m_level.LevelToScreen(aj->x, aj->y, &x, &y);
sx = SHADOWX(x);
sy = SHADOWY(y);
sx += frame->offsets[0];
sy += frame->offsets[1];
frame->pic->Draw(sx, sy, 255, 255, 255, (a*64)/256);
}
hal->SetAlphaOnly(false);
// draw units
for(aj = m_level.m_AirJobs.begin(); aj != m_level.m_AirJobs.end(); aj++)
Unit_DrawAirjob(&*aj, 0, 0, a);
// draw movement nodes
if (m_iMode == tlm_unit)
{
for(airjob_it aj = m_level.m_AirJobs.begin(); aj != m_level.m_AirJobs.end(); aj++)
Unit_DrawAirjobNodes(&*aj, 0, 0, a);
}
}
/*
==============
Editor::Draw
Get the map to draw
==============
*/
void Editor::Draw()
{
m_level.Draw();
// if (m_iMode == tlm_unit)
DrawAirJobs();
switch(m_iMode) {
case tlm_tile: Tile_Draw(); break;
case tlm_unit: Unit_Draw(); break;
}
}
/*
==============
Editor::Key
Escape quits the editor
==============
*/
bool Editor::Key(int code, char c, bool down)
{
switch(m_iMode) {
case tlm_tile:
if (Tile_Key(code, c, down))
return true;
break;
case tlm_unit:
if (Unit_Key(code, c, down))
return true;
break;
}
if (down) {
switch(code) {
case KEY_ESCAPE: Options(-1); return true;
case KEY_F5: TestRun(); return true;
case KEY_F6: ToggleMode(-1); return true;
case KEY_t: SetTLM(tlm_tile); Tile_Pick(); return true;
case KEY_u: SetTLM(tlm_unit); Unit_Pick(); return true;
case KEY_UP:
case KEY_DOWN:
return true; // blocked for scrolling (see Logic)
}
}
return GPanel::Key(code, c, down);
}
/*
==============
Editor::MouseButton
Multiplex mouse-clicks to the handler for the current mode
==============
*/
bool Editor::MouseButton(int btn, int x, int y, bool down)
{
// mode specific actions override
switch(m_iMode) {
case tlm_tile:
if (Tile_MouseButton(btn, x, y, down))
return true;
break;
case tlm_unit:
if (Unit_MouseButton(btn, x, y, down))
return true;
break;
}
return GPanel::MouseButton(btn, x, y, down);
}
/*
==============
Editor::MouseMove
Handle mouse moves by the appropriate handler
==============
*/
void Editor::MouseMove(int x, int y)
{
switch(m_iMode) {
//case tlm_tile: Tile_MouseMove(x, y); break;
case tlm_unit: Unit_MouseMove(x, y); break;
}
}
/*
==============================================================================
tlm_tile -- Tile editing mode
==============================================================================
*/
/*
===============
Editor::Tile_Enter
Begin with tile editing (called from SetTLM()).
===============
*/
void Editor::Tile_Enter()
{
m_bInPaint = false;
}
/*
===============
Editor::Tile_Leave
End tile editing
===============
*/
void Editor::Tile_Leave()
{
}
/*
===============
Editor::Tile_FixTile
Fix the tile so that the borders match the surroundings.
change is a bitmask with the borders that can be violated if necessary.
A bitmask of borders that have actually been changed is returned.
===============
*/
int Editor::Tile_FixTile(int tx, int ty, int change)
{
ed_tiletype_t *ett, *curett;
unsigned id;
LPool<unsigned> tp_perfect;
LPool<unsigned> tp_change;
int borders[4]; // desired borders (-1 = any)
int x, y, i;
id = m_level.GetTile(tx, ty);
if (!m_tilesets.m_tiletypes.ValidId(id))
return 0; // *never* replace an unset tile, gets messy as hell
curett = m_tilesets.GetTileTypeEx(id);
// determine the perfect borders
for(i = 0; i < 4; i++) {
// get neighbouring x/y
switch(i) {
case 0: x = tx-1; y = ty; break;
case 1: x = tx+1; y = ty; break;
case 2: x = tx; y = ty-1; break;
case 3: x = tx; y = ty+1; break;
}
if (x < 0 || x >= MAPWIDTH || y < 0 || y >= m_level.m_iMapLength) {
borders[i] = curett->border[i];
continue;
}
// get the neighbour's border attributes
id = m_level.GetTile(x, y);
if (!m_tilesets.m_tiletypes.ValidId(id)) {
// keep the current border, if possible
borders[i] = curett->border[i];
continue;
}
ett = m_tilesets.GetTileTypeEx(id);
borders[i] = ett->border[i ^ 1];
}
// do we need to change anything?
for(i = 0; i < 4; i++) {
if (borders[i] < 0 || curett->border[i] < 0)
continue;
if (borders[i] != curett->border[i])
break;
}
if (i == 4)
return 0; // nothing of significance changed
// walk all tiles to find suitable ones
for(id = 0; id < m_tilesets.m_tiletypes.m_iNumTT; id++) {
bool changes;
ett = m_tilesets.GetTileTypeEx(id);
if (ett->noauto)
continue;
// check the borders
changes = false;
for(i = 0; i < 4; i++) {
if (ett->border[i] < 0)
goto skiptile; // only use well-defined tiles
if (borders[i] >= 0 && ett->border[i] != borders[i]) {
changes = true;
if (!((1 << i) & change))
goto skiptile;
}
}
if (changes)
tp_change.Add(id);
else
tp_perfect.Add(id);
skiptile: ;
}
// try to find a replacement tile
if (tp_perfect.size) {
i = rand() % tp_perfect.size;
id = tp_perfect.items[i];
change = 0;
} else if (tp_change.size) {
i = rand() % tp_change.size;
id = tp_change.items[i];
ett = m_tilesets.GetTileTypeEx(id);
change = 0;
for(i = 0; i < 4; i++) {
if (borders[i] >= 0 && borders[i] != ett->border[i])
change |= (1 << i);
}
} else
return 0;
m_level.SetTile(tx, ty, id);
return change;
}
/*
===============
Editor::Tile_DoPlace
Place the tile with the given ID at the given coordinates.
Automatically adjust neighbouring tiles if necessary
===============
*/
void Editor::Tile_DoPlace(int tx, int ty, int tile)
{
unsigned oldtile;
int modmask, mask, change;
int x, y, i;
lassert(m_tilesets.m_tiletypes.ValidId(tile));
// Save original tile and place new one
oldtile = m_level.GetTile(tx, ty);
m_level.SetTile(tx, ty, tile);
// Fix the direct neighbour if a border has changed
modmask = 0;
for(i = 0; i < 4; i++) {
ed_tiletype_t *orig, *cur;
if (m_tilesets.m_tiletypes.ValidId(oldtile)) {
orig = m_tilesets.GetTileTypeEx(oldtile);
cur = m_tilesets.GetTileTypeEx(tile);
if (orig->border[i] == cur->border[i])
continue;
}
switch(i) {
case 0: x = tx-1; y = ty; change = 12; mask = 0; break;
case 1: x = tx+1; y = ty; change = 12; mask = 1; break;
case 2: x = tx; y = ty-1; change = 3; mask = 0; break;
case 3: x = tx; y = ty+1; change = 3; mask = 2; break;
}
if (x < 0 || x >= MAPWIDTH || y < 0 || y >= m_level.m_iMapLength)
continue;
change = Tile_FixTile(x, y, change);
if (change & 1)
modmask |= 1 << mask;
if (change & 2)
modmask |= 1 << (mask | 1);
if (change & 4)
modmask |= 1 << mask;
if (change & 8)
modmask |= 1 << (mask | 2);
}
// Fix the indirect neighbours that need fixing
for(i = 0; i < 4; i++) {
if (!(modmask & (1 << i)))
continue;
x = tx + ((i & 1) ? 1 : -1);
y = ty + ((i & 2) ? 1 : -1);
Tile_FixTile(x, y, 0);
}
m_bDirty = true;
}
/*
==============
Editor::Tile_Logic
Tile painting
==============
*/
void Editor::Tile_Logic(byte *keystate, int btns, int mx, int my)
{
if (m_bInPaint) {
m_bInPaint = false;
if (IsMouseOver() && (btns & 1) && m_tilesets.isCurTileValid()) {
int tx, ty;
unsigned id;
m_level.TileAtPoint(mx, my, &tx, &ty);
id = m_level.GetTile(tx, ty);
if (id != m_tilesets.m_iCurTile)
Tile_DoPlace(tx, ty, m_tilesets.m_iCurTile);
m_bInPaint = true;
}
}
}
/*
==============
Editor::Tile_MouseButton
Toggle paint mode. This locks painting out when one of the buttons is
pressed.
The actual painting is done in Tile_Logic().
Also handles pipette (right-click) action.
==============
*/
bool Editor::Tile_MouseButton(int btn, int x, int y, bool down)
{
if (btn == 0)
{
if (down)
m_bInPaint = true;
else
m_bInPaint = false;
return true;
}
else if (btn == 1)
{
if (down)
{
int tx, ty;
unsigned id;
m_level.TileAtPoint(x, y, &tx, &ty);
id = m_level.GetTile(tx, ty);
if (m_tilesets.m_tiletypes.ValidId(id))
m_tilesets.SetCurTile(id);
}
return true;
}
return false;
}
/*
==============
Editor::Tile_Key
Cycle through tile types
==============
*/
bool Editor::Tile_Key(int code, char c, bool down)
{
if (down) {
switch(code) {
case KEY_LEFT: m_tilesets.PrevTile(); break;
case KEY_RIGHT: m_tilesets.NextTile(); break;
}
}
return false;
}
/*
==============
Editor::Tile_Draw
Draw the hovering tile preview (if a tile has been selected).
==============
*/
void Editor::Tile_Draw()
{
int mx, my;
int tx, ty;
float x, y;
if (!m_tilesets.isCurTileValid())
return;
hal->GetMouseState(&mx, &my);
m_level.TileAtPoint(mx, my, &tx, &ty);
m_level.PointForTile(tx, ty, &x, &y);
m_tilesets.m_tiletypes.m_pTT[m_tilesets.m_iCurTile].pic->Draw(x, y, 255, 255, 255, 192);
}
/*
==============================================================================
tlm_unit - Unit editing mode
==============================================================================
*/
/*
===============
Editor::SetCurUnit
Editor::SetCurUnitType
Changes which unit is currently selected for placing
===============
*/
void Editor::SetCurUnit(int id)
{
lassert(id >= 0 && id < m_numunittypes);
SetCurUnitByType(m_unittypes[id]);
}
void Editor::SetCurUnitByType(airunit_type_t *type)
{
airjob_t aj(type);
unit_Formation.clear();
unit_Formation.push_back(aj);
}
/*
===============
Editor::Unit_Enter
Enter unit placement mode (called by SetTLM()).
===============
*/
void Editor::Unit_Enter()
{
unit_Formation.clear();
unit_bSelecting = false;
unit_Selected.clear();
unit_bDragStart = false;
}
/*
===============
Editor::Unit_Leave
End unit placement mode
===============
*/
void Editor::Unit_Leave()
{
}
/*
===============
Editor::Unit_IsSelected
Returns true if the given airjob is in the selection list
===============
*/
bool Editor::Unit_IsSelected(airjob_t *aj)
{
return std::find(unit_Selected.begin(), unit_Selected.end(), aj) != unit_Selected.end();
}
/*
===============
Editor::Unit_AddToSelection
Add the airjobs listed in the given vector to the current selection list.
===============
*/
void Editor::Unit_AddToSelection(const std::vector<airjob_t*> &list)
{
for(airjobp_cit aj = list.begin(); aj != list.end(); aj++) {
if (!Unit_IsSelected(*aj))
unit_Selected.push_back(*aj);
}
}
/*
===============
Editor::Unit_RemoveFromSelection
Remove the airjobs listed in the given vector from the current selection list
===============
*/
void Editor::Unit_RemoveFromSelection(const std::vector<airjob_t*> &list)
{
for(airjobp_cit aj = list.begin(); aj != list.end(); aj++) {
airjobp_it place = std::find(unit_Selected.begin(), unit_Selected.end(), *aj);
if (place != unit_Selected.end())
unit_Selected.erase(place);
}
}
/*
===============
Editor::Unit_DragStart
Begin dragging. After you've called this function, you must make sure to call
the other Unit_Drag* functions until you're done.
===============
*/
void Editor::Unit_DragStart(float lx, float ly, bool selectiffail)
{
unit_bDragStart = true;
unit_bDrag = false;
unit_bDragSelectIfFail = selectiffail;
unit_flDragX = lx;
unit_flDragY = ly;
unit_pDragNodeJob = 0;
GrabMouse(true);
}
/*
===============
Editor::Unit_DragStart
Begin dragging a node.
===============
*/
void Editor::Unit_DragStart(float lx, float ly, airjob_t *aj, int idx)
{
unit_bDragStart = true;
unit_bDrag = false;
unit_bDragSelectIfFail = false;
unit_flDragX = lx;
unit_flDragY = ly;
unit_pDragNodeJob = aj;
unit_iDragNodeIdx = idx;
GrabMouse(true);
}
/*
===============
Editor::Unit_DragUpdate
Call when the mouse pos has changed
===============
*/
void Editor::Unit_DragUpdate(float lx, float ly)
{
float sx, sy;
float dx, dy;
sx = unit_flDragX;
sy = unit_flDragY;
SnapToGrid(&sx, &sy);
SnapToGrid(&lx, &ly);
dx = lx - sx;
dy = ly - sy;
if (dx || dy) {
if (!unit_pDragNodeJob)
{
for(airjobp_it aj = unit_Selected.begin(); aj != unit_Selected.end(); aj++)
m_level.SetAirjobPos(*aj, (*aj)->x + dx, (*aj)->y + dy);
}
else
{
byte *keystate = hal->GetKeyState();;
bool shift = keystate[KEY_LSHIFT] || keystate[KEY_RSHIFT];
jobnode_t *jn;
jn = &unit_pDragNodeJob->nodes[unit_iDragNodeIdx];
jn->dx += dx;
jn->dy -= dy;
SnapToGrid(&jn->dx, &jn->dy);
if (!shift) {
if (unit_iDragNodeIdx+1 < unit_pDragNodeJob->nodes.size()) {
jn++;
jn->dx -= dx;
jn->dy += dy;
SnapToGrid(&jn->dx, &jn->dy);
}
}
}
unit_flDragX += dx;
unit_flDragY += dy;
unit_bDrag = true;
m_bDirty = true;
}
}
/*
===============
Editor::Unit_DragEnd
Called when we stop dragging
===============
*/
void Editor::Unit_DragEnd()
{
unit_bDragStart = false;
GrabMouse(false);
// cull merged nodes
if (unit_pDragNodeJob) {
airjob_t *aj = unit_pDragNodeJob;
int i = 0;
while(i < aj->nodes.size()) {
jobnode_it jn = aj->nodes.begin() + i;
if (fabs(jn->dx) < 0.1 && fabs(jn->dy) < 0.1)
aj->nodes.erase(jn);
else
i++;
}
}
// if we didn't actually drag anything it means that we should select
// the unit the user original clicked on
if (!unit_bDrag && unit_bDragSelectIfFail) {
airjob_t *aj = m_level.AirJobAtPoint(unit_flDragX, unit_flDragY);
if (aj) {
std::vector<airjob_t*> units;
units.push_back(aj);
unit_Selected = units;
}
}
}
/*
===============
Editor::Unit_Action_Level
Cycle the level of selected units
===============
*/
void Editor::Unit_Action_Level()
{
if (unit_Selected.empty())
return;
for(airjobp_it job = unit_Selected.begin(); job != unit_Selected.end(); job++) {
(*job)->level++;
if ((*job)->level > 2)
(*job)->level = 0;
}
m_bDirty = true;
}
/*
===============
Editor::Unit_Action_Mirror
Apply a vertical mirror to the currently selected units and their nodes
===============
*/
void Editor::Unit_Action_Mirror()
{
airjobp_it aj;
float x;
if (unit_Selected.empty())
return;
// determine the average X-position
x = 0;
for(aj = unit_Selected.begin(); aj != unit_Selected.end(); aj++)
x += (*aj)->x;
x /= unit_Selected.size();
// actually perform the mirroring
for(aj = unit_Selected.begin(); aj != unit_Selected.end(); aj++) {
m_level.SetAirjobPos(*aj, x + (x - (*aj)->x), (*aj)->y);
for(jobnode_it node = (*aj)->nodes.begin(); node != (*aj)->nodes.end(); node++)
node->dx = -node->dx;
}
m_bDirty = true;
}
/*
===============
Editor::Unit_Action_Copy
Copy the currently selected units into the formation so that they can now
be placed.
===============
*/
void Editor::Unit_Action_Copy()
{
if (unit_Selected.empty())
return;
unit_Formation.clear();
airjobp_it aj = unit_Selected.begin();
float x = (*aj)->x;
float y = (*aj)->y;
do {
airjob_t copy(**aj);
copy.x -= x;
copy.y -= y;
unit_Formation.push_back(copy);
aj++;
} while(aj != unit_Selected.end());
unit_Selected.clear();
}
/*
===============
Editor::Unit_Action_Delete
Delete the currently selected units
===============
*/
void Editor::Unit_Action_Delete()
{
if (unit_Selected.empty())
return;
m_level.DelAirjobs(unit_Selected);
m_bDirty = true;
unit_Selected.clear();
}
/*
===============
Editor::Unit_Node_Add
Add a new node after the current one
===============
*/
void Editor::Unit_Node_Add()
{
airjob_t *aj = unit_pEditNodeJob;
if (!aj)
return;
if (unit_iEditNodeIdx+1 >= aj->nodes.size()) {
jobnode_t jn;
jn.dx = 0;
jn.dy = 128;
aj->nodes.push_back(jn);
} else {
jobnode_t *next;
jobnode_t jn;
next = &aj->nodes[unit_iEditNodeIdx+1];
jn.dx = next->dx/2;
jn.dy = next->dy/2;
next->dx -= jn.dx;
next->dy -= jn.dy;
aj->nodes.insert(aj->nodes.begin() + unit_iEditNodeIdx+1, jn);
}
m_bDirty = true;
}
/*
===============
Editor::Unit_Node_AddRel
Add a new relative node
===============
*/
void Editor::Unit_Node_AddRel()
{
airjob_t *aj = unit_pEditNodeJob;
if (!aj)
return;
jobnode_t jn;
jn.dx = 0;
jn.dy = 128;
aj->nodes.insert(aj->nodes.begin() + unit_iEditNodeIdx+1, jn);
m_bDirty = true;
}
/*
===============
Editor::Unit_Node_Delete
Delete the given node; ensure that the next node doesn't change position
===============
*/
void Editor::Unit_Node_Delete()
{
airjob_t *aj = unit_pEditNodeJob;
if (!aj)
return;
if (unit_iEditNodeIdx+1 == aj->nodes.size()) {
aj->nodes.pop_back();
} else {
jobnode_t *jn = &aj->nodes[unit_iEditNodeIdx];
jobnode_t *next = jn+1;
next->dx += jn->dx;
next->dy += jn->dy;
aj->nodes.erase(aj->nodes.begin() + unit_iEditNodeIdx);
}
m_bDirty = true;
}
/*
===============
Editor::Unit_Node_DeleteRel
Delete a node relatively, i.e. subsequent nodes will change position
===============
*/
void Editor::Unit_Node_DeleteRel()
{
airjob_t *aj = unit_pEditNodeJob;
if (!aj)
return;
aj->nodes.erase(aj->nodes.begin() + unit_iEditNodeIdx);
m_bDirty = true;
}
/*
===============
Editor::Unit_RightClick
Bring up the right-click action menu for the currently selected unit(s)
===============
*/
void Editor::Unit_RightClick()
{
GPopupMenu menu;
menu.AddItem("Level")->signaled.Connect(this, &Editor::Unit_Action_Level);
menu.AddItem("Mirror")->signaled.Connect(this, &Editor::Unit_Action_Mirror);
menu.AddItem("Copy")->signaled.Connect(this, &Editor::Unit_Action_Copy);
// menu.AddItem("Delete")->signaled.Connect(this, &Editor::Unit_Action_Delete);
menu.DisplayNearMouse();
}
/*
===============
Editor::Unit_NodeRightClick
Bring up the right-click action menu for the clicked-on node
===============
*/
void Editor::Unit_NodeRightClick(airjob_t *aj, int idx)
{
GPopupMenu menu;
unit_pEditNodeJob = aj;
unit_iEditNodeIdx = idx;
menu.AddItem("Add Node")->signaled.Connect(this, &Editor::Unit_Node_Add);
menu.AddItem("Add Node (rel)")->signaled.Connect(this, &Editor::Unit_Node_AddRel);
if (idx >= 0) {
menu.AddItem("Delete Node")->signaled.Connect(this, &Editor::Unit_Node_Delete);
menu.AddItem("Delete Node (rel)")->signaled.Connect(this, &Editor::Unit_Node_DeleteRel);
} else {
menu.AddItem("Level")->signaled.Connect(this, &Editor::Unit_Action_Level);
menu.AddItem("Mirror")->signaled.Connect(this, &Editor::Unit_Action_Mirror);
menu.AddItem("Copy")->signaled.Connect(this, &Editor::Unit_Action_Copy);
// menu.AddItem("Delete")->signaled.Connect(this, &Editor::Unit_Action_Delete);
}
menu.DisplayNearMouse();
unit_pEditNodeJob = 0;
}
/*
===============
Editor::Unit_MouseButton
Place units on left-click, handle selections, etc...
===============
*/
bool Editor::Unit_MouseButton(int btn, int x, int y, bool down)
{
byte *keystate = hal->GetKeyState();;
bool shift = keystate[KEY_LSHIFT] || keystate[KEY_RSHIFT];
bool ctrl = keystate[KEY_LCTRL] || keystate[KEY_RCTRL];
float lx, ly;
m_level.ScreenToLevel(x, y, &lx, &ly);
// LEFT CLICK: Selection, dragging
if (btn == 0) {
// Left mouse-button pressed
if (down)
{
airjob_t *aj;
int idx;
// If there's a (non-root) node here, start dragging it
if (unit_Selected.empty())
aj = m_level.NodeAtPoint(lx, ly, &idx);
else
aj = m_level.NodeAtPoint(unit_Selected, lx, ly, &idx);
if (aj && idx >= 0) {
unit_Formation.clear();
Unit_DragStart(lx, ly, aj, idx);
return true;
}
// If there's a unit here, select it and possibly start dragging
aj = m_level.AirJobAtPoint(lx, ly);
if (aj) {
std::vector<airjob_t*> units;
units.push_back(aj);
unit_Formation.clear(); // no longer place units
if (ctrl)
Unit_RemoveFromSelection(units);
else {
bool selectiffail;
if (shift) {
Unit_AddToSelection(units);
selectiffail = false;
} else {
if (!Unit_IsSelected(aj))
unit_Selected = units;
else
selectiffail = true;
}
Unit_DragStart(lx, ly, selectiffail);
}
return true;
}
// Place a unit
if (!unit_Formation.empty()) {
SnapToGrid(&lx, &ly);
m_level.AddAirjobs(unit_Formation, lx, ly);
unit_Selected.clear();
m_bDirty = true;
return true;
}
// If no unit type is applied for placing, start selecting
GrabMouse(true);
unit_Formation.clear();
unit_bSelecting = true;
unit_flSelectStartX = lx;
unit_flSelectStartY = ly;
return true;
}
else
{
// Complete dragging action
if (unit_bDragStart) {
Unit_DragEnd();
return true;
}
// Complete selecting action
if (unit_bSelecting) {
std::vector<airjob_t*> units;
m_level.FindAirJobs(unit_flSelectStartX, unit_flSelectStartY, lx, ly, &units);
if (ctrl)
Unit_RemoveFromSelection(units);
else if (shift)
Unit_AddToSelection(units);
else
unit_Selected = units;
unit_bSelecting = false;
GrabMouse(false);
return true;
}
return true;
}
}
// RIGHT CLICK: Context menu
if (btn == 1) {
if (down)
{
airjob_t *aj;
int idx;
// We assume that the user wants to edit an existing unit when right-clicking
// Therefore, the formation is cleared
unit_Formation.clear();
// Edit nodes
if (unit_Selected.empty())
aj = m_level.NodeAtPoint(lx, ly, &idx);
else
aj = m_level.NodeAtPoint(unit_Selected, lx, ly, &idx);
if (aj) {
Unit_NodeRightClick(aj, idx);
return true;
}
// If there's a unit here, bring up the context menu.
// If the unit isn't currently selected, make it the only selection.
aj = m_level.AirJobAtPoint(lx, ly);
if (aj) {
if (!Unit_IsSelected(aj)) {
unit_Selected.clear();
unit_Selected.push_back(aj);
}
Unit_RightClick();
return true;
}
}
else
{
// does nothing ATM
return true;
}
}
return false;
}
/*
===============
Editor::Unit_MouseMove
Update while dragging
===============
*/
void Editor::Unit_MouseMove(int x, int y)
{
float lx, ly;
m_level.ScreenToLevel(x, y, &lx, &ly);
if (unit_bDragStart) {
Unit_DragUpdate(lx, ly);
return;
}
}
/*
===============
Editor::Unit_Key
Process unit editing shortcuts
===============
*/
bool Editor::Unit_Key(int code, char c, bool down)
{
byte *keystate = hal->GetKeyState();;
bool shift = keystate[KEY_LSHIFT] || keystate[KEY_RSHIFT];
bool ctrl = keystate[KEY_LCTRL] || keystate[KEY_RCTRL];
switch(code) {
case KEY_DELETE:
if (down)
Unit_Action_Delete();
return true;
case KEY_l:
if (down)
Unit_Action_Level();
return true;
case KEY_h:
if (down)
Unit_Action_Mirror();
return true;
case KEY_c:
if (ctrl) {
if (down)
Unit_Action_Copy();
return true;
}
break;
}
return false;
}
/*
===============
Editor::Unit_Draw
Draw the hovering unit preview
===============
*/
void Editor::Unit_Draw()
{
int mx, my;
float sx, sy;
hal->GetMouseState(&mx, &my);
// print the unit preview
if (unit_Formation.size())
{
airjob_it job;
m_level.LevelToScreen(0, 0, &sx, &sy);
for(job = unit_Formation.begin(); job != unit_Formation.end(); job++)
Unit_DrawAirjob(&*job, mx - sx, my - sy, 160);
for(job = unit_Formation.begin(); job != unit_Formation.end(); job++)
Unit_DrawAirjobNodes(&*job, mx - sx, my - sy, 160);
}
// print the selection dragging
if (unit_bSelecting)
{
m_level.LevelToScreen(unit_flSelectStartX, unit_flSelectStartY, &sx, &sy);
hal->DrawLine(mx, my, sx, my, 255, 255, 255);
hal->DrawLine(sx, my, sx, sy, 255, 255, 255);
hal->DrawLine(sx, sy, mx, sy, 255, 255, 255);
hal->DrawLine(mx, sy, mx, my, 255, 255, 255);
}
}
/*
===============
Editor::Unit_DrawAirjob
Draw the airjob, and if applicable additional stuff like selections
NOTE: This function can be called while not in tlm_unit mode; however, it may
choose to access unit_* data when in this mode.
===============
*/
void Editor::Unit_DrawAirjob(airjob_t *aj, float relx, float rely, int a)
{
float x, y;
sprite_t *spr;
m_level.LevelToScreen(aj->x, aj->y, &x, &y);
x += relx;
y += rely;
spr = aj->type->sprite;
SPR_Draw(spr, x, y, -1, hal->framestart, a);
if (m_iMode == tlm_unit)
{
// draw selection markers if necessary
if (Unit_IsSelected(aj)) {
float x1, y1, x2, y2;
x1 = x + spr->mins[0] - 3;
y1 = y + spr->mins[1] - 3;
x2 = x + spr->maxs[0] + 3;
y2 = y + spr->maxs[1] + 3;
hal->DrawLine(x1, y1, x1+10, y1, 255, 192, 0);
hal->DrawLine(x1, y1, x1, y1+10, 255, 192, 0);
hal->DrawLine(x2, y1, x2-10, y1, 255, 192, 0);
hal->DrawLine(x2, y1, x2, y1+10, 255, 192, 0);
hal->DrawLine(x2, y2, x2-10, y2, 255, 192, 0);
hal->DrawLine(x2, y2, x2, y2-10, 255, 192, 0);
hal->DrawLine(x1, y2, x1+10, y2, 255, 192, 0);
hal->DrawLine(x1, y2, x1, y2-10, 255, 192, 0);
}
}
}
/*
===============
Editor::Unit_DrawAirjobNodes
Draw the movement nodes belonging to an airjob
NOTE: This function can be called while not in tlm_unit mode; however, it may
choose to access unit_* data when in this mode.
===============
*/
void Editor::Unit_DrawAirjobNodes(airjob_t *aj, float relx, float rely, int a)
{
int idx;
int r, g, b;
float x, y;
float x2, y2;
if (!unit_Selected.empty())
{
if (std::find(unit_Selected.begin(), unit_Selected.end(), aj) == unit_Selected.end())
a = (a*112)/255;
}
m_level.LevelToScreen(aj->x, aj->y, &x, &y);
x += relx;
y += rely;
if (aj->level == 0) {
r = 0;
g = 255;
b = 0;
} else if (aj->level == 1) {
r = 255;
g = 128;
b = 0;
} else {
r = 255;
g = 0;
b = 0;
}
idx = 0;
for(;;) {
hal->FillRect((int)(x-4), (int)(y-4), 8, 8, r, g, b, a);
if (idx >= aj->nodes.size())
break;
x2 = x + aj->nodes[idx].dx;
y2 = y + aj->nodes[idx].dy;
hal->DrawLine(x, y, x2, y2, r, g, b, a);
x = x2;
y = y2;
idx++;
}
}
/*
==============
Editor::Unit_Pick
Open the pick unit dialog
==============
*/
void Editor::Unit_Pick()
{
lassert(m_iMode == tlm_unit);
int code;
PickUnitDlg dlg(this, m_numunittypes, m_unittypes);
code = dlg.Run();
if (code < 0)
return;
unit_Selected.clear();
SetCurUnit(code);
}
#
if 0
//============================================================================
// em_sel_unit mode
/*
==============
Editor::Select_MouseButton
Simple left-click changes selection to what the mouse is over.
SHIFT+left-click toggles selection of a single unit
==============
*/
bool Editor::Select_MouseButton(int btn, int x, int y, bool down)
{
airjob_t *aj;
byte *keystate;
float lx, ly;
bool shift;
if (btn == 0)
{
keystate = hal->GetKeyState();
m_level.ScreenToLevel(x, y, &lx, &ly);
shift = keystate[KEY_LSHIFT] || keystate[KEY_RSHIFT];
aj = m_level.AirJobAtPoint(lx, ly);
if (down) {
if (aj && IsSelected(aj)) {
m_flSelectDragX = lx;
m_flSelectDragY = ly;
SnapToGrid(&m_flSelectDragX, &m_flSelectDragY);
m_bSelectDragStart = true;
}
} else {
if (!m_bSelectDrag) {
if (shift) {
if (aj)
ToggleSelect(aj);
} else {
L_Free(m_pSelected);
m_pSelected = 0;
m_iNumSelected = 0;
if (aj)
ToggleSelect(aj);
else
SetMode(em_none);
}
}
m_bSelectDragStart = false;
m_bSelectDrag = false;
}
return true;
}
return false;
}
/*
==============
Editor::Select_MouseMove
If dragging, move all selected units
==============
*/
void Editor::Select_MouseMove(int x, int y)
{
float lx, ly;
float dx, dy;
airjob_t *aj;
int i;
if (m_bSelectDragStart) {
m_level.ScreenToLevel(x, y, &lx, &ly);
SnapToGrid(&lx, &ly);
dx = lx - m_flSelectDragX;
dy = ly - m_flSelectDragY;
if (dx || dy) {
for(i = 0; i < m_iNumSelected; i++) {
aj = m_pSelected[i];
m_level.SetAirjobPos(aj, aj->x + dx, aj->y + dy);
}
m_flSelectDragX = lx;
m_flSelectDragY = ly;
m_bSelectDrag = true;
m_bDirty = true;
}
}
}
/*
==============
Editor::Select_Key
DEL kills all airjobs in the current selection
==============
*/
bool Editor::Select_Key(int code, char c, bool down)
{
int i;
if (down) {
if (code == KEY_DELETE) {
if (m_bNodeDrag) // danger of deleting active airjob
return false;
for(i = 0; i < m_iNumSelected; i++)
m_level.DelAirJob(m_pSelected[i]);
ClearSelect();
m_bDirty = true;
return true;
} else if (code == KEY_l) {
for(i = 0; i < m_iNumSelected; i++) {
m_pSelected[i]->level++;
if (m_pSelected[i]->level > 2)
m_pSelected[i]->level = 0;
}
m_bDirty = true;
return true;
}
}
return false;
}
/*
==============
Editor::Select_DrawAirjob
If the airjob is selected, draw a selection box around it
==============
*/
void Editor::Select_DrawAirjob(airjob_t *aj)
{
sprite_t *spr;
float x, y;
float x1, y1, x2, y2;
if (IsSelected(aj)) {
spr = aj->type->sprite;
m_level.LevelToScreen(aj->x, aj->y, &x, &y);
x1 = x + spr->mins[0] - 3;
y1 = y + spr->mins[1] - 3;
x2 = x + spr->maxs[0] + 3;
y2 = y + spr->maxs[1] + 3;
hal->DrawLine(x1, y1, x1+10, y1, 255, 192, 0);
hal->DrawLine(x1, y1, x1, y1+10, 255, 192, 0);
hal->DrawLine(x2, y1, x2-10, y1, 255, 192, 0);
hal->DrawLine(x2, y1, x2, y1+10, 255, 192, 0);
hal->DrawLine(x2, y2, x2-10, y2, 255, 192, 0);
hal->DrawLine(x2, y2, x2, y2-10, 255, 192, 0);
hal->DrawLine(x1, y2, x1+10, y2, 255, 192, 0);
hal->DrawLine(x1, y2, x1, y2-10, 255, 192, 0);
}
}
//============================================================================
// node manipulation (not an independent state)
/*
==============
Editor::Node_Action
Bring up the action dialog box
==============
*/
void Editor::Node_Action(airjob_t *aj, int idx, int x, int y)
{
NodeActionDlg dlg(this, x, y, idx >= 0);
jobnode_t *jn, *next;
int code;
SetMode(em_none);
code = dlg.Run();
if (code == NODE_DELETE && idx >= 0) {
if (idx+1 < aj->numnodes) {
jn = &aj->nodes[idx];
next = jn+1;
next->dx += jn->dx;
next->dy += jn->dy;
}
m_level.DelAirjobNode(aj, idx);
m_bDirty = true;
} else if (code == NODE_LEVEL) {
aj->level++;
if (aj->level > 2)
aj->level = 0;
m_bDirty = true;
} else if (code == NODE_ADD) {
idx++;
jn = m_level.AddAirjobNode(aj, idx, 0, 0);
if (idx+1 < aj->numnodes)
next = &aj->nodes[idx+1];
else
next = 0;
if (next) {
jn->dx = next->dx/2;
jn->dy = next->dy/2;
next->dx -= jn->dx;
next->dy -= jn->dy;
} else {
jn->dx = 0;
jn->dy = 128;
}
m_bDirty = true;
}
}
/*
==============
Editor::Node_MouseButton
RClick: add/remove node
==============
*/
bool Editor::Node_MouseButton(int btn, int x, int y, bool down)
{
airjob_t *aj;
int idx;
float lx, ly;
if (btn == 1 && down)
{
m_level.ScreenToLevel(x, y, &lx, &ly);
aj = m_level.NodeAtPoint(lx, ly, &idx);
if (!aj)
return false;
Node_Action(aj, idx, x, y);
return true;
}
else if (btn == 0)
{
if (down) {
m_level.ScreenToLevel(x, y, &lx, &ly);
aj = m_level.NodeAtPoint(lx, ly, &idx);
if (!aj || idx < 0) // can't move root node
return false;
SetMode(em_none);
m_pNodeDragJob = aj;
m_iNodeDragIdx = idx;
m_flNodeDragX = lx;
m_flNodeDragY = ly;
m_bNodeDrag = true;
return true;
} else {
if (m_bNodeDrag) {
m_bNodeDrag = false;
return true;
}
}
}
return false;
}
/*
==============
Editor::Node_MouseMove
Actually move a node when in drag mode
==============
*/
void Editor::Node_MouseMove(int x, int y)
{
jobnode_t *jn;
float lx, ly;
float dx, dy;
if (!m_bNodeDrag)
return;
m_level.ScreenToLevel(x, y, &lx, &ly);
dx = lx - m_flNodeDragX;
dy = ly - m_flNodeDragY;
SnapToGrid(&dx, &dy);
m_flNodeDragX += dx;
m_flNodeDragY += dy;
if (!dx && !dy)
return;
m_bDirty = true;
// now move the thing
jn = &m_pNodeDragJob->nodes[m_iNodeDragIdx];
jn->dx += dx;
jn->dy -= dy;
SnapToGrid(&jn->dx, &jn->dy);
if (m_iNodeDragIdx+1 >= m_pNodeDragJob->numnodes)
return;
jn++;
jn->dx -= dx;
jn->dy += dy;
SnapToGrid(&jn->dx, &jn->dy);
}
#endif