From 2686764cb3f3d86403b5944220e2cbba42d32c76 Mon Sep 17 00:00:00 2001 From: Andrew Pamment Date: Mon, 21 Apr 2025 12:10:51 +1000 Subject: [PATCH] spells pt1 --- Actor.cpp | 6 ++++ Actor.h | 2 +- CMakeLists.txt | 2 +- Engine.cpp | 76 +++++++++++++++++++++++++++++++++++++++++++++-- Engine.h | 4 ++- Fireball.cpp | 30 +++++++++++++++++++ Fireball.h | 10 +++++++ Lightningbolt.cpp | 24 +++++++++++++++ Lightningbolt.h | 12 ++++++++ Map.cpp | 34 +++++++++++++++++---- 10 files changed, 190 insertions(+), 10 deletions(-) create mode 100644 Fireball.cpp create mode 100644 Fireball.h create mode 100644 Lightningbolt.cpp create mode 100644 Lightningbolt.h diff --git a/Actor.cpp b/Actor.cpp index 0372371..fe63ce0 100644 --- a/Actor.cpp +++ b/Actor.cpp @@ -31,4 +31,10 @@ Actor::~Actor() { if (ai) delete ai; if (pickable) delete pickable; if (container) delete container; +} + +float Actor::getDistance(int cx, int cy) const { + int dx = x - cx; + int dy = y - cy; + return sqrtf(dx * dx + dy * dy); } \ No newline at end of file diff --git a/Actor.h b/Actor.h index 93bf653..e1e919a 100644 --- a/Actor.h +++ b/Actor.h @@ -21,7 +21,7 @@ public: Ai* ai; Pickable* pickable; // something that can be picked and used Container* container; // something that can contain actors - + float getDistance(int cx, int cy) const; Actor(int x, int y, std::string_view ch, std::string name, const TCOD_ColorRGB& col); ~Actor(); void render(TCOD_Console& cons) const; diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ff591b..57c4b2c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,7 @@ project ("ADG5") find_package(libtcod CONFIG REQUIRED) # Add source to this project's executable. -add_executable (ADG5 "ADG5.cpp" "ADG5.h" "Actor.cpp" "Actor.h" "Map.h" "Map.cpp" "Engine.h" "Engine.cpp" "Destructible.h" "Destructible.cpp" "Attacker.h" "Attacker.cpp" "Ai.h" "Ai.cpp" "Gui.h" "Gui.cpp" "Container.h" "Container.cpp" "Pickable.h" "Pickable.cpp" "Healer.h" "Healer.cpp") +add_executable (ADG5 "ADG5.cpp" "ADG5.h" "Actor.cpp" "Actor.h" "Map.h" "Map.cpp" "Engine.h" "Engine.cpp" "Destructible.h" "Destructible.cpp" "Attacker.h" "Attacker.cpp" "Ai.h" "Ai.cpp" "Gui.h" "Gui.cpp" "Container.h" "Container.cpp" "Pickable.h" "Pickable.cpp" "Healer.h" "Healer.cpp" "Lightningbolt.h" "Lightningbolt.cpp" "Fireball.h" "Fireball.cpp") target_link_libraries(ADG5 PRIVATE diff --git a/Engine.cpp b/Engine.cpp index 8ae5ddd..2cc4585 100644 --- a/Engine.cpp +++ b/Engine.cpp @@ -22,6 +22,8 @@ Engine::Engine(int screenWidth, int screenHeight, tcod::Context *context, tcod:: gameStatus = STARTUP; mouse.cx = 0; mouse.cy = 0; + mouse.rbutton_pressed = false; + mouse.lbutton_pressed = false; } void Engine::init() { @@ -62,7 +64,7 @@ bool Engine::update() { return true; } -void Engine::render() { +void Engine::render(bool present) { console->clear(); // draw the map map->render(*console); @@ -77,10 +79,80 @@ void Engine::render() { } player->render(*console); gui->render(); - context->present(*console); + if (present) + context->present(*console); } void Engine::sendToBack(Actor* actor) { actors.remove(actor); actors.insertBefore(actor, 0); +} + +Actor* Engine::getClosestMonster(int x, int y, float range) const { + Actor* closest = NULL; + float bestDistance = 1E6f; + for (Actor** iterator = actors.begin(); + iterator != actors.end(); iterator++) { + Actor* actor = *iterator; + if (actor != player && actor->destructible + && !actor->destructible->isDead()) { + float distance = actor->getDistance(x, y); + if (distance < bestDistance && (distance <= range || range == 0.0f)) { + bestDistance = distance; + closest = actor; + } + } + } + return closest; +} + +bool Engine::pickATile(int* x, int* y, float maxRange) { + while (context->get_sdl_window() != nullptr) { + render(false); + + // highlight the possible range + for (int cx = 0; cx < map->width; cx++) { + for (int cy = 0; cy < map->height; cy++) { + if (map->isInFov(cx, cy) + && (maxRange == 0 || player->getDistance(cx, cy) <= maxRange)) { + TCOD_ColorRGBA col = console->at(cx, cy).bg; + + col.r = col.r * 1.2f; + col.g = col.g * 1.2f; + col.b = col.b * 1.2f; + + console->at(cx, cy).bg = col; + } + } + } + SDL_Event event; + while (SDL_PollEvent(&event)) { + engine->context->convert_event_coordinates(event); + switch (event.type) { + case SDL_EVENT_MOUSE_MOTION: + engine->mouse.cx = event.motion.x; + engine->mouse.cy = event.motion.y; + break; + case SDL_EVENT_MOUSE_BUTTON_DOWN: + engine->mouse.lbutton_pressed = event.button.button == SDL_BUTTON_LEFT; + engine->mouse.rbutton_pressed = event.button.button == SDL_BUTTON_RIGHT; + break; + } + } + + if (map->isInFov(mouse.cx, mouse.cy) + && (maxRange == 0 || player->getDistance(mouse.cx, mouse.cy) <= maxRange)) { + console->at(mouse.cx, mouse.cy).bg = TCOD_ColorRGBA(100, 100, 100, 255); + if (mouse.lbutton_pressed) { + *x = mouse.cx; + *y = mouse.cy; + return true; + } + } + if (mouse.rbutton_pressed) { + return false; + } + context->present(*console); + } + return false; } \ No newline at end of file diff --git a/Engine.h b/Engine.h index 83005fb..5cceedd 100644 --- a/Engine.h +++ b/Engine.h @@ -30,8 +30,10 @@ public: void init(); ~Engine(); bool update(); - void render(); + void render(bool present = true); void sendToBack(Actor* actor); + Actor* getClosestMonster(int x, int y, float range) const; + bool pickATile(int* x, int* y, float maxRange = 0.0f); private: bool computeFov; }; diff --git a/Fireball.cpp b/Fireball.cpp new file mode 100644 index 0000000..94e665d --- /dev/null +++ b/Fireball.cpp @@ -0,0 +1,30 @@ +#include "libtcod.hpp" +#include "Fireball.h" +#include "Engine.h" +#include "Gui.h" +#include "Destructible.h" +#include "Actor.h" + +Fireball::Fireball(float range, float damage) + : LightningBolt(range, damage) { +} + +bool Fireball::use(Actor* owner, Actor* wearer) { + engine->gui->message(TCOD_ColorRGB(0, 255, 255), "Left-click a target tile for the fireball,\nor right-click to cancel."); + int x, y; + if (!engine->pickATile(&x, &y)) { + return false; + } + engine->gui->message(TCOD_ColorRGB(255, 255, 100), "The fireball explodes, burning everything within %g tiles!", range); + for (Actor** iterator = engine->actors.begin(); + iterator != engine->actors.end(); iterator++) { + Actor* actor = *iterator; + if (actor->destructible && !actor->destructible->isDead() + && actor->getDistance(x, y) <= range) { + engine->gui->message(TCOD_ColorRGB(255, 255, 100), "The %s gets burned for %g hit points.", + actor->name.c_str(), damage); + actor->destructible->takeDamage(actor, damage); + } + } + return Pickable::use(owner, wearer); +} \ No newline at end of file diff --git a/Fireball.h b/Fireball.h new file mode 100644 index 0000000..4ee8b83 --- /dev/null +++ b/Fireball.h @@ -0,0 +1,10 @@ +#pragma once + +#include "Lightningbolt.h" + + +class Fireball : public LightningBolt { +public: + Fireball(float range, float damage); + bool use(Actor* owner, Actor* wearer); +}; \ No newline at end of file diff --git a/Lightningbolt.cpp b/Lightningbolt.cpp new file mode 100644 index 0000000..99299e1 --- /dev/null +++ b/Lightningbolt.cpp @@ -0,0 +1,24 @@ +#include "Lightningbolt.h" +#include "Engine.h" +#include "Actor.h" +#include "Gui.h" +#include "Destructible.h" + +LightningBolt::LightningBolt(float range, float damage) + : range(range), damage(damage) { +} + +bool LightningBolt::use(Actor* owner, Actor* wearer) { + Actor* closestMonster = engine->getClosestMonster(wearer->x, wearer->y, range); + if (!closestMonster) { + engine->gui->message(TCOD_ColorRGB(200,200,200), "No enemy is close enough to strike."); + return false; + } + // hit closest monster for hit points + engine->gui->message(TCOD_ColorRGB(100, 255, 100), + "A lighting bolt strikes the %s with a loud crack of thunder!\n" + "The damage is %g hit points.", + closestMonster->name.c_str(), damage); + closestMonster->destructible->takeDamage(closestMonster, damage); + return Pickable::use(owner, wearer); +} \ No newline at end of file diff --git a/Lightningbolt.h b/Lightningbolt.h new file mode 100644 index 0000000..d4b617e --- /dev/null +++ b/Lightningbolt.h @@ -0,0 +1,12 @@ +#pragma once + +#include "Pickable.h" + +class Actor; + +class LightningBolt : public Pickable { +public: + float range, damage; + LightningBolt(float range, float damage); + bool use(Actor* owner, Actor* wearer); +}; \ No newline at end of file diff --git a/Map.cpp b/Map.cpp index ddae346..810e7dc 100644 --- a/Map.cpp +++ b/Map.cpp @@ -6,6 +6,8 @@ #include "Attacker.h" #include "Ai.h" #include "Healer.h" +#include "Lightningbolt.h" +#include "Fireball.h" static const int ROOM_MAX_SIZE = 12; static const int ROOM_MIN_SIZE = 6; @@ -190,9 +192,31 @@ void Map::addMonster(int x, int y) { } void Map::addItem(int x, int y) { - Actor* healthPotion = new Actor(x, y, "!", "health potion", - TCOD_ColorRGB(128, 0, 128)); - healthPotion->blocks = false; - healthPotion->pickable = new Healer(4); - engine->actors.push(healthPotion); + TCODRandom* rng = TCODRandom::getInstance(); + int dice = rng->getInt(0, 100); + if (dice < 70) { + // create a health potion + Actor* healthPotion = new Actor(x, y, "!", "health potion", + TCOD_ColorRGB(128, 0, 128)); + healthPotion->blocks = false; + healthPotion->pickable = new Healer(4); + engine->actors.push(healthPotion); + } + else if (dice < 70 + 10) { + // create a scroll of lightning bolt + Actor* scrollOfLightningBolt = new Actor(x, y, "#", "scroll of lightning bolt", + TCOD_ColorRGB(255, 100, 100)); + scrollOfLightningBolt->blocks = false; + scrollOfLightningBolt->pickable = new LightningBolt(5, 20); + engine->actors.push(scrollOfLightningBolt); + } + + else if (dice < 70 + 10 + 10) { + // create a scroll of fireball + Actor* scrollOfFireball = new Actor(x, y, "#", "scroll of fireball", + TCOD_ColorRGB(255, 100, 100)); + scrollOfFireball->blocks = false; + scrollOfFireball->pickable = new Fireball(3, 12); + engine->actors.push(scrollOfFireball); + } } \ No newline at end of file