| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207 |
- // Copyright (c) 2022-2023 the Dviglo project
- // Copyright (c) 2008-2023 the Urho3D project
- // License: MIT
- /*
- Простой запуск игры - одиночная игра.
- При запуске с параметром "-nobgm" фоновая музыка будет отключена.
- С параметром "-server" игра запустится в режиме сервера.
- Сервер удобно запускать в виде консольного приложения, используя параметры "-server -headless".
- С параметром "-address 127.0.0.1" игра запустится в режиме клиента и
- подключится к серверу, запущенному на этом же компьютере.
- Также игра поддерживает геймпады и сенсорные экраны.
- Параметр "-touch" позволяет сэмулировать тачскрин на ПК.
- Друг другу игроки урон не наносят.
- */
- #include <Urho3D/Urho3DAll.h>
- #include "foot_steps.h"
- #include "light_flash.h"
- #include "ninja.h"
- #include "player.h"
- #include "potion.h"
- #include "snow_crate.h"
- #include "snowball.h"
- #include "utilities/network.h"
- #include "utilities/spawn.h"
- namespace Urho3D
- {
- static constexpr float MOUSE_SENSITIVITY = 0.125f;
- static constexpr float TOUCH_SENSITIVITY = 2.0f;
- static constexpr float JOY_SENSITIVITY = 0.5f;
- static constexpr float JOY_MOVE_DEAD_ZONE = 0.333f;
- static constexpr float JOY_LOOK_DEAD_ZONE = 0.05f;
- static constexpr float CAMERA_MIN_DIST = 0.25f;
- static constexpr float CAMERA_MAX_DIST = 5.0f;
- static constexpr float CAMERA_SAFETY_DIST = 0.3f;
- static constexpr i32 INITIAL_MAX_ENEMIES = 5;
- static constexpr i32 FINAL_MAX_ENEMIES = 25;
- static constexpr i32 MAX_POWERUPS = 5;
- static constexpr i32 INCREMENT_EACH = 10;
- static constexpr i32 PLAYER_HEALTH = 20;
- static constexpr float ENEMY_SPAWN_RATE = 1.0f;
- static constexpr float POWERUP_SPAWN_RATE = 15.0f;
- static constexpr float SPAWN_AREA_SIZE = 5.0f;
- class App : public Application
- {
- URHO3D_OBJECT(App, Application);
- private:
- SharedPtr<Scene> gameScene;
- SharedPtr<Node> gameCameraNode;
- Camera* gameCamera = nullptr;
- SharedPtr<Node> musicNode;
- SoundSource* musicSource = nullptr;
- SharedPtr<Text> scoreText;
- SharedPtr<Text> hiscoreText;
- SharedPtr<Text> messageText;
- SharedPtr<BorderImage> healthBar;
- SharedPtr<BorderImage> sight;
- Controls playerControls;
- Controls prevPlayerControls;
- bool singlePlayer = true;
- bool gameOn = false;
- bool drawDebug = false;
- bool drawOctreeDebug = false;
- i32 maxEnemies = 0;
- i32 incrementCounter = 0;
- float enemySpawnTimer = 0.f;
- float powerupSpawnTimer = 0.f;
- NodeId clientNodeID = 0;
- i32 clientScore = 0;
- i32 screenJoystickID = -1;
- i32 screenJoystickSettingsID = -1;
- bool touchEnabled = false;
- Vector<Player> players;
- Vector<HiscoreEntry> hiscores;
- public:
- App(Context* context)
- : Application(context)
- {
- Ninja::RegisterObject(context);
- FootSteps::RegisterObject(context);
- GameObject::RegisterObject(context);
- LightFlash::RegisterObject(context);
- Potion::RegisterObject(context);
- SnowCrate::RegisterObject(context);
- Snowball::RegisterObject(context);
- }
- void Setup() override
- {
- // Modify engine startup parameters
- engineParameters_[EP_WINDOW_TITLE] = "Ninja Snow Wars Native";
- engineParameters_[EP_LOG_NAME] = GetSubsystem<FileSystem>()->GetAppPreferencesDir("urho3d", "logs") + "80_ninja_snow_wars_native.log";
- //engineParameters_[EP_FULL_SCREEN] = false;
- }
- void Start() override
- {
- if (GetSubsystem<Engine>()->IsHeadless())
- OpenConsoleWindow();
- ParseNetworkArguments();
- if (runServer || runClient)
- singlePlayer = false;
- InitAudio();
- InitConsole();
- InitScene();
- InitNetworking();
- CreateCamera();
- CreateOverlays();
- SubscribeToEvent(gameScene, E_SCENEUPDATE, URHO3D_HANDLER(App, HandleUpdate));
- PhysicsWorld* physicsWorld = gameScene->GetComponent<PhysicsWorld>();
- if (physicsWorld)
- SubscribeToEvent(physicsWorld, E_PHYSICSPRESTEP, URHO3D_HANDLER(App, HandleFixedUpdate));
- SubscribeToEvent(gameScene, E_SCENEPOSTUPDATE, URHO3D_HANDLER(App, HandlePostUpdate));
- SubscribeToEvent(E_POSTRENDERUPDATE, URHO3D_HANDLER(App, HandlePostRenderUpdate));
- SubscribeToEvent(E_KEYDOWN, URHO3D_HANDLER(App, HandleKeyDown));
- SubscribeToEvent("Points", URHO3D_HANDLER(App, HandlePoints));
- SubscribeToEvent("Kill", URHO3D_HANDLER(App, HandleKill));
- SubscribeToEvent(E_SCREENMODE, URHO3D_HANDLER(App, HandleScreenMode));
- if (singlePlayer)
- {
- StartGame(nullptr);
- GetSubsystem<Engine>()->SetPauseMinimized(true);
- }
- }
- void InitAudio()
- {
- if (GetSubsystem<Engine>()->IsHeadless())
- return;
- Audio* audio = GetSubsystem<Audio>();
- // Lower mastervolumes slightly.
- audio->SetMasterGain(SOUND_MASTER, 0.75f);
- audio->SetMasterGain(SOUND_MUSIC, 0.9f);
- if (!nobgm)
- {
- Sound* musicFile = GetSubsystem<ResourceCache>()->GetResource<Sound>("Music/Ninja Gods.ogg");
- musicFile->SetLooped(true);
- // Note: the non-positional sound source component need to be attached to a node to become effective
- // Due to networked mode clearing the scene on connect, do not attach to the scene itself
- musicNode = new Node(context_);
- musicSource = musicNode->CreateComponent<SoundSource>();
- musicSource->SetSoundType(SOUND_MUSIC);
- musicSource->Play(musicFile);
- }
- }
- void InitConsole()
- {
- Engine* engine = GetSubsystem<Engine>();
- if (engine->IsHeadless())
- return;
- XMLFile* uiStyle = GetSubsystem<ResourceCache>()->GetResource<XMLFile>("UI/DefaultStyle.xml");
- GetSubsystem<UI>()->GetRoot()->SetDefaultStyle(uiStyle);
- Console* console = engine->CreateConsole();
- console->SetDefaultStyle(uiStyle);
- console->GetBackground()->SetOpacity(0.8f);
- DebugHud* debug_hud = engine->CreateDebugHud();
- debug_hud->SetDefaultStyle(uiStyle);
- }
- void InitScene()
- {
- gameScene = new Scene(context_);
- gameScene->SetName("NinjaSnowWar");
- // For the multiplayer client, do not load the scene, let it load from the server
- if (runClient)
- return;
- ResourceCache* cache = GetSubsystem<ResourceCache>();
- Renderer* renderer = GetSubsystem<Renderer>();
- Engine* engine = GetSubsystem<Engine>();
- Graphics* graphics = GetSubsystem<Graphics>();
- gameScene->LoadXML(*cache->GetFile("Scenes/NinjaSnowWar.xml"));
- // On mobile devices render the shadowmap first for better performance, adjust the cascaded shadows
- String platform = GetPlatform();
- if (platform == "Android" || platform == "iOS" || platform == "Raspberry Pi")
- {
- renderer->SetReuseShadowMaps(false);
- // Adjust the directional light shadow range slightly further, as only the first
- // cascade is supported
- Node* dirLightNode = gameScene->GetChild("GlobalLight", true);
- if (dirLightNode)
- {
- Light* dirLight = dirLightNode->GetComponent<Light>();
- dirLight->SetShadowCascade(CascadeParameters(15.0f, 0.0f, 0.0f, 0.0f, 0.9f));
- }
- }
- // Precache shaders if possible
- if (!engine->IsHeadless() && cache->Exists("NinjaSnowWarShaders.xml"))
- graphics->PrecacheShaders(*cache->GetFile("NinjaSnowWarShaders.xml"));
- }
- void InitNetworking()
- {
- Network* network = GetSubsystem<Network>();
- network->SetUpdateFps(25); // 1/4 of physics FPS
- // Remote events sent between client & server must be explicitly registered or else they are not allowed to be received
- network->RegisterRemoteEvent("PlayerSpawned");
- network->RegisterRemoteEvent("UpdateScore");
- network->RegisterRemoteEvent("UpdateHiscores");
- network->RegisterRemoteEvent("ParticleEffect");
- if (runServer)
- {
- network->StartServer(serverPort);
- // Disable physics interpolation to ensure clients get sent physically correct transforms
- gameScene->GetComponent<PhysicsWorld>()->SetInterpolation(false);
- SubscribeToEvent(E_CLIENTIDENTITY, URHO3D_HANDLER(App, HandleClientIdentity));
- SubscribeToEvent(E_CLIENTSCENELOADED, URHO3D_HANDLER(App, HandleClientSceneLoaded));
- SubscribeToEvent(E_CLIENTDISCONNECTED, URHO3D_HANDLER(App, HandleClientDisconnected));
- }
- if (runClient)
- {
- VariantMap identity;
- identity["UserName"] = userName;
- network->SetUpdateFps(50); // Increase controls send rate for better responsiveness
- network->Connect(serverAddress, serverPort, gameScene, identity);
- SubscribeToEvent("PlayerSpawned", URHO3D_HANDLER(App, HandlePlayerSpawned));
- SubscribeToEvent("UpdateScore", URHO3D_HANDLER(App, HandleUpdateScore));
- SubscribeToEvent("UpdateHiscores", URHO3D_HANDLER(App, HandleUpdateHiscores));
- SubscribeToEvent(E_NETWORKUPDATESENT, URHO3D_HANDLER(App, HandleNetworkUpdateSent));
- }
- }
- void InitTouchInput()
- {
- Input* input = GetSubsystem<Input>();
- ResourceCache* cache = GetSubsystem<ResourceCache>();
- touchEnabled = true;
- screenJoystickID = input->AddScreenJoystick(cache->GetResource<XMLFile>("UI/ScreenJoystick_NinjaSnowWar.xml"));
- }
- void CreateCamera()
- {
- // Note: the camera is not in the scene
- gameCameraNode = new Node(context_);
- gameCameraNode->SetPosition(Vector3(0.f, 2.f, -10.f));
- gameCamera = gameCameraNode->CreateComponent<Camera>();
- gameCamera->SetNearClip(0.5f);
- gameCamera->SetFarClip(160.f);
- Engine* engine = GetSubsystem<Engine>();
- Renderer* renderer = GetSubsystem<Renderer>();
- Audio* audio = GetSubsystem<Audio>();
- if (!engine->IsHeadless())
- {
- SharedPtr<Viewport> viewport(new Viewport(context_, gameScene, gameCamera));
- renderer->SetViewport(0, viewport);
- audio->SetListener(gameCameraNode->CreateComponent<SoundListener>());
- }
- }
- void CreateOverlays()
- {
- if (GetSubsystem<Engine>()->IsHeadless() || runServer)
- return;
- i32 height = GetSubsystem<Graphics>()->GetHeight() / 22;
- if (height > 64)
- height = 64;
- ResourceCache* cache = GetSubsystem<ResourceCache>();
- UI* ui = GetSubsystem<UI>();
- Input* input = GetSubsystem<Input>();
- sight = new BorderImage(context_);
- sight->SetTexture(cache->GetResource<Texture2D>("Textures/NinjaSnowWar/Sight.png"));
- sight->SetAlignment(HA_CENTER, VA_CENTER);
- sight->SetSize(height, height);
- ui->GetRoot()->AddChild(sight);
- Font* font = cache->GetResource<Font>("Fonts/BlueHighway.ttf");
- scoreText = new Text(context_);
- scoreText->SetFont(font, 13.f);
- scoreText->SetAlignment(HA_LEFT, VA_TOP);
- scoreText->SetPosition(5, 5);
- scoreText->SetColor(C_BOTTOMLEFT, Color(1.f, 1.f, 0.25f));
- scoreText->SetColor(C_BOTTOMRIGHT, Color(1.f, 1.f, 0.25f));
- ui->GetRoot()->AddChild(scoreText);
- hiscoreText = new Text(context_);
- hiscoreText->SetFont(font, 13.f);
- hiscoreText->SetAlignment(HA_RIGHT, VA_TOP);
- hiscoreText->SetPosition(-5, 5);
- hiscoreText->SetColor(C_BOTTOMLEFT, Color(1.f, 1.f, 0.25f));
- hiscoreText->SetColor(C_BOTTOMRIGHT, Color(1.f, 1.f, 0.25f));
- ui->GetRoot()->AddChild(hiscoreText);
- messageText = new Text(context_);
- messageText->SetFont(font, 13.f);
- messageText->SetAlignment(HA_CENTER, VA_CENTER);
- messageText->SetPosition(0, -height * 2);
- messageText->SetColor(Color(1.f, 0.f, 0.f));
- ui->GetRoot()->AddChild(messageText);
- SharedPtr<BorderImage> healthBorder(new BorderImage(context_));
- healthBorder->SetTexture(cache->GetResource<Texture2D>("Textures/NinjaSnowWar/HealthBarBorder.png"));
- healthBorder->SetAlignment(HA_CENTER, VA_TOP);
- healthBorder->SetPosition(0, 8);
- healthBorder->SetSize(120, 20);
- ui->GetRoot()->AddChild(healthBorder);
- healthBar = new BorderImage(context_);
- healthBar->SetTexture(cache->GetResource<Texture2D>("Textures/NinjaSnowWar/HealthBarInside.png"));
- healthBar->SetPosition(2, 2);
- healthBar->SetSize(116, 16);
- healthBorder->AddChild(healthBar);
- if (GetPlatform() == "Android" || GetPlatform() == "iOS")
- // On mobile platform, enable touch by adding a screen joystick
- InitTouchInput();
- else if (input->GetNumJoysticks() == 0)
- // On desktop platform, do not detect touch when we already got a joystick
- SubscribeToEvent(E_TOUCHBEGIN, URHO3D_HANDLER(App, HandleTouchBegin));
- }
- void SetMessage(const String& message)
- {
- if (messageText)
- messageText->SetText(message);
- }
- void StartGame(Connection* connection)
- {
- // Clear the scene of all existing scripted objects
- {
- Vector<Node*> scriptedNodes;
- for (Node* child : gameScene->GetChildren())
- {
- for (Component* component : child->GetComponents())
- {
- // Проверяем, что компонент - производный от GameObject
- GameObject* gameObject = dynamic_cast<GameObject*>(component);
- if (gameObject)
- scriptedNodes.Push(child);
- }
- }
- for (Node* node : scriptedNodes)
- node->Remove();
- }
- players.Clear();
- SpawnPlayer(connection);
- ResetAI();
- gameOn = true;
- maxEnemies = INITIAL_MAX_ENEMIES;
- incrementCounter = 0;
- enemySpawnTimer = 0;
- powerupSpawnTimer = 0;
- if (singlePlayer)
- {
- playerControls.yaw_ = 0;
- playerControls.pitch_ = 0;
- SetMessage("");
- }
- }
- void SpawnPlayer(Connection* connection)
- {
- Vector3 spawnPosition;
- if (singlePlayer)
- spawnPosition = Vector3(0.f, 0.97f, 0.f);
- else
- spawnPosition = Vector3(Random(SPAWN_AREA_SIZE) - SPAWN_AREA_SIZE * 0.5f, 0.97f, Random(SPAWN_AREA_SIZE) - SPAWN_AREA_SIZE);
- Node* playerNode = SpawnObject(gameScene, spawnPosition, Quaternion(), "ninja");
- // Set owner connection. Owned nodes are always updated to the owner at full frequency
- playerNode->SetOwner(connection);
- playerNode->SetName("Player");
- // Initialize variables
- Ninja* playerNinja = playerNode->GetComponent<Ninja>();
- playerNinja->health = playerNinja->maxHealth = PLAYER_HEALTH;
- playerNinja->side = SIDE_PLAYER;
- // Make sure the player can not shoot on first frame by holding the button down
- if (!connection)
- playerNinja->controls = playerNinja->prevControls = playerControls;
- else
- playerNinja->controls = playerNinja->prevControls = connection->GetControls();
- // Check if player entry already exists
- i32 playerIndex = -1;
- for (i32 i = 0; i < players.Size(); ++i)
- {
- if (players[i].connection == connection)
- {
- playerIndex = i;
- break;
- }
- }
- // Does not exist, create new
- if (playerIndex < 0)
- {
- playerIndex = players.Size();
- players.Resize(players.Size() + 1);
- players[playerIndex].connection = connection;
- if (connection)
- {
- players[playerIndex].name = connection->identity_["UserName"].GetString();
- // In multiplayer, send current hiscores to the new player
- SendHiscores(playerIndex);
- }
- else
- {
- players[playerIndex].name = "Player";
- // In singleplayer, create also the default hiscore entry immediately
- HiscoreEntry newHiscore;
- newHiscore.name = players[playerIndex].name;
- newHiscore.score = 0;
- hiscores.Push(newHiscore);
- }
- }
- players[playerIndex].nodeID = playerNode->GetID();
- players[playerIndex].score = 0;
- if (connection)
- {
- // In multiplayer, send initial score, then send a remote event that tells the spawned node's ID
- // It is important for the event to be in-order so that the node has been replicated first
- SendScore(playerIndex);
- VariantMap eventData;
- eventData["NodeID"] = playerNode->GetID();
- connection->SendRemoteEvent("PlayerSpawned", true, eventData);
- // Create name tag (Text3D component) for players in multiplayer
- Node* textNode = playerNode->CreateChild("NameTag");
- textNode->SetPosition(Vector3(0.f, 1.2f, 0.f));
- Text3D* text3D = textNode->CreateComponent<Text3D>();
- Font* font = GetSubsystem<ResourceCache>()->GetResource<Font>("Fonts/BlueHighway.ttf");
- text3D->SetFont(font, 19.f);
- text3D->SetColor(Color(1.f, 1.f, 0.f));
- text3D->SetText(players[playerIndex].name);
- text3D->SetHorizontalAlignment(HA_CENTER);
- text3D->SetVerticalAlignment(VA_CENTER);
- text3D->SetFaceCameraMode(FC_ROTATE_XYZ);
- }
- }
- void HandleUpdate(StringHash eventType, VariantMap& eventData)
- {
- float timeStep = eventData["TimeStep"].GetFloat();
- UpdateControls();
- CheckEndAndRestart();
- if (GetSubsystem<Engine>()->IsHeadless())
- {
- String command = GetConsoleInput();
- if (command.Length() > 0)
- GetSubsystem<Script>()->Execute(command);
- }
- else
- {
- DebugHud* debugHud = GetSubsystem<DebugHud>();
- if (debugHud->GetMode() != DebugHudElements::None)
- {
- Node* playerNode = FindOwnNode();
- if (playerNode)
- {
- debugHud->SetAppStats("Player Pos", playerNode->GetWorldPosition().ToString());
- debugHud->SetAppStats("Player Yaw", Variant(playerNode->GetWorldRotation().YawAngle()));
- }
- else
- {
- debugHud->ClearAppStats();
- }
- }
- }
- }
- void HandleFixedUpdate(StringHash eventType, VariantMap& eventData)
- {
- float timeStep = eventData["TimeStep"].GetFloat();
- // Spawn new objects, singleplayer or server only
- if (singlePlayer || runServer)
- SpawnObjects(timeStep);
- }
- void HandlePostUpdate(StringHash eventType, VariantMap& eventData)
- {
- UpdateCamera();
- UpdateStatus();
- }
- void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
- {
- if (GetSubsystem<Engine>()->IsHeadless())
- return;
- if (drawDebug)
- gameScene->GetComponent<PhysicsWorld>()->DrawDebugGeometry(true);
- if (drawOctreeDebug)
- gameScene->GetComponent<Octree>()->DrawDebugGeometry(true);
- }
- void HandleTouchBegin(StringHash eventType, VariantMap& eventData)
- {
- // On some platforms like Windows the presence of touch input can only be detected dynamically
- InitTouchInput();
- UnsubscribeFromEvent("TouchBegin");
- }
- void HandleKeyDown(StringHash eventType, VariantMap& eventData)
- {
- Console* console = GetSubsystem<Console>();
- DebugHud* debugHud = GetSubsystem<DebugHud>();
- Engine* engine = GetSubsystem<Engine>();
- Graphics* graphics = GetSubsystem<Graphics>();
- FileSystem* fileSystem = GetSubsystem<FileSystem>();
- Time* time = GetSubsystem<Time>();
- Audio* audio = GetSubsystem<Audio>();
- Input* input = GetSubsystem<Input>();
- ResourceCache* cache = GetSubsystem<ResourceCache>();
- i32 key = eventData["Key"].GetI32();
- if (key == KEY_ESCAPE)
- {
- if (!console->IsVisible())
- engine->Exit();
- else
- console->SetVisible(false);
- }
- else if (key == KEY_F1)
- console->Toggle();
- else if (key == KEY_F2)
- debugHud->ToggleAll();
- else if (key == KEY_F3)
- drawDebug = !drawDebug;
- else if (key == KEY_F4)
- drawOctreeDebug = !drawOctreeDebug;
- else if (key == KEY_F5)
- debugHud->Toggle(DebugHudElements::EventProfiler);
- // Take screenshot
- else if (key == KEY_F6)
- {
- Image screenshot(context_);
- graphics->TakeScreenShot(screenshot);
- // Here we save in the Data folder with date and time appended
- screenshot.SavePNG(fileSystem->GetProgramDir() + "Data/Screenshot_" +
- time->GetTimeStamp().Replaced(':', '_').Replaced('.', '_').Replaced(' ', '_') + ".png");
- }
- // Allow pause only in singleplayer
- if (key == KEY_P && singlePlayer && !console->IsVisible() && gameOn)
- {
- gameScene->SetUpdateEnabled(!gameScene->IsUpdateEnabled());
- if (!gameScene->IsUpdateEnabled())
- {
- SetMessage("PAUSED");
- audio->PauseSoundType(SOUND_EFFECT);
- // Open the settings joystick only if the controls screen joystick was already open
- if (screenJoystickID >= 0)
- {
- // Lazy initialization
- if (screenJoystickSettingsID < 0)
- screenJoystickSettingsID = input->AddScreenJoystick(cache->GetResource<XMLFile>("UI/ScreenJoystickSettings_NinjaSnowWar.xml"));
- else
- input->SetScreenJoystickVisible(screenJoystickSettingsID, true);
- }
- }
- else
- {
- SetMessage("");
- audio->ResumeSoundType(SOUND_EFFECT);
- // Hide the settings joystick
- if (screenJoystickSettingsID >= 0)
- input->SetScreenJoystickVisible(screenJoystickSettingsID, false);
- }
- }
- }
- void HandlePoints(StringHash eventType, VariantMap& eventData)
- {
- if (eventData["DamageSide"].GetI32() == SIDE_PLAYER)
- {
- // Get node ID of the object that should receive points -> use it to find player index
- i32 playerIndex = FindPlayerIndex(eventData["Receiver"].GetI32());
- if (playerIndex >= 0)
- {
- players[playerIndex].score += eventData["Points"].GetI32();
- SendScore(playerIndex);
- bool newHiscore = CheckHiscore(playerIndex);
- if (newHiscore)
- SendHiscores(-1);
- }
- }
- }
- void HandleKill(StringHash eventType, VariantMap& eventData)
- {
- if (eventData["DamageSide"].GetI32() == SIDE_PLAYER)
- {
- MakeAIHarder();
- // Increment amount of simultaneous enemies after enough kills
- incrementCounter++;
- if (incrementCounter >= INCREMENT_EACH)
- {
- incrementCounter = 0;
- if (maxEnemies < FINAL_MAX_ENEMIES)
- maxEnemies++;
- }
- }
- }
- void HandleClientIdentity(StringHash eventType, VariantMap& eventData)
- {
- Connection* connection = (Connection*)GetEventSender();
- // If user has empty name, invent one
- if (connection->identity_["UserName"].GetString().Trimmed().Empty())
- connection->identity_["UserName"] = "user" + String(Random(1000));
- // Assign scene to begin replicating it to the client
- connection->SetScene(gameScene);
- }
- void HandleClientSceneLoaded(StringHash eventType, VariantMap& eventData)
- {
- // Now client is actually ready to begin. If first player, clear the scene and restart the game
- Connection* connection = (Connection*)GetEventSender();
- if (players.Empty())
- StartGame(connection);
- else
- SpawnPlayer(connection);
- }
- void HandleClientDisconnected(StringHash eventType, VariantMap& eventData)
- {
- Connection* connection = (Connection*)GetEventSender();
- // Erase the player entry, and make the player's ninja commit seppuku (if exists)
- for (i32 i = 0; i < players.Size(); ++i)
- {
- if (players[i].connection == connection)
- {
- players[i].connection = nullptr;
- Node* playerNode = FindPlayerNode(i);
- if (playerNode)
- {
- Ninja* playerNinja = playerNode->GetComponent<Ninja>();
- playerNinja->health = 0;
- playerNinja->lastDamageSide = SIDE_NEUTRAL; // No-one scores from this
- }
- players.Erase(i);
- return;
- }
- }
- }
- void HandlePlayerSpawned(StringHash eventType, VariantMap& eventData)
- {
- // Store our node ID and mark the game as started
- clientNodeID = eventData["NodeID"].GetI32();
- gameOn = true;
- SetMessage("");
- // Copy initial yaw from the player node (we should have it replicated now)
- Node* playerNode = FindOwnNode();
- if (playerNode)
- {
- playerControls.yaw_ = playerNode->GetRotation().YawAngle();
- playerControls.pitch_ = 0.f;
- // Disable the nametag from own character
- Node* nameTag = playerNode->GetChild("NameTag");
- nameTag->SetEnabled(false);
- }
- }
- void HandleUpdateScore(StringHash eventType, VariantMap& eventData)
- {
- clientScore = eventData["Score"].GetI32();
- scoreText->SetText("Score " + String(clientScore));
- }
- void HandleUpdateHiscores(StringHash eventType, VariantMap& eventData)
- {
- VectorBuffer data(eventData["Hiscores"].GetBuffer());
- hiscores.Resize(data.ReadVLE());
- for (i32 i = 0; i < hiscores.Size(); ++i)
- {
- hiscores[i].name = data.ReadString();
- hiscores[i].score = data.ReadI32();
- }
- String allHiscores;
- for (i32 i = 0; i < hiscores.Size(); ++i)
- allHiscores += hiscores[i].name + " " + String(hiscores[i].score) + "\n";
- hiscoreText->SetText(allHiscores);
- }
- void HandleNetworkUpdateSent(StringHash eventType, VariantMap& eventData)
- {
- Network* network = GetSubsystem<Network>();
- // Clear accumulated buttons from the network controls
- if (network->GetServerConnection() != nullptr)
- network->GetServerConnection()->controls_.Set(CTRL_ALL, false);
- }
- i32 FindPlayerIndex(NodeId nodeID)
- {
- for (i32 i = 0; i < players.Size(); ++i)
- {
- if (players[i].nodeID == nodeID)
- return i;
- }
- return -1;
- }
- Node* FindPlayerNode(i32 playerIndex)
- {
- if (playerIndex >= 0 && playerIndex < players.Size())
- return gameScene->GetNode(players[playerIndex].nodeID);
- else
- return nullptr;
- }
- Node* FindOwnNode()
- {
- if (singlePlayer)
- return gameScene->GetChild("Player", true);
- else
- return gameScene->GetNode(clientNodeID);
- }
- bool CheckHiscore(i32 playerIndex)
- {
- for (i32 i = 0; i < hiscores.Size(); ++i)
- {
- if (hiscores[i].name == players[playerIndex].name)
- {
- if (players[playerIndex].score > hiscores[i].score)
- {
- hiscores[i].score = players[playerIndex].score;
- SortHiscores();
- return true;
- }
- else
- return false; // No update to individual hiscore
- }
- }
- // Not found, create new hiscore entry
- HiscoreEntry newHiscore;
- newHiscore.name = players[playerIndex].name;
- newHiscore.score = players[playerIndex].score;
- hiscores.Push(newHiscore);
- SortHiscores();
- return true;
- }
- void SortHiscores()
- {
- for (i32 i = 1; i < hiscores.Size(); ++i)
- {
- HiscoreEntry temp = hiscores[i];
- i32 j = i;
- while (j > 0 && temp.score > hiscores[j - 1].score)
- {
- hiscores[j] = hiscores[j - 1];
- --j;
- }
- hiscores[j] = temp;
- }
- }
- void SendScore(i32 playerIndex)
- {
- if (!runServer || playerIndex < 0 || playerIndex >= players.Size())
- return;
- VariantMap eventData;
- eventData["Score"] = players[playerIndex].score;
- players[playerIndex].connection->SendRemoteEvent("UpdateScore", true, eventData);
- }
- void SendHiscores(int playerIndex)
- {
- if (!runServer)
- return;
- VectorBuffer data;
- data.WriteVLE(hiscores.Size());
- for (i32 i = 0; i < hiscores.Size(); ++i)
- {
- data.WriteString(hiscores[i].name);
- data.WriteI32(hiscores[i].score);
- }
- VariantMap eventData;
- eventData["Hiscores"] = data;
- if (playerIndex >= 0 && playerIndex < players.Size())
- players[playerIndex].connection->SendRemoteEvent("UpdateHiscores", true, eventData);
- else
- GetSubsystem<Network>()->BroadcastRemoteEvent(gameScene, "UpdateHiscores", true, eventData); // Send to all in scene
- }
- void SpawnObjects(float timeStep)
- {
- // If game not running, run only the random generator
- if (!gameOn)
- {
- Random();
- return;
- }
- // Spawn powerups
- powerupSpawnTimer += timeStep;
- if (powerupSpawnTimer >= POWERUP_SPAWN_RATE)
- {
- powerupSpawnTimer = 0;
- i32 numPowerups = gameScene->GetChildrenWithComponent("SnowCrate", true).Size() + gameScene->GetChildrenWithComponent("Potion", true).Size();
- if (numPowerups < MAX_POWERUPS)
- {
- const float maxOffset = 40.f;
- float xOffset = Random(maxOffset * 2.0f) - maxOffset;
- float zOffset = Random(maxOffset * 2.0f) - maxOffset;
- SpawnObject(gameScene, Vector3(xOffset, 50.f, zOffset), Quaternion(), "snow_crate");
- }
- }
- // Spawn enemies
- enemySpawnTimer += timeStep;
- if (enemySpawnTimer > ENEMY_SPAWN_RATE)
- {
- enemySpawnTimer = 0;
- i32 numEnemies = 0;
- Vector<Node*> ninjaNodes = gameScene->GetChildrenWithComponent("Ninja", true);
- for (i32 i = 0; i < ninjaNodes.Size(); ++i)
- {
- Ninja* ninja = ninjaNodes[i]->GetComponent<Ninja>();
- if (ninja->side == SIDE_ENEMY)
- ++numEnemies;
- }
- if (numEnemies < maxEnemies)
- {
- const float maxOffset = 40.f;
- float offset = Random(maxOffset * 2.0f) - maxOffset;
- // Random north/east/south/west direction
- i32 dir = Rand() & 3;
- dir *= 90;
- Quaternion rotation(0.f, (float)dir, 0.f);
- Node* enemyNode = SpawnObject(gameScene, rotation * Vector3(offset, 10.f, -120.f), rotation, "ninja");
- // Initialize variables
- Ninja* enemyNinja = enemyNode->GetComponent<Ninja>();
- enemyNinja->side = SIDE_ENEMY;
- enemyNinja->controller.reset(new AIController());
- RigidBody* enemyBody = enemyNode->GetComponent<RigidBody>();
- enemyBody->SetLinearVelocity(rotation * Vector3(0.f, 10.f, 30.f));
- }
- }
- }
- void CheckEndAndRestart()
- {
- // Only check end of game if singleplayer or client
- if (runServer)
- return;
- // Check if player node has vanished
- Node* playerNode = FindOwnNode();
- if (gameOn && !playerNode)
- {
- gameOn = false;
- SetMessage("Press Fire or Jump to restart!");
- return;
- }
- // Check for restart (singleplayer only)
- if (!gameOn && singlePlayer && playerControls.IsPressed(CTRL_FIRE | CTRL_JUMP, prevPlayerControls))
- StartGame(nullptr);
- }
- void UpdateControls()
- {
- Input* input = GetSubsystem<Input>();
- Graphics* graphics = GetSubsystem<Graphics>();
- Console* console = GetSubsystem<Console>();
- Network* network = GetSubsystem<Network>();
- if (singlePlayer || runClient)
- {
- prevPlayerControls = playerControls;
- playerControls.Set(CTRL_ALL, false);
- if (touchEnabled)
- {
- for (i32 i = 0; i < input->GetNumTouches(); ++i)
- {
- TouchState* touch = input->GetTouch(i);
- if (!touch->touchedElement_)
- {
- // Touch on empty space
- playerControls.yaw_ += TOUCH_SENSITIVITY * gameCamera->GetFov() / graphics->GetHeight() * touch->delta_.x_;
- playerControls.pitch_ += TOUCH_SENSITIVITY * gameCamera->GetFov() / graphics->GetHeight() * touch->delta_.y_;
- }
- }
- }
- if (input->GetNumJoysticks() > 0)
- {
- JoystickState* joystick = touchEnabled ? input->GetJoystick(screenJoystickID) : input->GetJoystickByIndex(0);
- if (joystick->GetNumButtons() > 0)
- {
- if (joystick->GetButtonDown(0))
- playerControls.Set(CTRL_JUMP, true);
- if (joystick->GetButtonDown(1))
- playerControls.Set(CTRL_FIRE, true);
- if (joystick->GetNumButtons() >= 6)
- {
- if (joystick->GetButtonDown(4))
- playerControls.Set(CTRL_JUMP, true);
- if (joystick->GetButtonDown(5))
- playerControls.Set(CTRL_FIRE, true);
- }
- if (joystick->GetNumHats() > 0)
- {
- if ((joystick->GetHatPosition(0) & HAT_LEFT) != 0)
- playerControls.Set(CTRL_LEFT, true);
- if ((joystick->GetHatPosition(0) & HAT_RIGHT) != 0)
- playerControls.Set(CTRL_RIGHT, true);
- if ((joystick->GetHatPosition(0) & HAT_UP) != 0)
- playerControls.Set(CTRL_UP, true);
- if ((joystick->GetHatPosition(0) & HAT_DOWN) != 0)
- playerControls.Set(CTRL_DOWN, true);
- }
- if (joystick->GetNumAxes() >= 2)
- {
- if (joystick->GetAxisPosition(0) < -JOY_MOVE_DEAD_ZONE)
- playerControls.Set(CTRL_LEFT, true);
- if (joystick->GetAxisPosition(0) > JOY_MOVE_DEAD_ZONE)
- playerControls.Set(CTRL_RIGHT, true);
- if (joystick->GetAxisPosition(1) < -JOY_MOVE_DEAD_ZONE)
- playerControls.Set(CTRL_UP, true);
- if (joystick->GetAxisPosition(1) > JOY_MOVE_DEAD_ZONE)
- playerControls.Set(CTRL_DOWN, true);
- }
- if (joystick->GetNumAxes() >= 4)
- {
- float lookX = joystick->GetAxisPosition(2);
- float lookY = joystick->GetAxisPosition(3);
- if (lookX < -JOY_LOOK_DEAD_ZONE)
- playerControls.yaw_ -= JOY_SENSITIVITY * lookX * lookX;
- if (lookX > JOY_LOOK_DEAD_ZONE)
- playerControls.yaw_ += JOY_SENSITIVITY * lookX * lookX;
- if (lookY < -JOY_LOOK_DEAD_ZONE)
- playerControls.pitch_ -= JOY_SENSITIVITY * lookY * lookY;
- if (lookY > JOY_LOOK_DEAD_ZONE)
- playerControls.pitch_ += JOY_SENSITIVITY * lookY * lookY;
- }
- }
- }
- // For the triggered actions (fire & jump) check also for press, in case the FPS is low
- // and the key was already released
- if (!console || !console->IsVisible())
- {
- if (input->GetKeyDown(KEY_W))
- playerControls.Set(CTRL_UP, true);
- if (input->GetKeyDown(KEY_S))
- playerControls.Set(CTRL_DOWN, true);
- if (input->GetKeyDown(KEY_A))
- playerControls.Set(CTRL_LEFT, true);
- if (input->GetKeyDown(KEY_D))
- playerControls.Set(CTRL_RIGHT, true);
- if (input->GetKeyDown(KEY_LCTRL) || input->GetKeyPress(KEY_LCTRL))
- playerControls.Set(CTRL_FIRE, true);
- if (input->GetKeyDown(KEY_SPACE) || input->GetKeyPress(KEY_SPACE))
- playerControls.Set(CTRL_JUMP, true);
- if (input->GetMouseButtonDown(MOUSEB_LEFT) || input->GetMouseButtonPress(MOUSEB_LEFT))
- playerControls.Set(CTRL_FIRE, true);
- if (input->GetMouseButtonDown(MOUSEB_RIGHT) || input->GetMouseButtonPress(MOUSEB_RIGHT))
- playerControls.Set(CTRL_JUMP, true);
- playerControls.yaw_ += MOUSE_SENSITIVITY * input->GetMouseMoveX();
- playerControls.pitch_ += MOUSE_SENSITIVITY * input->GetMouseMoveY();
- playerControls.pitch_ = Clamp(playerControls.pitch_, -60.0f, 60.0f);
- }
- // In singleplayer, set controls directly on the player's ninja. In multiplayer, transmit to server
- if (singlePlayer)
- {
- Node* playerNode = gameScene->GetChild("Player", true);
- if (playerNode)
- {
- Ninja* playerNinja = playerNode->GetComponent<Ninja>();
- playerNinja->controls = playerControls;
- }
- }
- else if (network->GetServerConnection() != nullptr)
- {
- // Set the latest yaw & pitch to server controls, and accumulate the buttons so that we do not miss any presses
- network->GetServerConnection()->controls_.yaw_ = playerControls.yaw_;
- network->GetServerConnection()->controls_.pitch_ = playerControls.pitch_;
- network->GetServerConnection()->controls_.buttons_ |= playerControls.buttons_;
- // Tell the camera position to server for interest management
- network->GetServerConnection()->SetPosition(gameCameraNode->GetWorldPosition());
- }
- }
- if (runServer)
- {
- // Apply each connection's controls to the ninja they control
- for (i32 i = 0; i < players.Size(); ++i)
- {
- Node* playerNode = FindPlayerNode(i);
- if (playerNode)
- {
- Ninja* playerNinja = playerNode->GetComponent<Ninja>();
- playerNinja->controls = players[i].connection->controls_;
- }
- else
- {
- // If player has no ninja, respawn if fire/jump is pressed
- if (players[i].connection->controls_.IsPressed(CTRL_FIRE | CTRL_JUMP, players[i].lastControls))
- SpawnPlayer(players[i].connection);
- }
- players[i].lastControls = players[i].connection->controls_;
- }
- }
- }
- void UpdateCamera()
- {
- if (GetSubsystem<Engine>()->IsHeadless())
- return;
- // On the server, use a simple freelook camera
- if (runServer)
- {
- UpdateFreelookCamera();
- return;
- }
- Node* playerNode = FindOwnNode();
- if (!playerNode)
- return;
- Vector3 pos = playerNode->GetPosition();
- Quaternion dir;
- // Make controls seem more immediate by forcing the current mouse yaw to player ninja's Y-axis rotation
- if (playerNode->GetVar("Health").GetI32() > 0)
- playerNode->SetRotation(Quaternion(0.f, playerControls.yaw_, 0.f));
- dir = dir * Quaternion(playerNode->GetRotation().YawAngle(), Vector3(0.f, 1.f, 0.f));
- dir = dir * Quaternion(playerControls.pitch_, Vector3(1.f, 0.f, 0.f));
- Vector3 aimPoint = pos + Vector3(0.f, 1.f, 0.f);
- Vector3 minDist = aimPoint + dir * Vector3(0.f, 0.f, -CAMERA_MIN_DIST);
- Vector3 maxDist = aimPoint + dir * Vector3(0.f, 0.f, -CAMERA_MAX_DIST);
- // Collide camera ray with static objects (collision mask 2)
- Vector3 rayDir = (maxDist - minDist).Normalized();
- float rayDistance = CAMERA_MAX_DIST - CAMERA_MIN_DIST + CAMERA_SAFETY_DIST;
- PhysicsRaycastResult result;
- gameScene->GetComponent<PhysicsWorld>()->RaycastSingle(result, Ray(minDist, rayDir), rayDistance, 2);
- if (result.body_)
- rayDistance = Min(rayDistance, result.distance_ - CAMERA_SAFETY_DIST);
- gameCameraNode->SetPosition(minDist + rayDir * rayDistance);
- gameCameraNode->SetRotation(dir);
- }
- void UpdateFreelookCamera()
- {
- Console* console = GetSubsystem<Console>();
- Time* time = GetSubsystem<Time>();
- Input* input = GetSubsystem<Input>();
- if (!console || !console->IsVisible())
- {
- float timeStep = time->GetTimeStep();
- float speedMultiplier = 1.0f;
- if (input->GetKeyDown(KEY_LSHIFT))
- speedMultiplier = 5.0f;
- if (input->GetKeyDown(KEY_LCTRL))
- speedMultiplier = 0.1f;
- if (input->GetKeyDown(KEY_W))
- gameCameraNode->Translate(Vector3(0.f, 0.f, 10.f) * timeStep * speedMultiplier);
- if (input->GetKeyDown(KEY_S))
- gameCameraNode->Translate(Vector3(0.f, 0.f, -10.f) * timeStep * speedMultiplier);
- if (input->GetKeyDown(KEY_A))
- gameCameraNode->Translate(Vector3(-10.f, 0.f, 0.f) * timeStep * speedMultiplier);
- if (input->GetKeyDown(KEY_D))
- gameCameraNode->Translate(Vector3(10.f, 0.f, 0.f) * timeStep * speedMultiplier);
- playerControls.yaw_ += MOUSE_SENSITIVITY * input->GetMouseMoveX();
- playerControls.pitch_ += MOUSE_SENSITIVITY * input->GetMouseMoveY();
- playerControls.pitch_ = Clamp(playerControls.pitch_, -90.0f, 90.0f);
- gameCameraNode->SetRotation(Quaternion(playerControls.pitch_, playerControls.yaw_, 0.f));
- }
- }
- void UpdateStatus()
- {
- Engine* engine = GetSubsystem<Engine>();
- if (engine->IsHeadless() || runServer)
- return;
- if (singlePlayer)
- {
- if (players.Size() > 0)
- scoreText->SetText("Score " + String(players[0].score));
- if (hiscores.Size() > 0)
- hiscoreText->SetText("Hiscore " + String(hiscores[0].score));
- }
- Node* playerNode = FindOwnNode();
- if (playerNode)
- {
- i32 health = 0;
- if (singlePlayer)
- {
- GameObject* object = playerNode->GetDerivedComponent<GameObject>();
- health = object->health;
- }
- else
- {
- // In multiplayer the client does not have script logic components, but health is replicated via node user variables
- health = playerNode->GetVar("Health").GetI32();
- }
- healthBar->SetWidth(116 * health / PLAYER_HEALTH);
- }
- }
- void HandleScreenMode(StringHash eventType, VariantMap& eventData)
- {
- Graphics* graphics = GetSubsystem<Graphics>();
- i32 height = graphics->GetHeight() / 22;
- if (height > 64)
- height = 64;
- sight->SetSize(height, height);
- messageText->SetPosition(0, -height * 2);
- }
- };
- } // namespace Urho3D
- URHO3D_DEFINE_APPLICATION_MAIN(App);
|