1336 lines
45 KiB
HTML
1336 lines
45 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Map Editor - Echoes of the Ashes</title>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
||
color: #e0e0e0;
|
||
}
|
||
|
||
/* Login Screen */
|
||
.login-container {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
min-height: 100vh;
|
||
padding: 20px;
|
||
}
|
||
|
||
.login-box {
|
||
background: #2a2a4a;
|
||
padding: 40px;
|
||
border-radius: 12px;
|
||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
||
max-width: 400px;
|
||
width: 100%;
|
||
}
|
||
|
||
.login-box h1 {
|
||
color: #ffa726;
|
||
margin-bottom: 10px;
|
||
font-size: 2em;
|
||
}
|
||
|
||
.login-box p {
|
||
opacity: 0.7;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.form-group {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.form-group label {
|
||
display: block;
|
||
margin-bottom: 8px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.form-group input {
|
||
width: 100%;
|
||
padding: 12px;
|
||
border: 2px solid #3a3a6a;
|
||
border-radius: 6px;
|
||
background: #1a1a3e;
|
||
color: #e0e0e0;
|
||
font-size: 1em;
|
||
}
|
||
|
||
.form-group input:focus {
|
||
outline: none;
|
||
border-color: #ffa726;
|
||
}
|
||
|
||
.btn {
|
||
padding: 12px 24px;
|
||
border: none;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-size: 1em;
|
||
font-weight: 500;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.btn-primary {
|
||
background: linear-gradient(135deg, #ffa726 0%, #ff8f00 100%);
|
||
color: #1a1a2e;
|
||
width: 100%;
|
||
}
|
||
|
||
.btn-primary:hover {
|
||
background: linear-gradient(135deg, #ffb74d 0%, #ffa726 100%);
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 12px rgba(255, 167, 38, 0.4);
|
||
}
|
||
|
||
.error-message {
|
||
background: #d32f2f;
|
||
color: white;
|
||
padding: 12px;
|
||
border-radius: 6px;
|
||
margin-bottom: 20px;
|
||
display: none;
|
||
}
|
||
|
||
/* Editor Interface */
|
||
.editor-container {
|
||
display: none;
|
||
flex-direction: column;
|
||
height: 100vh;
|
||
}
|
||
|
||
.sidebar {
|
||
background: #1a1a3e;
|
||
border-right: 2px solid #2a2a4a;
|
||
overflow-y: auto;
|
||
height: 100%;
|
||
min-height: 0;
|
||
}
|
||
|
||
.sidebar-header {
|
||
padding: 20px;
|
||
background: #2a2a4a;
|
||
border-bottom: 2px solid #3a3a6a;
|
||
}
|
||
|
||
.sidebar-header h2 {
|
||
color: #ffa726;
|
||
font-size: 1.3em;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.location-list {
|
||
padding: 10px;
|
||
}
|
||
|
||
.location-item {
|
||
padding: 12px;
|
||
margin-bottom: 8px;
|
||
background: #2a2a4a;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
border: 2px solid transparent;
|
||
}
|
||
|
||
.location-item:hover {
|
||
background: #3a3a6a;
|
||
border-color: #ffa726;
|
||
}
|
||
|
||
.location-item.active {
|
||
background: #3a3a6a;
|
||
border-color: #ffa726;
|
||
}
|
||
|
||
.location-item-name {
|
||
font-weight: 500;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.location-item-coords {
|
||
font-size: 0.85em;
|
||
opacity: 0.7;
|
||
}
|
||
|
||
.map-canvas-section {
|
||
background: #0f0f1e;
|
||
position: relative;
|
||
overflow: hidden;
|
||
width: 100%;
|
||
height: 100%;
|
||
min-height: 0;
|
||
}
|
||
|
||
.canvas-header {
|
||
padding: 15px 20px;
|
||
background: #1a1a3e;
|
||
border-bottom: 2px solid #2a2a4a;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
min-height: 60px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.canvas-header h1 {
|
||
color: #ffa726;
|
||
font-size: 1.5em;
|
||
margin: 0;
|
||
}
|
||
|
||
/* Tabs */
|
||
.editor-tabs {
|
||
display: flex;
|
||
gap: 5px;
|
||
padding: 10px 20px 0;
|
||
background: #1a1a3e;
|
||
border-bottom: 2px solid #2a2a4a;
|
||
min-height: 52px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.tab-button {
|
||
padding: 10px 20px;
|
||
background: transparent;
|
||
border: none;
|
||
border-bottom: 3px solid transparent;
|
||
color: #e0e0e0;
|
||
cursor: pointer;
|
||
font-size: 1em;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.tab-button:hover {
|
||
background: rgba(255, 167, 38, 0.1);
|
||
color: #ffa726;
|
||
}
|
||
|
||
.tab-button.active {
|
||
color: #ffa726;
|
||
border-bottom-color: #ffa726;
|
||
}
|
||
|
||
.tab-content {
|
||
display: none !important;
|
||
flex: 1;
|
||
overflow: hidden;
|
||
min-height: 0;
|
||
}
|
||
|
||
.tab-content.active {
|
||
display: flex !important;
|
||
}
|
||
|
||
/* Locations tab specific layout */
|
||
#tab-locations {
|
||
display: grid;
|
||
grid-template-columns: 300px 1fr 500px;
|
||
grid-template-rows: 1fr;
|
||
height: 100%;
|
||
min-height: 0;
|
||
gap: 0;
|
||
}
|
||
|
||
#tab-locations .sidebar {
|
||
display: grid !important;
|
||
}
|
||
|
||
/* Management pages */
|
||
.management-container {
|
||
display: flex;
|
||
flex: 1;
|
||
min-height: 0;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.management-list {
|
||
width: 350px;
|
||
flex-shrink: 0;
|
||
background: #1a1a3e;
|
||
border-right: 2px solid #2a2a4a;
|
||
overflow-y: auto;
|
||
padding: 20px;
|
||
min-height: 0;
|
||
}
|
||
|
||
.management-list h3 {
|
||
color: #ffa726;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.management-item {
|
||
background: #2a2a4a;
|
||
padding: 12px;
|
||
margin-bottom: 10px;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
border: 2px solid transparent;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.management-item:hover {
|
||
background: #3a3a6a;
|
||
border-color: #ffa726;
|
||
}
|
||
|
||
.management-item.active {
|
||
background: #3a3a6a;
|
||
border-color: #ffa726;
|
||
}
|
||
|
||
.management-editor {
|
||
flex: 1;
|
||
min-width: 0;
|
||
min-height: 0;
|
||
background: #0f0f1e;
|
||
padding: 30px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.management-editor h2 {
|
||
color: #ffa726;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.form-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||
gap: 20px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.array-item {
|
||
background: #2a2a4a;
|
||
padding: 15px;
|
||
border-radius: 6px;
|
||
margin-bottom: 10px;
|
||
position: relative;
|
||
}
|
||
|
||
.array-item-remove {
|
||
position: absolute;
|
||
top: 10px;
|
||
right: 10px;
|
||
background: #d32f2f;
|
||
color: white;
|
||
border: none;
|
||
width: 30px;
|
||
height: 30px;
|
||
border-radius: 50%;
|
||
cursor: pointer;
|
||
font-size: 1.2em;
|
||
line-height: 1;
|
||
}
|
||
|
||
.array-item-remove:hover {
|
||
background: #f44336;
|
||
}
|
||
|
||
.btn-logout {
|
||
background: linear-gradient(135deg, #f44336 0%, #d32f2f 100%);
|
||
color: white;
|
||
padding: 10px 20px;
|
||
border: none;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-weight: 600;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.btn-logout:hover {
|
||
background: linear-gradient(135deg, #e57373 0%, #f44336 100%);
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 12px rgba(244, 67, 54, 0.4);
|
||
}
|
||
|
||
#editorCanvas {
|
||
width: 100%;
|
||
height: 100%;
|
||
cursor: crosshair;
|
||
}
|
||
|
||
.zoom-controls {
|
||
position: absolute;
|
||
bottom: 20px;
|
||
right: 20px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
z-index: 100;
|
||
}
|
||
|
||
.zoom-btn {
|
||
width: 45px;
|
||
height: 45px;
|
||
background: rgba(42, 42, 74, 0.95);
|
||
border: 2px solid #ffa726;
|
||
border-radius: 8px;
|
||
color: #ffa726;
|
||
font-size: 1.5em;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: all 0.2s ease;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.zoom-btn:hover {
|
||
background: #ffa726;
|
||
color: #1a1a2e;
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
.properties-panel {
|
||
background: #1a1a3e;
|
||
border-left: 2px solid #2a2a4a;
|
||
overflow-y: auto;
|
||
padding: 20px;
|
||
height: 100%;
|
||
min-height: 0;
|
||
}
|
||
|
||
.properties-panel h3 {
|
||
color: #ffa726;
|
||
margin-bottom: 20px;
|
||
font-size: 1.3em;
|
||
}
|
||
|
||
.property-group {
|
||
margin-bottom: 25px;
|
||
}
|
||
|
||
.property-group label {
|
||
display: block;
|
||
margin-bottom: 8px;
|
||
font-weight: 500;
|
||
font-size: 0.9em;
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.property-group input,
|
||
.property-group textarea,
|
||
.property-group select {
|
||
width: 100%;
|
||
padding: 10px;
|
||
border: 2px solid #2a2a4a;
|
||
border-radius: 6px;
|
||
background: #0f0f1e;
|
||
color: #e0e0e0;
|
||
font-size: 0.95em;
|
||
font-family: inherit;
|
||
}
|
||
|
||
.property-group textarea {
|
||
resize: vertical;
|
||
min-height: 80px;
|
||
}
|
||
|
||
.property-group input:focus,
|
||
.property-group textarea:focus,
|
||
.property-group select:focus {
|
||
outline: none;
|
||
border-color: #ffa726;
|
||
}
|
||
|
||
.property-row {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 10px;
|
||
}
|
||
|
||
.spawn-list {
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.spawn-item {
|
||
background: #2a2a4a;
|
||
padding: 10px;
|
||
border-radius: 6px;
|
||
margin-bottom: 8px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.spawn-item-info {
|
||
flex: 1;
|
||
}
|
||
|
||
.spawn-item-name {
|
||
font-weight: 500;
|
||
}
|
||
|
||
.spawn-item-weight {
|
||
font-size: 0.85em;
|
||
opacity: 0.7;
|
||
}
|
||
|
||
.btn-remove {
|
||
background: #d32f2f;
|
||
color: white;
|
||
padding: 6px 12px;
|
||
font-size: 0.85em;
|
||
}
|
||
|
||
.btn-remove:hover {
|
||
background: #f44336;
|
||
}
|
||
|
||
.btn-add {
|
||
background: #4caf50;
|
||
color: white;
|
||
width: 100%;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.btn-add:hover {
|
||
background: #66bb6a;
|
||
}
|
||
|
||
.btn-save {
|
||
background: linear-gradient(135deg, #4caf50 0%, #388e3c 100%);
|
||
color: white;
|
||
padding: 10px 20px;
|
||
border: none;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-weight: 600;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.btn-save:hover {
|
||
background: linear-gradient(135deg, #66bb6a 0%, #4caf50 100%);
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 12px rgba(76, 175, 80, 0.4);
|
||
}
|
||
|
||
.btn-restart {
|
||
background: linear-gradient(135deg, #2196f3 0%, #1976d2 100%);
|
||
color: white;
|
||
padding: 10px 20px;
|
||
border: none;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-weight: 600;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.btn-restart:hover {
|
||
background: linear-gradient(135deg, #42a5f5 0%, #2196f3 100%);
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 12px rgba(33, 150, 243, 0.4);
|
||
}
|
||
|
||
.btn-delete {
|
||
background: linear-gradient(135deg, #f44336 0%, #d32f2f 100%);
|
||
color: white;
|
||
width: 100%;
|
||
margin-top: 10px;
|
||
padding: 14px;
|
||
}
|
||
|
||
.btn-delete:hover {
|
||
background: linear-gradient(135deg, #e57373 0%, #f44336 100%);
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 12px rgba(244, 67, 54, 0.4);
|
||
}
|
||
|
||
.image-preview {
|
||
width: 100%;
|
||
height: 150px;
|
||
background: #0f0f1e;
|
||
border: 2px solid #2a2a4a;
|
||
border-radius: 6px;
|
||
margin-top: 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.image-preview img {
|
||
max-width: 100%;
|
||
max-height: 100%;
|
||
object-fit: contain;
|
||
}
|
||
|
||
.file-input-wrapper {
|
||
position: relative;
|
||
display: inline-block;
|
||
width: 100%;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.file-input-wrapper input[type="file"] {
|
||
position: absolute;
|
||
opacity: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.file-input-label {
|
||
display: block;
|
||
padding: 10px;
|
||
background: #2a2a4a;
|
||
border: 2px solid #3a3a6a;
|
||
border-radius: 6px;
|
||
text-align: center;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.file-input-label:hover {
|
||
background: #3a3a6a;
|
||
border-color: #ffa726;
|
||
}
|
||
|
||
.success-message {
|
||
background: #4caf50;
|
||
color: white;
|
||
padding: 12px;
|
||
border-radius: 6px;
|
||
margin-bottom: 20px;
|
||
display: none;
|
||
}
|
||
|
||
.add-spawn-modal {
|
||
display: none;
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.8);
|
||
z-index: 1000;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.modal-content {
|
||
background: #2a2a4a;
|
||
padding: 30px;
|
||
border-radius: 12px;
|
||
max-width: 500px;
|
||
width: 90%;
|
||
max-height: 80vh;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.modal-header {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.modal-header h3 {
|
||
color: #ffa726;
|
||
}
|
||
|
||
.npc-select-list {
|
||
max-height: 400px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.npc-select-item {
|
||
background: #1a1a3e;
|
||
padding: 12px;
|
||
margin-bottom: 8px;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
border: 2px solid transparent;
|
||
}
|
||
|
||
.npc-select-item:hover {
|
||
background: #2a2a4a;
|
||
border-color: #ffa726;
|
||
}
|
||
|
||
.connection-target-item {
|
||
background: #1a1a3e;
|
||
padding: 12px;
|
||
margin-bottom: 8px;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
border: 2px solid transparent;
|
||
}
|
||
|
||
.connection-target-item:hover {
|
||
background: #2a2a4a;
|
||
border-color: #ffa726;
|
||
}
|
||
|
||
.btn-secondary {
|
||
background: #616161;
|
||
color: white;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.btn-secondary:hover {
|
||
background: #757575;
|
||
}
|
||
|
||
.hidden {
|
||
display: none !important;
|
||
}
|
||
|
||
.connection-section {
|
||
border-top: 2px solid #2a2a4a;
|
||
padding-top: 20px;
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.connection-list {
|
||
max-height: 200px;
|
||
overflow-y: auto;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.connection-direction {
|
||
font-weight: 500;
|
||
color: #ffa726;
|
||
}
|
||
|
||
.connection-item {
|
||
background: #0f0f1e;
|
||
padding: 10px;
|
||
margin-bottom: 8px;
|
||
border-radius: 6px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
border: 1px solid #2a2a4a;
|
||
}
|
||
|
||
.connection-info {
|
||
flex: 1;
|
||
}
|
||
|
||
.connection-name {
|
||
font-weight: 500;
|
||
color: #ffa726;
|
||
}
|
||
|
||
.connection-cost {
|
||
font-size: 0.85em;
|
||
opacity: 0.7;
|
||
}
|
||
|
||
.logs-container {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
padding: 20px;
|
||
background: #0a0a1e;
|
||
}
|
||
|
||
.logs-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
padding-bottom: 15px;
|
||
border-bottom: 2px solid #2a2a4a;
|
||
}
|
||
|
||
.logs-controls {
|
||
display: flex;
|
||
gap: 10px;
|
||
align-items: center;
|
||
}
|
||
|
||
.logs-viewer {
|
||
flex: 1;
|
||
background: #0f0f1e;
|
||
border: 1px solid #2a2a4a;
|
||
border-radius: 8px;
|
||
padding: 15px;
|
||
overflow-y: auto;
|
||
font-family: 'Courier New', monospace;
|
||
font-size: 13px;
|
||
line-height: 1.5;
|
||
color: #e0e0e0;
|
||
}
|
||
|
||
.log-line {
|
||
margin: 2px 0;
|
||
white-space: pre-wrap;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.log-error {
|
||
color: #ff5252;
|
||
}
|
||
|
||
.log-warning {
|
||
color: #ffa726;
|
||
}
|
||
|
||
.log-info {
|
||
color: #4fc3f7;
|
||
}
|
||
|
||
.log-success {
|
||
color: #66bb6a;
|
||
}
|
||
|
||
.btn-refresh-logs {
|
||
background: linear-gradient(135deg, #9c27b0 0%, #7b1fa2 100%);
|
||
color: white;
|
||
padding: 8px 16px;
|
||
border: none;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-weight: 600;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.btn-refresh-logs:hover {
|
||
background: linear-gradient(135deg, #ba68c8 0%, #9c27b0 100%);
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 12px rgba(156, 39, 176, 0.4);
|
||
}
|
||
|
||
.btn-clear-logs {
|
||
background: linear-gradient(135deg, #757575 0%, #616161 100%);
|
||
color: white;
|
||
padding: 8px 16px;
|
||
border: none;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-weight: 600;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.btn-clear-logs:hover {
|
||
background: linear-gradient(135deg, #9e9e9e 0%, #757575 100%);
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 12px rgba(117, 117, 117, 0.4);
|
||
}
|
||
|
||
.logs-lines-input {
|
||
width: 80px;
|
||
padding: 8px;
|
||
border: 1px solid #3a3a6a;
|
||
border-radius: 4px;
|
||
background: #1a1a3e;
|
||
color: #e0e0e0;
|
||
font-weight: 500;
|
||
}
|
||
</style>
|
||
</head>
|
||
|
||
<body>
|
||
<!-- Login Screen -->
|
||
<div class="login-container" id="loginContainer">
|
||
<div class="login-box">
|
||
<h1>🗺️ Map Editor</h1>
|
||
<p>Echoes of the Ashes</p>
|
||
|
||
<div class="error-message" id="loginError"></div>
|
||
|
||
<form id="loginForm">
|
||
<div class="form-group">
|
||
<label for="password">Admin Password</label>
|
||
<input type="password" id="password" name="password" required autocomplete="current-password">
|
||
</div>
|
||
<button type="submit" class="btn btn-primary">Login</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Editor Interface -->
|
||
<div class="editor-container" id="editorContainer">
|
||
<!-- Header with tabs -->
|
||
<div class="canvas-header">
|
||
<h1>🗺️ Game Editor</h1>
|
||
<div style="display: flex; gap: 10px;">
|
||
<button class="btn btn-save" onclick="saveCurrentItem()">💾 Save</button>
|
||
<button class="btn btn-restart" onclick="restartBot()">🔄 Restart Bot</button>
|
||
<button class="btn btn-logout" onclick="logout()">Logout</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="editor-tabs">
|
||
<button class="tab-button active" onclick="switchTab('locations')">📍 Locations</button>
|
||
<button class="tab-button" onclick="switchTab('npcs')">👹 NPCs</button>
|
||
<button class="tab-button" onclick="switchTab('items')">🎒 Items</button>
|
||
<button class="tab-button" onclick="switchTab('interactables')">🎯 Interactables</button>
|
||
<button class="tab-button" onclick="switchTab('players')">👥 Players</button>
|
||
<button class="tab-button" onclick="switchTab('accounts')">👤 Accounts</button>
|
||
<button class="tab-button" onclick="switchTab('logs')">📋 Bot Logs</button>
|
||
</div>
|
||
|
||
<!-- Tab 1: Locations (Map Editor) -->
|
||
<div id="tab-locations" class="tab-content active">
|
||
<!-- Left Sidebar: Location List -->
|
||
<div class="sidebar">
|
||
<div class="sidebar-header">
|
||
<h2>📍 Locations</h2>
|
||
<button class="btn btn-add" onclick="createNewLocation()">+ New Location</button>
|
||
</div>
|
||
<div style="padding: 10px;">
|
||
<input type="text" id="locationSearchInput" placeholder="🔍 Search locations..."
|
||
style="width: 100%; padding: 8px; border: 1px solid #3a3a6a; border-radius: 4px; background: #1a1a3e; color: #e0e0e0;"
|
||
oninput="filterLocationList()">
|
||
</div>
|
||
<div class="location-list" id="locationList">
|
||
<!-- Populated dynamically -->
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Center: Map Canvas -->
|
||
<div class="map-canvas-section">
|
||
<canvas id="editorCanvas"></canvas>
|
||
|
||
<!-- Zoom Controls -->
|
||
<div class="zoom-controls">
|
||
<button class="zoom-btn" onclick="zoomIn()" title="Zoom In">+</button>
|
||
<button class="zoom-btn" onclick="resetView()" title="Reset View">⊙</button>
|
||
<button class="zoom-btn" onclick="zoomOut()" title="Zoom Out">−</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Right Panel: Properties -->
|
||
<div class="properties-panel">
|
||
<div id="noSelectionMessage">
|
||
<h3>No Location Selected</h3>
|
||
<p>Select a location from the list or click on the map to create a new one.</p>
|
||
</div>
|
||
|
||
<div id="propertiesForm" class="hidden">
|
||
<h3>📝 Edit Location</h3>
|
||
|
||
<div class="success-message" id="saveSuccess"></div>
|
||
|
||
<div class="property-group">
|
||
<label for="locationId">Location ID</label>
|
||
<input type="text" id="locationId" readonly>
|
||
</div>
|
||
|
||
<div class="property-group">
|
||
<label>Name</label>
|
||
<div class="property-row">
|
||
<input type="text" id="locationName_en" placeholder="Name (EN)">
|
||
<input type="text" id="locationName_es" placeholder="Nombre (ES)">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="property-group">
|
||
<label>Description</label>
|
||
<textarea id="locationDescription_en" placeholder="Description (EN)" style="margin-bottom: 5px; min-height: 60px;"></textarea>
|
||
<textarea id="locationDescription_es" placeholder="Descripción (ES)" style="min-height: 60px;"></textarea>
|
||
</div>
|
||
|
||
<div class="property-group">
|
||
<label>Coordinates</label>
|
||
<div class="property-row">
|
||
<input type="number" id="locationX" step="0.1" placeholder="X">
|
||
<input type="number" id="locationY" step="0.1" placeholder="Y">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="property-group">
|
||
<label for="dangerLevel">Danger Level (0-4)</label>
|
||
<select id="dangerLevel">
|
||
<option value="0">0 - Safe Zone</option>
|
||
<option value="1">1 - Low Danger</option>
|
||
<option value="2">2 - Medium Danger</option>
|
||
<option value="3">3 - High Danger</option>
|
||
<option value="4">4 - Extreme Danger</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="property-group">
|
||
<label for="encounterRate">Encounter Rate (0.0-1.0)</label>
|
||
<input type="number" id="encounterRate" min="0" max="1" step="0.05" placeholder="0.15">
|
||
</div>
|
||
|
||
<div class="property-group">
|
||
<label for="wanderingChance">Wandering Enemy Chance (0.0-1.0)</label>
|
||
<input type="number" id="wanderingChance" min="0" max="1" step="0.05" placeholder="0.20">
|
||
</div>
|
||
|
||
<div class="property-group">
|
||
<label>Location Image</label>
|
||
<input type="text" id="imagePath" placeholder="images/locations/example.webp">
|
||
<div class="file-input-wrapper">
|
||
<input type="file" id="imageUpload" accept="image/*" onchange="uploadImage()">
|
||
<label for="imageUpload" class="file-input-label">📤 Upload New Image</label>
|
||
</div>
|
||
<div class="image-preview" id="imagePreview">
|
||
<span>No image</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="property-group">
|
||
<label>Spawn NPCs</label>
|
||
<div class="spawn-list" id="spawnList">
|
||
<!-- Populated dynamically -->
|
||
</div>
|
||
<button class="btn btn-add" onclick="showAddSpawnModal()">+ Add NPC Spawn</button>
|
||
</div>
|
||
|
||
<div class="property-group">
|
||
<label>Interactables</label>
|
||
<div class="spawn-list" id="interactablesList">
|
||
<!-- Populated dynamically -->
|
||
</div>
|
||
<button class="btn btn-add" onclick="showAddInteractableModal()">+ Add Interactable</button>
|
||
</div>
|
||
|
||
<!-- Connection Management -->
|
||
<div class="connection-section">
|
||
<div class="property-group">
|
||
<label>🔗 Connections</label>
|
||
<div class="connection-list" id="connectionList">
|
||
<!-- Populated dynamically -->
|
||
</div>
|
||
<button class="btn btn-add" onclick="showAddConnectionModal()">+ Add Connection</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="margin-top: 20px; padding-top: 20px; border-top: 2px solid #3a3a6a;">
|
||
<button class="btn btn-danger" style="width: 100%;" onclick="deleteCurrentLocation()">🗑️ Delete
|
||
Location</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tab 2: NPCs Management -->
|
||
<div id="tab-npcs" class="tab-content">
|
||
<div class="management-container">
|
||
<div class="management-list">
|
||
<div
|
||
style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
|
||
<h3>👹 NPCs</h3>
|
||
<button class="btn btn-add" onclick="createNewNPC()">+ New</button>
|
||
</div>
|
||
<input type="text" id="npcManagementSearch" placeholder="🔍 Search NPCs..."
|
||
style="width: 100%; padding: 8px; margin-bottom: 15px; border: 1px solid #3a3a6a; border-radius: 4px; background: #1a1a3e; color: #e0e0e0;"
|
||
oninput="filterNPCManagementList()">
|
||
<div id="npcManagementList">
|
||
<!-- Populated dynamically -->
|
||
</div>
|
||
</div>
|
||
<div class="management-editor" id="npcEditor">
|
||
<div style="text-align: center; opacity: 0.5; padding: 50px;">
|
||
<h3>Select an NPC or create a new one</h3>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tab 3: Items Management -->
|
||
<div id="tab-items" class="tab-content">
|
||
<div class="management-container">
|
||
<div class="management-list">
|
||
<div
|
||
style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
|
||
<h3>🎒 Items</h3>
|
||
<button class="btn btn-add" onclick="createNewItem()">+ New</button>
|
||
</div>
|
||
<select id="itemCategoryFilter"
|
||
style="width: 100%; padding: 8px; margin-bottom: 10px; border: 1px solid #3a3a6a; border-radius: 4px; background: #1a1a3e; color: #e0e0e0;"
|
||
onchange="filterItemManagementList()">
|
||
<option value="all">All Categories</option>
|
||
<option value="weapon">⚔️ Weapons</option>
|
||
<option value="armor">🛡️ Armor</option>
|
||
<option value="consumable">💊 Consumables</option>
|
||
<option value="resource">📦 Resources</option>
|
||
<option value="container">🎒 Containers</option>
|
||
<option value="tool">🔧 Tools</option>
|
||
<option value="quest">📜 Quest Items</option>
|
||
</select>
|
||
<input type="text" id="itemManagementSearch" placeholder="🔍 Search items..."
|
||
style="width: 100%; padding: 8px; margin-bottom: 15px; border: 1px solid #3a3a6a; border-radius: 4px; background: #1a1a3e; color: #e0e0e0;"
|
||
oninput="filterItemManagementList()">
|
||
<div id="itemManagementList">
|
||
<!-- Populated dynamically -->
|
||
</div>
|
||
</div>
|
||
<div class="management-editor" id="itemEditor">
|
||
<div style="text-align: center; opacity: 0.5; padding: 50px;">
|
||
<h3>Select an item or create a new one</h3>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tab 4: Interactables Management -->
|
||
<div id="tab-interactables" class="tab-content">
|
||
<div class="management-container">
|
||
<!-- Left: List of Interactables -->
|
||
<div class="management-list">
|
||
<h3>🎯 Interactable Templates</h3>
|
||
<button class="btn btn-add" onclick="createNewInteractable()"
|
||
style="width: 100%; margin-bottom: 15px;">+ New Interactable</button>
|
||
<input type="text" id="interactableManagementSearch" placeholder="🔍 Search interactables..."
|
||
oninput="filterInteractableManagementList()"
|
||
style="width: 100%; padding: 8px; margin-bottom: 15px; border: 1px solid #3a3a6a; border-radius: 4px; background: #1a1a3e; color: #e0e0e0;">
|
||
<div id="interactableManagementList"></div>
|
||
</div>
|
||
|
||
<!-- Right: Interactable Editor -->
|
||
<div class="management-editor">
|
||
<div id="interactableEditor" style="padding: 20px;">
|
||
<h3>Select an interactable to edit</h3>
|
||
<p style="opacity: 0.7;">Choose from the list or create a new one.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tab 5: Players Management -->
|
||
<div id="tab-players" class="tab-content">
|
||
<div class="management-container">
|
||
<!-- Left: Player List -->
|
||
<div class="management-list">
|
||
<div
|
||
style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
|
||
<h3>👥 Players</h3>
|
||
</div>
|
||
<input type="text" id="playerSearch" placeholder="🔍 Search players..."
|
||
style="width: 100%; padding: 8px; margin-bottom: 10px; border: 1px solid #3a3a6a; border-radius: 4px; background: #1a1a3e; color: #e0e0e0;"
|
||
oninput="filterPlayerList()">
|
||
<select id="playerStatusFilter"
|
||
style="width: 100%; padding: 8px; margin-bottom: 15px; border: 1px solid #3a3a6a; border-radius: 4px; background: #1a1a3e; color: #e0e0e0;"
|
||
onchange="filterPlayerList()">
|
||
<option value="all">All Players</option>
|
||
<option value="active">Active</option>
|
||
<option value="banned">Banned</option>
|
||
<option value="premium">Premium</option>
|
||
</select>
|
||
<div id="playerList">
|
||
<!-- Populated dynamically -->
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Right: Player Editor -->
|
||
<div class="management-editor" id="playerEditor">
|
||
<div style="text-align: center; opacity: 0.5; padding: 50px;">
|
||
<h3>Select a player to edit</h3>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tab 6: Accounts Management -->
|
||
<div id="tab-accounts" class="tab-content">
|
||
<div class="management-container">
|
||
<!-- Left: Account List -->
|
||
<div class="management-list">
|
||
<div
|
||
style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
|
||
<h3>👤 Accounts</h3>
|
||
</div>
|
||
<input type="text" id="accountSearch" placeholder="🔍 Search accounts..."
|
||
style="width: 100%; padding: 8px; margin-bottom: 15px; border: 1px solid #3a3a6a; border-radius: 4px; background: #1a1a3e; color: #e0e0e0;"
|
||
oninput="filterAccountList()">
|
||
<div id="accountList">
|
||
<!-- Populated dynamically -->
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Right: Account Editor -->
|
||
<div class="management-editor" id="accountEditor">
|
||
<div style="text-align: center; opacity: 0.5; padding: 50px;">
|
||
<h3>Select an account to edit</h3>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tab 7: Bot Logs -->
|
||
<div id="tab-logs" class="tab-content">
|
||
<div class="logs-container">
|
||
<div class="logs-header">
|
||
<h2>📋 Bot Logs (echoes_of_the_ashes_api)</h2>
|
||
<div class="logs-controls">
|
||
<label style="color: #e0e0e0; font-weight: 500;">Lines:</label>
|
||
<input type="number" id="logsLineCount" class="logs-lines-input" value="100" min="10" max="1000"
|
||
step="10">
|
||
<button class="btn btn-refresh-logs" onclick="refreshBotLogs()">🔄 Refresh</button>
|
||
<button class="btn btn-clear-logs" onclick="clearLogsDisplay()">🗑️ Clear</button>
|
||
</div>
|
||
</div>
|
||
<div class="logs-viewer" id="logsViewer">
|
||
<div style="opacity: 0.5; text-align: center; padding: 20px;">
|
||
Click "Refresh" to load bot logs...
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div> <!-- Close editor-container -->
|
||
|
||
<!-- Add Spawn Modal -->
|
||
<div class="add-spawn-modal" id="addSpawnModal">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h3>Add NPC Spawn</h3>
|
||
</div>
|
||
<div class="property-group">
|
||
<input type="text" id="npcSearch" placeholder="🔍 Search NPCs..." oninput="filterNPCList()"
|
||
style="margin-bottom: 10px;">
|
||
</div>
|
||
<div class="npc-select-list" id="npcSelectList">
|
||
<!-- Populated dynamically -->
|
||
</div>
|
||
<button class="btn btn-secondary" onclick="closeAddSpawnModal()">Cancel</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Add Connection Modal -->
|
||
<div class="add-spawn-modal" id="addConnectionModal">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h3>Add Connection</h3>
|
||
<p style="opacity: 0.7; margin-top: 10px;">Select a destination location</p>
|
||
</div>
|
||
<div class="property-group">
|
||
<input type="text" id="connectionSearch" placeholder="🔍 Search locations..."
|
||
oninput="filterConnectionList()" style="margin-bottom: 10px;">
|
||
</div>
|
||
<div class="npc-select-list" id="connectionTargetList">
|
||
<!-- Populated dynamically -->
|
||
</div>
|
||
<button class="btn btn-secondary" onclick="closeAddConnectionModal()">Cancel</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Select Direction Modal -->
|
||
<div class="add-spawn-modal" id="selectDirectionModal">
|
||
<div class="modal-content" style="max-width: 500px;">
|
||
<div class="modal-header">
|
||
<h3>Select Direction</h3>
|
||
<p style="opacity: 0.7; margin-top: 10px;" id="directionModalSubtitle">
|
||
From <strong id="fromLocationName"></strong> to <strong id="toLocationName"></strong>
|
||
</p>
|
||
</div>
|
||
<div class="property-group">
|
||
<label for="directionSelect">Direction from source:</label>
|
||
<select id="directionSelect" style="width: 100%; padding: 10px; border-radius: 6px;
|
||
background: #1a1a3e; color: #e0e0e0; border: 2px solid #3a3a6a; font-size: 1em;">
|
||
<option value="">-- Select Direction --</option>
|
||
<option value="north">⬆️ North</option>
|
||
<option value="south">⬇️ South</option>
|
||
<option value="east">➡️ East</option>
|
||
<option value="west">⬅️ West</option>
|
||
<option value="northeast">↗️ Northeast</option>
|
||
<option value="northwest">↖️ Northwest</option>
|
||
<option value="southeast">↘️ Southeast</option>
|
||
<option value="southwest">↙️ Southwest</option>
|
||
<option value="up">⬆️ Up</option>
|
||
<option value="down">⬇️ Down</option>
|
||
<option value="inside">🚪 Inside</option>
|
||
<option value="outside">🚪 Outside</option>
|
||
</select>
|
||
</div>
|
||
<div class="property-group"
|
||
style="background: #2a2a4a; padding: 15px; border-radius: 8px; margin-top: 15px;">
|
||
<label style="display: flex; align-items: center; cursor: pointer;">
|
||
<input type="checkbox" id="autoReverseConnection" checked
|
||
style="margin-right: 10px; width: 20px; height: 20px;">
|
||
<span>
|
||
<strong>Automatically add reverse connection</strong><br>
|
||
<span style="opacity: 0.7; font-size: 0.9em;" id="reverseConnectionText">
|
||
Will also create a connection back from destination to source
|
||
</span>
|
||
</span>
|
||
</label>
|
||
</div>
|
||
<div style="display: flex; gap: 10px; margin-top: 20px;">
|
||
<button class="btn btn-primary" onclick="confirmAddConnection()">Add Connection</button>
|
||
<button class="btn btn-secondary" onclick="closeSelectDirectionModal()">Cancel</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Delete Connection Modal -->
|
||
<div class="add-spawn-modal" id="deleteConnectionModal">
|
||
<div class="modal-content" style="max-width: 500px;">
|
||
<div class="modal-header">
|
||
<h3 style="color: #f44336;">⚠️ Delete Connection</h3>
|
||
<p style="opacity: 0.7; margin-top: 10px;">
|
||
<strong id="deleteFromLocationName"></strong> → <span id="deleteDirectionText"></span> → <strong
|
||
id="deleteToLocationName"></strong>
|
||
</p>
|
||
</div>
|
||
<div class="property-group"
|
||
style="background: #3a2a2a; padding: 15px; border-radius: 8px; border: 2px solid #f44336;">
|
||
<p style="margin-bottom: 10px;">⚠️ This will permanently delete this connection.</p>
|
||
</div>
|
||
<div class="property-group" id="deleteReverseOption"
|
||
style="background: #2a2a4a; padding: 15px; border-radius: 8px; margin-top: 15px;">
|
||
<label style="display: flex; align-items: center; cursor: pointer;">
|
||
<input type="checkbox" id="deleteBothConnections" checked
|
||
style="margin-right: 10px; width: 20px; height: 20px;">
|
||
<span>
|
||
<strong>Also delete reverse connection</strong><br>
|
||
<span style="opacity: 0.7; font-size: 0.9em;" id="deleteReverseText">
|
||
Will also delete the connection back
|
||
</span>
|
||
</span>
|
||
</label>
|
||
</div>
|
||
<div style="display: flex; gap: 10px; margin-top: 20px;">
|
||
<button class="btn btn-primary" style="background: linear-gradient(135deg, #f44336 0%, #d32f2f 100%);"
|
||
onclick="confirmDeleteConnection()">Delete Connection</button>
|
||
<button class="btn btn-secondary" onclick="closeDeleteConnectionModal()">Cancel</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Add Interactable Modal -->
|
||
<div class="add-spawn-modal" id="addInteractableModal">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h3>Add Interactable</h3>
|
||
<p style="opacity: 0.7; margin-top: 10px;">Select an interactable template</p>
|
||
</div>
|
||
<div class="property-group">
|
||
<input type="text" id="interactableSearch" placeholder="🔍 Search interactables..."
|
||
oninput="filterInteractableSelectList()" style="margin-bottom: 10px;">
|
||
</div>
|
||
<div class="npc-select-list" id="interactableSelectList">
|
||
<!-- Populated dynamically -->
|
||
</div>
|
||
<button class="btn btn-secondary" onclick="closeAddInteractableModal()">Cancel</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Edit Interactable Instance Modal -->
|
||
<div class="add-spawn-modal" id="editInteractableInstanceModal">
|
||
<div class="modal-content" style="max-width: 700px; max-height: 80vh; overflow-y: auto;">
|
||
<div class="modal-header">
|
||
<h3>Configure Interactable</h3>
|
||
</div>
|
||
<div id="interactableInstanceEditor">
|
||
<!-- Populated dynamically -->
|
||
</div>
|
||
<div style="display: flex; gap: 10px; margin-top: 20px;">
|
||
<button class="btn btn-primary" onclick="saveInteractableInstance()">Save</button>
|
||
<button class="btn btn-secondary" onclick="closeEditInteractableInstanceModal()">Cancel</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="editor_enhanced.js"></script>
|
||
</body>
|
||
|
||
</html> |