combat testing and polishing in the dev console, many bug fixes
This commit is contained in:
258
public_web/templates/game/combat.html
Normal file
258
public_web/templates/game/combat.html
Normal file
@@ -0,0 +1,258 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Combat - Code of Conquest{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/combat.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/inventory.css') }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="combat-page">
|
||||
<div class="combat-container">
|
||||
{# ===== COMBAT HEADER ===== #}
|
||||
<header class="combat-header">
|
||||
<h1 class="combat-title">
|
||||
<span class="combat-title-icon">⚔</span>
|
||||
Combat Encounter
|
||||
</h1>
|
||||
<div class="combat-round">
|
||||
<span class="round-counter">Round <strong>{{ encounter.round_number }}</strong></span>
|
||||
{% if is_player_turn %}
|
||||
<span class="turn-indicator turn-indicator--player">Your Turn</span>
|
||||
{% else %}
|
||||
<span class="turn-indicator turn-indicator--enemy">Enemy Turn</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{# ===== LEFT COLUMN: COMBATANTS ===== #}
|
||||
<aside class="combatant-panel">
|
||||
{# Player Section #}
|
||||
<div class="combatant-section">
|
||||
<h2 class="combatant-section-title">Your Party</h2>
|
||||
{% for combatant in encounter.combatants if combatant.is_player %}
|
||||
<div class="combatant-card combatant-card--player {% if combatant.combatant_id == current_turn_id %}combatant-card--active{% endif %} {% if combatant.current_hp <= 0 %}combatant-card--defeated{% endif %}">
|
||||
<div class="combatant-header">
|
||||
<span class="combatant-name">{{ combatant.name }}</span>
|
||||
<span class="combatant-level">Lv.{{ combatant.level|default(1) }}</span>
|
||||
</div>
|
||||
<div class="combatant-resources">
|
||||
{% set hp_percent = ((combatant.current_hp / combatant.max_hp) * 100)|round|int %}
|
||||
<div class="resource-bar resource-bar--hp {% if hp_percent < 25 %}low{% endif %}">
|
||||
<div class="resource-bar-label">
|
||||
<span class="resource-bar-name">HP</span>
|
||||
<span class="resource-bar-value">{{ combatant.current_hp }} / {{ combatant.max_hp }}</span>
|
||||
</div>
|
||||
<div class="resource-bar-track">
|
||||
<div class="resource-bar-fill" style="width: {{ hp_percent }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
{% set mp_percent = ((combatant.current_mp / combatant.max_mp) * 100)|round|int if combatant.max_mp > 0 else 0 %}
|
||||
<div class="resource-bar resource-bar--mp">
|
||||
<div class="resource-bar-label">
|
||||
<span class="resource-bar-name">MP</span>
|
||||
<span class="resource-bar-value">{{ combatant.current_mp }} / {{ combatant.max_mp }}</span>
|
||||
</div>
|
||||
<div class="resource-bar-track">
|
||||
<div class="resource-bar-fill" style="width: {{ mp_percent }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{# Enemies Section #}
|
||||
<div class="combatant-section">
|
||||
<h2 class="combatant-section-title">Enemies</h2>
|
||||
{% for combatant in encounter.combatants if not combatant.is_player %}
|
||||
<div class="combatant-card combatant-card--enemy {% if combatant.combatant_id == current_turn_id %}combatant-card--active{% endif %} {% if combatant.current_hp <= 0 %}combatant-card--defeated{% endif %}">
|
||||
<div class="combatant-header">
|
||||
<span class="combatant-name">{{ combatant.name }}</span>
|
||||
<span class="combatant-level">{% if combatant.current_hp <= 0 %}Defeated{% endif %}</span>
|
||||
</div>
|
||||
<div class="combatant-resources">
|
||||
{% set hp_percent = ((combatant.current_hp / combatant.max_hp) * 100)|round|int %}
|
||||
<div class="resource-bar resource-bar--hp {% if hp_percent < 25 %}low{% endif %}">
|
||||
<div class="resource-bar-label">
|
||||
<span class="resource-bar-name">HP</span>
|
||||
<span class="resource-bar-value">{{ combatant.current_hp }} / {{ combatant.max_hp }}</span>
|
||||
</div>
|
||||
<div class="resource-bar-track">
|
||||
<div class="resource-bar-fill" style="width: {{ hp_percent }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{# ===== CENTER COLUMN: COMBAT LOG + ACTIONS ===== #}
|
||||
<main class="combat-main">
|
||||
{# Combat Log #}
|
||||
<div id="combat-log" class="combat-log" role="log" aria-live="polite" aria-label="Combat log">
|
||||
{% include "game/partials/combat_log.html" %}
|
||||
</div>
|
||||
|
||||
{# Combat Actions #}
|
||||
<div id="combat-actions" class="combat-actions">
|
||||
{% include "game/partials/combat_actions.html" %}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{# ===== RIGHT COLUMN: TURN ORDER + EFFECTS ===== #}
|
||||
<aside class="combat-sidebar">
|
||||
{# Turn Order #}
|
||||
<div class="turn-order">
|
||||
<h2 class="turn-order__title">Turn Order</h2>
|
||||
<div class="turn-order__list">
|
||||
{% for combatant_id in encounter.turn_order %}
|
||||
{% set combatant = encounter.combatants|selectattr('combatant_id', 'equalto', combatant_id)|first %}
|
||||
{% if combatant %}
|
||||
<div class="turn-order__item {% if combatant.is_player %}turn-order__item--player{% else %}turn-order__item--enemy{% endif %} {% if combatant_id == current_turn_id %}turn-order__item--active{% endif %} {% if combatant.current_hp <= 0 %}turn-order__item--defeated{% endif %}">
|
||||
<span class="turn-order__position">{{ loop.index }}</span>
|
||||
<span class="turn-order__name">{{ combatant.name }}</span>
|
||||
{% if combatant_id == current_turn_id %}
|
||||
<span class="turn-order__check" title="Current turn">➤</span>
|
||||
{% elif combatant.current_hp <= 0 %}
|
||||
<span class="turn-order__check" title="Defeated">✗</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Active Effects #}
|
||||
<div class="effects-panel">
|
||||
<h2 class="effects-panel__title">Active Effects</h2>
|
||||
{% if player_combatant and player_combatant.active_effects %}
|
||||
<div class="effects-list">
|
||||
{% for effect in player_combatant.active_effects %}
|
||||
<div class="effect-item effect-item--{{ effect.effect_type|default('buff') }}">
|
||||
<span class="effect-icon">
|
||||
{% if effect.effect_type == 'shield' %}🛡
|
||||
{% elif effect.effect_type == 'buff' %}⬆
|
||||
{% elif effect.effect_type == 'debuff' %}⬇
|
||||
{% elif effect.effect_type == 'dot' %}🔥
|
||||
{% elif effect.effect_type == 'hot' %}❤
|
||||
{% else %}★
|
||||
{% endif %}
|
||||
</span>
|
||||
<span class="effect-name">{{ effect.name }}</span>
|
||||
<span class="effect-duration">{{ effect.remaining_duration }} {% if effect.remaining_duration == 1 %}turn{% else %}turns{% endif %}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="effects-empty">No active effects</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
{# Modal Container for Ability selection #}
|
||||
<div id="modal-container"></div>
|
||||
|
||||
{# Combat Items Sheet Container #}
|
||||
<div id="combat-sheet-container"></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// Auto-scroll combat log to bottom on new entries
|
||||
function scrollCombatLog() {
|
||||
const log = document.getElementById('combat-log');
|
||||
if (log) {
|
||||
log.scrollTop = log.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
// Scroll on page load
|
||||
document.addEventListener('DOMContentLoaded', scrollCombatLog);
|
||||
|
||||
// Scroll after HTMX swaps
|
||||
document.body.addEventListener('htmx:afterSwap', function(event) {
|
||||
if (event.detail.target.id === 'combat-log' ||
|
||||
event.detail.target.closest('#combat-log')) {
|
||||
scrollCombatLog();
|
||||
}
|
||||
});
|
||||
|
||||
// Close modal function
|
||||
function closeModal() {
|
||||
const container = document.getElementById('modal-container');
|
||||
if (container) {
|
||||
container.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Close combat items sheet
|
||||
function closeCombatSheet() {
|
||||
const container = document.getElementById('combat-sheet-container');
|
||||
if (container) {
|
||||
container.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Close modal/sheet on Escape key
|
||||
document.addEventListener('keydown', function(event) {
|
||||
if (event.key === 'Escape') {
|
||||
closeModal();
|
||||
closeCombatSheet();
|
||||
}
|
||||
});
|
||||
|
||||
// Guard against duplicate enemy turn requests
|
||||
let enemyTurnPending = false;
|
||||
let enemyTurnTimeout = null;
|
||||
|
||||
function triggerEnemyTurn() {
|
||||
// Prevent duplicate requests
|
||||
if (enemyTurnPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear any pending timeout
|
||||
if (enemyTurnTimeout) {
|
||||
clearTimeout(enemyTurnTimeout);
|
||||
}
|
||||
|
||||
enemyTurnPending = true;
|
||||
enemyTurnTimeout = setTimeout(function() {
|
||||
htmx.ajax('POST', '{{ url_for("combat.combat_enemy_turn", session_id=session_id) }}', {
|
||||
target: '#combat-log',
|
||||
swap: 'beforeend'
|
||||
}).then(function() {
|
||||
enemyTurnPending = false;
|
||||
}).catch(function() {
|
||||
enemyTurnPending = false;
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// Handle enemy turn polling
|
||||
document.body.addEventListener('htmx:afterRequest', function(event) {
|
||||
// Check if we need to trigger enemy turn
|
||||
const response = event.detail.xhr;
|
||||
if (response && response.getResponseHeader('HX-Trigger')) {
|
||||
const triggers = response.getResponseHeader('HX-Trigger');
|
||||
if (triggers && triggers.includes('enemyTurn')) {
|
||||
triggerEnemyTurn();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Handle combat end redirect
|
||||
document.body.addEventListener('htmx:beforeSwap', function(event) {
|
||||
// If the response indicates combat ended, handle accordingly
|
||||
const response = event.detail.xhr;
|
||||
if (response && response.getResponseHeader('X-Combat-Ended')) {
|
||||
// Let the full page swap happen for victory/defeat screen
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user