diff --git a/deploy_web.sh b/deploy_web.sh new file mode 100755 index 0000000..b9fa578 --- /dev/null +++ b/deploy_web.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -euo pipefail + +SERVER_USER="richard" +SERVER_HOST="192.168.1.168" +SERVER_PATH="/opt/bulloseum/data" + +BUILD_DIR="build/web" + +mkdir -p "$BUILD_DIR" +godot --headless --export-release "Web" "$BUILD_DIR/index.html" + +gzip -kf "$BUILD_DIR/index.wasm" + +scp "$BUILD_DIR/index.html" \ + "$BUILD_DIR/index.js" \ + "$BUILD_DIR/index.wasm.gz" \ + "$BUILD_DIR/index.pck" \ + "$BUILD_DIR/index.audio.worklet.js" \ + "$BUILD_DIR/index.audio.position.worklet.js" \ + "$BUILD_DIR/index.icon.png" \ + "$BUILD_DIR/index.apple-touch-icon.png" \ + "$BUILD_DIR/index.png" \ + "$SERVER_USER@$SERVER_HOST:$SERVER_PATH/" + +echo "deployed to $SERVER_HOST:$SERVER_PATH" diff --git a/hud.gd b/hud.gd new file mode 100644 index 0000000..af29d03 --- /dev/null +++ b/hud.gd @@ -0,0 +1,360 @@ +extends CanvasLayer + +const _GOLD := Color(1.00, 0.78, 0.22) +const _CREAM := Color(0.88, 0.83, 0.72) +const _MUTED := Color(0.65, 0.60, 0.48) +const _BG := Color(0.06, 0.04, 0.02, 0.78) +const _BADGE := Color(0.16, 0.10, 0.03, 0.90) +const _BORDER := Color(0.65, 0.48, 0.15, 0.55) + +const ROUND_DURATION := 60.0 + +var _controls_visible: bool = true +var _timer_remaining: float = ROUND_DURATION +var _score: int = 0 +var _round_active: bool = true + +var _controls_panel: PanelContainer +var _timer_label: Label +var _score_label: Label +var _toggle_btn: Button +var _game_over_panel: Control +var _final_score_label: Label +var _spawner: Node + + +func _ready() -> void: + layer = 11 + process_mode = Node.PROCESS_MODE_ALWAYS + _build() + _find_spawner.call_deferred() + + +func _find_spawner() -> void: + var spawners := get_tree().get_nodes_in_group(&"matador_spawn") + if not spawners.is_empty(): + _spawner = spawners[0] + _spawner.matador_killed.connect(_on_matador_killed) + + +func _process(delta: float) -> void: + if not _round_active: + return + _timer_remaining = maxf(0.0, _timer_remaining - delta) + _update_timer_display() + if _timer_remaining <= 0.0: + _end_round() + + +func _unhandled_input(event: InputEvent) -> void: + if event is InputEventKey and event.pressed and not event.echo: + if event.physical_keycode == KEY_H: + _toggle_controls() + + +func _toggle_controls() -> void: + _controls_visible = not _controls_visible + _controls_panel.visible = _controls_visible + _toggle_btn.text = "Hide controls" if _controls_visible else "Show controls" + + +func _on_matador_killed() -> void: + if _round_active: + _score += 1 + _update_score_display() + + +func _end_round() -> void: + _round_active = false + _final_score_label.text = "You killed %d matadors!" % _score + _game_over_panel.visible = true + get_tree().paused = true + + +func _on_continue_pressed() -> void: + _on_reset_pressed() + + +func _on_reset_pressed() -> void: + if get_tree().paused: + get_tree().paused = false + _game_over_panel.visible = false + _timer_remaining = ROUND_DURATION + _score = 0 + _round_active = true + _update_timer_display() + _update_score_display() + if _spawner: + _spawner.reset_spawn() + + +func _update_timer_display() -> void: + var s := int(_timer_remaining) + _timer_label.text = "%d:%02d" % [s / 60, s % 60] + + +func _update_score_display() -> void: + _score_label.text = "Kills: %d" % _score + + +func _build() -> void: + _build_controls_panel() + _build_score_panel() + _build_game_over_overlay() + + +func _build_controls_panel() -> void: + _controls_panel = PanelContainer.new() + + var bg := StyleBoxFlat.new() + bg.bg_color = _BG + bg.corner_radius_top_left = 7 + bg.corner_radius_top_right = 7 + bg.corner_radius_bottom_left = 7 + bg.corner_radius_bottom_right = 7 + bg.border_width_left = 1 + bg.border_width_right = 1 + bg.border_width_top = 1 + bg.border_width_bottom = 1 + bg.border_color = _BORDER + bg.content_margin_left = 14.0 + bg.content_margin_right = 14.0 + bg.content_margin_top = 10.0 + bg.content_margin_bottom = 10.0 + _controls_panel.add_theme_stylebox_override("panel", bg) + + _controls_panel.anchor_left = 0.0 + _controls_panel.anchor_top = 1.0 + _controls_panel.anchor_right = 0.0 + _controls_panel.anchor_bottom = 1.0 + _controls_panel.grow_vertical = Control.GROW_DIRECTION_BEGIN + _controls_panel.offset_left = 20.0 + _controls_panel.offset_bottom = -20.0 + add_child(_controls_panel) + + var vbox := VBoxContainer.new() + vbox.add_theme_constant_override("separation", 5) + _controls_panel.add_child(vbox) + + _row(vbox, "WASD / Arrows", "Move (not intended)", _MUTED) + _row(vbox, "LMB (hold)", "Right horn", _CREAM) + _row(vbox, "RMB (hold)", "Left horn", _CREAM) + _row(vbox, "LMB + RMB", "Bull charge", _CREAM) + _row(vbox, "Scroll", "Zoom", _CREAM) + _row(vbox, "R", "Respawn matadors (cooldown)", _MUTED) + _row(vbox, "H", "Toggle controls", _MUTED) + + +func _build_score_panel() -> void: + var panel := PanelContainer.new() + + var bg := StyleBoxFlat.new() + bg.bg_color = _BG + bg.corner_radius_top_left = 7 + bg.corner_radius_top_right = 7 + bg.corner_radius_bottom_left = 7 + bg.corner_radius_bottom_right = 7 + bg.border_width_left = 1 + bg.border_width_right = 1 + bg.border_width_top = 1 + bg.border_width_bottom = 1 + bg.border_color = _BORDER + bg.content_margin_left = 16.0 + bg.content_margin_right = 16.0 + bg.content_margin_top = 12.0 + bg.content_margin_bottom = 12.0 + panel.add_theme_stylebox_override("panel", bg) + + panel.anchor_left = 1.0 + panel.anchor_top = 0.0 + panel.anchor_right = 1.0 + panel.anchor_bottom = 0.0 + panel.grow_horizontal = Control.GROW_DIRECTION_BEGIN + panel.grow_vertical = Control.GROW_DIRECTION_END + panel.offset_right = -20.0 + panel.offset_top = 20.0 + add_child(panel) + + var vbox := VBoxContainer.new() + vbox.add_theme_constant_override("separation", 8) + panel.add_child(vbox) + + _timer_label = Label.new() + _timer_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + _timer_label.add_theme_color_override("font_color", _GOLD) + _timer_label.add_theme_font_size_override("font_size", 32) + vbox.add_child(_timer_label) + + _score_label = Label.new() + _score_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + _score_label.add_theme_color_override("font_color", _CREAM) + _score_label.add_theme_font_size_override("font_size", 16) + vbox.add_child(_score_label) + + var sep := HSeparator.new() + var sep_style := StyleBoxFlat.new() + sep_style.bg_color = Color(0.65, 0.48, 0.15, 0.30) + sep_style.content_margin_top = 2.0 + sep_style.content_margin_bottom = 2.0 + sep.add_theme_stylebox_override("separator", sep_style) + vbox.add_child(sep) + + var hbox := HBoxContainer.new() + hbox.add_theme_constant_override("separation", 8) + vbox.add_child(hbox) + + _toggle_btn = _make_button("Hide controls") + _toggle_btn.pressed.connect(_toggle_controls) + hbox.add_child(_toggle_btn) + + var reset_btn := _make_button("Reset") + reset_btn.pressed.connect(_on_reset_pressed) + hbox.add_child(reset_btn) + + _update_timer_display() + _update_score_display() + + +func _build_game_over_overlay() -> void: + _game_over_panel = Control.new() + _game_over_panel.set_anchors_preset(Control.PRESET_FULL_RECT) + _game_over_panel.visible = false + _game_over_panel.process_mode = Node.PROCESS_MODE_ALWAYS + add_child(_game_over_panel) + + var backdrop := ColorRect.new() + backdrop.set_anchors_preset(Control.PRESET_FULL_RECT) + backdrop.color = Color(0.0, 0.0, 0.0, 0.65) + _game_over_panel.add_child(backdrop) + + var card := PanelContainer.new() + var card_bg := StyleBoxFlat.new() + card_bg.bg_color = Color(0.08, 0.05, 0.02, 0.95) + card_bg.corner_radius_top_left = 12 + card_bg.corner_radius_top_right = 12 + card_bg.corner_radius_bottom_left = 12 + card_bg.corner_radius_bottom_right = 12 + card_bg.border_width_left = 2 + card_bg.border_width_right = 2 + card_bg.border_width_top = 2 + card_bg.border_width_bottom = 2 + card_bg.border_color = _GOLD + card_bg.content_margin_left = 48.0 + card_bg.content_margin_right = 48.0 + card_bg.content_margin_top = 36.0 + card_bg.content_margin_bottom = 36.0 + card.add_theme_stylebox_override("panel", card_bg) + card.anchor_left = 0.5 + card.anchor_top = 0.5 + card.anchor_right = 0.5 + card.anchor_bottom = 0.5 + card.grow_horizontal = Control.GROW_DIRECTION_BOTH + card.grow_vertical = Control.GROW_DIRECTION_BOTH + _game_over_panel.add_child(card) + + var vbox := VBoxContainer.new() + vbox.add_theme_constant_override("separation", 18) + vbox.alignment = BoxContainer.ALIGNMENT_CENTER + card.add_child(vbox) + + var title := Label.new() + title.text = "TIME'S UP!" + title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + title.add_theme_color_override("font_color", _GOLD) + title.add_theme_font_size_override("font_size", 48) + vbox.add_child(title) + + _final_score_label = Label.new() + _final_score_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + _final_score_label.add_theme_color_override("font_color", _CREAM) + _final_score_label.add_theme_font_size_override("font_size", 24) + vbox.add_child(_final_score_label) + + var continue_btn := _make_button("Continue") + continue_btn.custom_minimum_size = Vector2(160.0, 44.0) + continue_btn.process_mode = Node.PROCESS_MODE_ALWAYS + continue_btn.pressed.connect(_on_continue_pressed) + vbox.add_child(continue_btn) + + +func _make_button(label_text: String) -> Button: + var btn := Button.new() + btn.text = label_text + btn.process_mode = Node.PROCESS_MODE_ALWAYS + + var normal := StyleBoxFlat.new() + normal.bg_color = Color(0.18, 0.12, 0.04, 0.90) + normal.corner_radius_top_left = 5 + normal.corner_radius_top_right = 5 + normal.corner_radius_bottom_left = 5 + normal.corner_radius_bottom_right = 5 + normal.border_width_left = 1 + normal.border_width_right = 1 + normal.border_width_top = 1 + normal.border_width_bottom = 1 + normal.border_color = _BORDER + normal.content_margin_left = 12.0 + normal.content_margin_right = 12.0 + normal.content_margin_top = 6.0 + normal.content_margin_bottom = 6.0 + + var hover := StyleBoxFlat.new() + hover.bg_color = Color(0.30, 0.20, 0.06, 0.95) + hover.corner_radius_top_left = 5 + hover.corner_radius_top_right = 5 + hover.corner_radius_bottom_left = 5 + hover.corner_radius_bottom_right = 5 + hover.border_width_left = 1 + hover.border_width_right = 1 + hover.border_width_top = 1 + hover.border_width_bottom = 1 + hover.border_color = _GOLD + hover.content_margin_left = 12.0 + hover.content_margin_right = 12.0 + hover.content_margin_top = 6.0 + hover.content_margin_bottom = 6.0 + + btn.add_theme_stylebox_override("normal", normal) + btn.add_theme_stylebox_override("hover", hover) + btn.add_theme_stylebox_override("pressed", hover) + btn.add_theme_color_override("font_color", _GOLD) + btn.add_theme_color_override("font_hover_color", _GOLD) + btn.add_theme_font_size_override("font_size", 12) + + return btn + + +func _row(parent: VBoxContainer, key: String, desc: String, desc_color: Color) -> void: + var row := HBoxContainer.new() + row.add_theme_constant_override("separation", 10) + parent.add_child(row) + + var badge := PanelContainer.new() + var bs := StyleBoxFlat.new() + bs.bg_color = _BADGE + bs.corner_radius_top_left = 3 + bs.corner_radius_top_right = 3 + bs.corner_radius_bottom_left = 3 + bs.corner_radius_bottom_right = 3 + bs.content_margin_left = 6.0 + bs.content_margin_right = 6.0 + bs.content_margin_top = 2.0 + bs.content_margin_bottom = 2.0 + badge.add_theme_stylebox_override("panel", bs) + badge.custom_minimum_size = Vector2(100.0, 0.0) + row.add_child(badge) + + var key_lbl := Label.new() + key_lbl.text = key + key_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + key_lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + key_lbl.add_theme_color_override("font_color", _GOLD) + key_lbl.add_theme_font_size_override("font_size", 12) + badge.add_child(key_lbl) + + var desc_lbl := Label.new() + desc_lbl.text = desc + desc_lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + desc_lbl.add_theme_color_override("font_color", desc_color) + desc_lbl.add_theme_font_size_override("font_size", 12) + row.add_child(desc_lbl) diff --git a/sounds/bull_huff.ogg b/sounds/bull_huff.ogg new file mode 100644 index 0000000..b5c56d2 Binary files /dev/null and b/sounds/bull_huff.ogg differ diff --git a/sounds/bull_huff.ogg.import b/sounds/bull_huff.ogg.import new file mode 100644 index 0000000..e77dbdc --- /dev/null +++ b/sounds/bull_huff.ogg.import @@ -0,0 +1,19 @@ +[remap] + +importer="oggvorbisstr" +type="AudioStreamOggVorbis" +uid="uid://dhcr48rm0wy3o" +path="res://.godot/imported/bull_huff.ogg-6df51b9bb7b90a2b004ecf393f681865.oggvorbisstr" + +[deps] + +source_file="res://sounds/bull_huff.ogg" +dest_files=["res://.godot/imported/bull_huff.ogg-6df51b9bb7b90a2b004ecf393f681865.oggvorbisstr"] + +[params] + +loop=false +loop_offset=0 +bpm=0 +beat_count=0 +bar_beats=4 diff --git a/sounds/crowd_ambient.ogg b/sounds/crowd_ambient.ogg new file mode 100644 index 0000000..b537158 Binary files /dev/null and b/sounds/crowd_ambient.ogg differ diff --git a/sounds/crowd_ambient.ogg.import b/sounds/crowd_ambient.ogg.import new file mode 100644 index 0000000..df3bd42 --- /dev/null +++ b/sounds/crowd_ambient.ogg.import @@ -0,0 +1,19 @@ +[remap] + +importer="oggvorbisstr" +type="AudioStreamOggVorbis" +uid="uid://don2b32xiq60j" +path="res://.godot/imported/crowd_ambient.ogg-27288d402877e4b55c8baec23d7ee8a3.oggvorbisstr" + +[deps] + +source_file="res://sounds/crowd_ambient.ogg" +dest_files=["res://.godot/imported/crowd_ambient.ogg-27288d402877e4b55c8baec23d7ee8a3.oggvorbisstr"] + +[params] + +loop=false +loop_offset=0 +bpm=0 +beat_count=0 +bar_beats=4 diff --git a/sounds/matador_death.ogg b/sounds/matador_death.ogg new file mode 100644 index 0000000..f7e36ae Binary files /dev/null and b/sounds/matador_death.ogg differ diff --git a/sounds/ole.ogg b/sounds/ole.ogg new file mode 100644 index 0000000..0edde48 Binary files /dev/null and b/sounds/ole.ogg differ diff --git a/sounds/ole.ogg.import b/sounds/ole.ogg.import new file mode 100644 index 0000000..693e4b4 --- /dev/null +++ b/sounds/ole.ogg.import @@ -0,0 +1,19 @@ +[remap] + +importer="oggvorbisstr" +type="AudioStreamOggVorbis" +uid="uid://3ocnywvipl1x" +path="res://.godot/imported/ole.ogg-354fe92955fd8cdee52db7c574f0b653.oggvorbisstr" + +[deps] + +source_file="res://sounds/ole.ogg" +dest_files=["res://.godot/imported/ole.ogg-354fe92955fd8cdee52db7c574f0b653.oggvorbisstr"] + +[params] + +loop=false +loop_offset=0 +bpm=0 +beat_count=0 +bar_beats=4