/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Copyright 2020 Google * */ #include "hadesch/hadesch.h" #include "hadesch/video.h" #include "hadesch/rooms/monster.h" namespace Hadesch { Projectile::Projectile(int id, int level, Monster monster, int startScale, Common::Point startPoint, int xmomentum) { _level = level; switch (monster) { case kCyclops: _flyAnim = "V7140BA0"; _interceptAnim = "V7130BD0"; _hitAnim = "V7140BD0"; break; case kTyphoon: _flyAnim = "V7140BB0"; _interceptAnim = "V7130BD1"; _hitAnim = "V7140BE0"; break; case kIllusion: _flyAnim = "V7140BC0"; _interceptAnim = "V7130BD2"; _hitAnim = "V7140BF0"; break; } _isMiss = g_vm->getRnd().getRandomNumberRng(0, getProjectileHitChance()) == 0; _isFlightFinished = false; _flightCounterMs = -1; _projectileId = id; _pending = 0; _flightStart = g_vm->getCurrentTime(); _startScale = startScale; _start = startPoint; makeFlightParams(xmomentum); } void Projectile::handleEvent(int ev) { switch (ev) { case 15053: g_vm->handleEvent(kHitReceived); // TODO: stop red _pending--; break; case 15054: _pending--; break; } } void Projectile::stop() { Common::SharedPtr room = g_vm->getVideoRoom(); room->stopAnim(LayerId(_flyAnim, _projectileId, "projectile")); room->stopAnim(LayerId(_hitAnim, _projectileId, "projectile")); room->stopAnim(LayerId(_interceptAnim, _projectileId, "projectile")); } void Projectile::makeFlightParams(int xmomentum) { Common::RandomSource &rnd = g_vm->getRnd(); _flightLengthMs = getProjectileFlightLength(_level) * 100; if (_isMiss) { switch (rnd.getRandomNumberRng(0, 2)) { case 0: _target = Common::Point( -50, rnd.getRandomNumberRngSigned(-50, 400)); break; case 1: _target = Common::Point( rnd.getRandomNumberRngSigned(-50, 650), -50); break; case 2: _target = Common::Point( 650, rnd.getRandomNumberRngSigned(-50, 400)); break; } } else { _target = Common::Point( rnd.getRandomNumberRng(100, 500), rnd.getRandomNumberRng(100, 300)); } switch (xmomentum) { case 1: _attractor1 = Common::Point( rnd.getRandomNumberRng(0, 600), rnd.getRandomNumberRng(0, 300)); break; case -1: _attractor1 = Common::Point( rnd.getRandomNumberRngSigned(-600, 0), rnd.getRandomNumberRng(0, 300)); break; case 0: _attractor1 = Common::Point( rnd.getRandomNumberRngSigned(-600, 600), rnd.getRandomNumberRngSigned(-600, 600)); break; } _attractor2 = Common::Point( rnd.getRandomNumberRngSigned(-600, 600), rnd.getRandomNumberRng(0, 600)); } FlightPosition Projectile::getFlightPosition(double t) { double t2 = t * t; double t3 = t2 * t; struct FlightPosition fp; // Pseudo-Bezier fp.centerPos = ((2 * t3 - 3 * t2 + 1.0) * _start + (t3 - 2 * t2 + t) * _attractor1 + (t3 - t2) * _attractor2 + (-2 * t3 + 3 * t2) * _target); fp.scale = _startScale + (120 - _startScale) * t; return fp; } Projectile::~Projectile() { Common::SharedPtr room = g_vm->getVideoRoom(); room->purgeAnim(LayerId(_flyAnim, _projectileId, "projectile")); room->purgeAnim(LayerId(_hitAnim, _projectileId, "projectile")); room->purgeAnim(LayerId(_interceptAnim, _projectileId, "projectile")); } int Projectile::getProjectileFlightLength(int level) { return 41 - _level; } int Projectile::getProjectileHitChance() { if (_level >= 26) return 6; if (_level >= 17) return 5; if (_level >= 12) return 4; if (_level >= 7) return 3; return 2; } Battleground::Battleground() { _level = 1; _projectileId = 0; _isInFight = false; } int Battleground::getNumOfProjectiles() { return (_level - 1) / 10 + 1; } void Battleground::launchProjectile(int startScale, Common::Point startPoint, int xmomentum) { ++_projectileId; Common::SharedPtr pj(new Projectile(_projectileId, _level, _monsterNum, startScale, startPoint, xmomentum)); _projectiles.push_back(pj); pj->tick(pj); } void Battleground::handleAbsoluteClick(Common::Point p) { for (auto &projectile : _projectiles) { projectile.operator->()->handleAbsoluteClick(projectile, p); } } void Battleground::tick() { if (!_isInFight) _projectiles.clear(); else for (Common::Array >::iterator it = _projectiles.begin(); it != _projectiles.end();) { if (it->operator->()->tick(*it)) { it++; } else { it = _projectiles.erase(it); } } } void Battleground::stopFight() { Common::SharedPtr room = g_vm->getVideoRoom(); _isInFight = false; room->stopAnim("v7040ba0"); room->stopAnim("V7100BJ0"); room->stopAnim("v7180ba0"); room->stopAnim("V7180BB0"); room->stopAnim("v7180be0"); room->stopAnim("v7180bh0"); room->stopAnim("v7180bh1"); room->stopAnim("v7180bi0"); room->stopAnim("v7180bk0"); room->stopAnim("v7180bl0"); room->stopAnim("v7180oa0"); room->stopAnim("v7210bx0"); stopProjectiles(); Typhoon::stopAnims(); Illusion::stopAnims(); for (unsigned i = 0; i < 6; i++) { room->stopAnim(Common::String::format("v7220bt%d", i)); room->stopAnim(Common::String::format("v7220bg%d", i)); } room->dumpLayers(); } void Battleground::stopProjectiles() { for (auto &projectile : _projectiles) projectile.operator->()->stop(); } class HandlerProjectile : public EventHandler { public: void operator()() override { _projectile->handleEvent(_event); } HandlerProjectile(Common::SharedPtr projectile, int event) { _projectile = projectile; _event = event; } private: Common::SharedPtr _projectile; int _event; }; bool Projectile::tick(Common::SharedPtr backRef) { Common::SharedPtr room = g_vm->getVideoRoom(); if (_isFlightFinished) return _pending > 0; LayerId flyLayer = LayerId(_flyAnim, _projectileId, "projectile"); _flightCounterMs = (g_vm->getCurrentTime() - _flightStart); if (_flightCounterMs < _flightLengthMs) { FlightPosition fp = getFlightPosition(_flightCounterMs / (double) _flightLengthMs); int scale = fp.scale; Common::Point cornerPos = fp.centerPos - (scale / 100.0) * Common::Point(186, 210); room->selectFrame(flyLayer, 400, (_flightCounterMs / 100) % 8, cornerPos); room->setScale(flyLayer, scale); } else { room->stopAnim(flyLayer); _isFlightFinished = true; if (_isMiss) { _pending = 0; } else { FlightPosition fp = getFlightPosition(_flightCounterMs / (double) _flightLengthMs); LayerId l = LayerId(_hitAnim, _projectileId, "projectile"); room->playAnimWithSFX(l, "v7130ea0", 400, PlayAnimParams::disappear(), Common::SharedPtr(new HandlerProjectile(backRef, 15053)), fp.centerPos - Common::Point(182, 205)); _pending = 1; // TODO: fade to red, in 100 ms, callback 15055 // TODO: shake camera for 1s } } return true; } void Projectile::handleAbsoluteClick(Common::SharedPtr backRef, Common::Point p) { Common::SharedPtr room = g_vm->getVideoRoom(); if (_isFlightFinished || _flightCounterMs >= _flightLengthMs) return; FlightPosition fp = getFlightPosition(_flightCounterMs / (double) _flightLengthMs); int r = fp.scale * 40 / 100; if ((int) p.sqrDist(fp.centerPos) > r * r) return; room->stopAnim(LayerId(_flyAnim, _projectileId, "projectile")); _isFlightFinished = true; _pending = 1; LayerId l = LayerId(_interceptAnim, _projectileId, "projectile"); room->playAnimWithSFX(l, "v7130eg0", 400, PlayAnimParams::disappear(), Common::SharedPtr(new HandlerProjectile(backRef, 15054)), fp.centerPos - Common::Point(186, 210) * (fp.scale / 100.0)); } }