| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155 |
- // Remake of NinjaSnowWar in script
- #include "Scripts/NinjaSnowWar/FootSteps.as"
- #include "Scripts/NinjaSnowWar/LightFlash.as"
- #include "Scripts/NinjaSnowWar/Ninja.as"
- #include "Scripts/NinjaSnowWar/Player.as"
- #include "Scripts/NinjaSnowWar/Potion.as"
- #include "Scripts/NinjaSnowWar/SnowBall.as"
- #include "Scripts/NinjaSnowWar/SnowCrate.as"
- #include "Scripts/Utilities/Network.as"
- const float mouseSensitivity = 0.125;
- const float touchSensitivity = 2.0;
- const float joySensitivity = 0.5;
- const float joyMoveDeadZone = 0.333;
- const float joyLookDeadZone = 0.05;
- const float cameraMinDist = 0.25;
- const float cameraMaxDist = 5;
- const float cameraSafetyDist = 0.3;
- const int initialMaxEnemies = 5;
- const int finalMaxEnemies = 25;
- const int maxPowerups = 5;
- const int incrementEach = 10;
- const int playerHealth = 20;
- const float enemySpawnRate = 1;
- const float powerupSpawnRate = 15;
- const float spawnAreaSize = 5;
- Scene@ gameScene;
- Node@ gameCameraNode;
- Node@ musicNode;
- Camera@ gameCamera;
- Text@ scoreText;
- Text@ hiscoreText;
- Text@ messageText;
- BorderImage@ healthBar;
- BorderImage@ sight;
- BorderImage@ moveButton;
- BorderImage@ fireButton;
- SoundSource@ musicSource;
- Controls playerControls;
- Controls prevPlayerControls;
- bool singlePlayer = true;
- bool gameOn = false;
- bool drawDebug = false;
- bool drawOctreeDebug = false;
- int maxEnemies = 0;
- int incrementCounter = 0;
- float enemySpawnTimer = 0;
- float powerupSpawnTimer = 0;
- uint clientNodeID = 0;
- int clientScore = 0;
- bool touchEnabled = false;
- int touchButtonSize = 96;
- int touchButtonBorder = 12;
- int moveTouchID = -1;
- int rotateTouchID = -1;
- int fireTouchID = -1;
- Array<Player> players;
- Array<HiscoreEntry> hiscores;
- void Start()
- {
- if (engine.headless)
- OpenConsoleWindow();
- ParseNetworkArguments();
- if (runServer || runClient)
- singlePlayer = false;
- InitAudio();
- InitConsole();
- InitScene();
- InitNetworking();
- CreateCamera();
- CreateOverlays();
- SubscribeToEvent(gameScene, "SceneUpdate", "HandleUpdate");
- if (gameScene.physicsWorld !is null)
- SubscribeToEvent(gameScene.physicsWorld, "PhysicsPreStep", "HandleFixedUpdate");
- SubscribeToEvent(gameScene, "ScenePostUpdate", "HandlePostUpdate");
- SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
- SubscribeToEvent("KeyDown", "HandleKeyDown");
- SubscribeToEvent("Points", "HandlePoints");
- SubscribeToEvent("Kill", "HandleKill");
- SubscribeToEvent("ScreenMode", "HandleScreenMode");
- SubscribeToEvent("TouchBegin", "HandleTouchBegin");
- SubscribeToEvent("TouchEnd", "HandleTouchEnd");
- if (singlePlayer)
- {
- StartGame(null);
- engine.pauseMinimized = true;
- }
- }
- void InitAudio()
- {
- if (engine.headless)
- return;
- // Lower mastervolumes slightly.
- audio.masterGain[SOUND_MASTER] = 0.75;
- audio.masterGain[SOUND_MUSIC] = 0.9;
- if (!nobgm)
- {
- Sound@ musicFile = cache.GetResource("Sound", "Music/Ninja Gods.ogg");
- musicFile.looped = 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 = Node();
- musicSource = musicNode.CreateComponent("SoundSource");
- musicSource.soundType = SOUND_MUSIC;
- musicSource.Play(musicFile);
- }
- }
- void InitConsole()
- {
- if (engine.headless)
- return;
- XMLFile@ uiStyle = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");
- Console@ console = engine.CreateConsole();
- console.defaultStyle = uiStyle;
- console.numRows = 16;
- engine.CreateDebugHud();
- debugHud.defaultStyle = uiStyle;
- }
- void InitScene()
- {
- gameScene = Scene("NinjaSnowWar");
- // Enable access to this script file & scene from the console
- script.defaultScene = gameScene;
- script.defaultScriptFile = scriptFile;
- // For the multiplayer client, do not load the scene, let it load from the server
- if (runClient)
- return;
- gameScene.LoadXML(cache.GetFile("Scenes/NinjaSnowWar.xml"));
- // On mobile devices render the shadowmap first
- if (GetPlatform() == "Android" || GetPlatform() == "iOS")
- renderer.reuseShadowMaps = false;
- }
- void InitNetworking()
- {
- network.updateFps = 25; // 1/4 of physics FPS
- 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.physicsWorld.interpolation = false;
- SubscribeToEvent("ClientIdentity", "HandleClientIdentity");
- SubscribeToEvent("ClientSceneLoaded", "HandleClientSceneLoaded");
- SubscribeToEvent("ClientDisconnected", "HandleClientDisconnected");
- }
- if (runClient)
- {
- VariantMap identity;
- identity["UserName"] = userName;
- network.updateFps = 50; // Increase controls send rate for better responsiveness
- network.Connect(serverAddress, serverPort, gameScene, identity);
- SubscribeToEvent("PlayerSpawned", "HandlePlayerSpawned");
- SubscribeToEvent("UpdateScore", "HandleUpdateScore");
- SubscribeToEvent("UpdateHiscores", "HandleUpdateHiscores");
- SubscribeToEvent("NetworkUpdateSent", "HandleNetworkUpdateSent");
- SubscribeToEvent("ParticleEffect", "HandleParticleEffect");
- }
- }
- void InitTouchInput()
- {
- touchEnabled = true;
- moveButton = BorderImage();
- moveButton.texture = cache.GetResource("Texture2D", "Textures/TouchInput.png");
- moveButton.imageRect = IntRect(0, 0, 96, 96);
- moveButton.SetAlignment(HA_LEFT, VA_BOTTOM);
- moveButton.SetPosition(touchButtonBorder, -touchButtonBorder);
- moveButton.SetSize(touchButtonSize, touchButtonSize);
- ui.root.AddChild(moveButton);
- fireButton = BorderImage();
- fireButton.texture = cache.GetResource("Texture2D", "Textures/TouchInput.png");
- fireButton.imageRect = IntRect(96, 0, 192, 96);
- fireButton.SetAlignment(HA_RIGHT, VA_BOTTOM);
- fireButton.SetPosition(-touchButtonBorder, -touchButtonBorder);
- fireButton.SetSize(touchButtonSize, touchButtonSize);
- ui.root.AddChild(fireButton);
- }
- void CreateCamera()
- {
- // Note: the camera is not in the scene
- gameCameraNode = Node();
- gameCameraNode.position = Vector3(0, 2, -10);
- gameCamera = gameCameraNode.CreateComponent("Camera");
- gameCamera.nearClip = 0.5;
- gameCamera.farClip = 160;
- if (!engine.headless)
- {
- renderer.viewports[0] = Viewport(gameScene, gameCamera);
- audio.listener = gameCameraNode.CreateComponent("SoundListener");
- }
- }
- void CreateOverlays()
- {
- if (engine.headless || runServer)
- return;
- int height = graphics.height / 22;
- if (height > 64)
- height = 64;
- sight = BorderImage();
- sight.texture = cache.GetResource("Texture2D", "Textures/Sight.png");
- sight.SetAlignment(HA_CENTER, VA_CENTER);
- sight.SetSize(height, height);
- ui.root.AddChild(sight);
- Font@ font = cache.GetResource("Font", "Fonts/BlueHighway.ttf");
- scoreText = Text();
- scoreText.SetFont(font, 17);
- scoreText.SetAlignment(HA_LEFT, VA_TOP);
- scoreText.SetPosition(5, 5);
- scoreText.colors[C_BOTTOMLEFT] = Color(1, 1, 0.25);
- scoreText.colors[C_BOTTOMRIGHT] = Color(1, 1, 0.25);
- ui.root.AddChild(scoreText);
- @hiscoreText = Text();
- hiscoreText.SetFont(font, 17);
- hiscoreText.SetAlignment(HA_RIGHT, VA_TOP);
- hiscoreText.SetPosition(-5, 5);
- hiscoreText.colors[C_BOTTOMLEFT] = Color(1, 1, 0.25);
- hiscoreText.colors[C_BOTTOMRIGHT] = Color(1, 1, 0.25);
- ui.root.AddChild(hiscoreText);
- @messageText = Text();
- messageText.SetFont(font, 17);
- messageText.SetAlignment(HA_CENTER, VA_CENTER);
- messageText.SetPosition(0, -height * 2);
- messageText.color = Color(1, 0, 0);
- ui.root.AddChild(messageText);
- BorderImage@ healthBorder = BorderImage();
- healthBorder.texture = cache.GetResource("Texture2D", "Textures/HealthBarBorder.png");
- healthBorder.SetAlignment(HA_CENTER, VA_TOP);
- healthBorder.SetPosition(0, 8);
- healthBorder.SetSize(120, 20);
- ui.root.AddChild(healthBorder);
- healthBar = BorderImage();
- healthBar.texture = cache.GetResource("Texture2D", "Textures/HealthBarInside.png");
- healthBar.SetPosition(2, 2);
- healthBar.SetSize(116, 16);
- healthBorder.AddChild(healthBar);
- if (GetPlatform() == "Android" || GetPlatform() == "iOS")
- InitTouchInput();
- }
- void SetMessage(const String&in message)
- {
- if (messageText !is null)
- messageText.text = message;
- }
- void StartGame(Connection@ connection)
- {
- // Clear the scene of all existing scripted objects
- {
- Array<Node@> scriptedNodes = gameScene.GetChildrenWithScript(true);
- for (uint i = 0; i < scriptedNodes.length; ++i)
- scriptedNodes[i].Remove();
- }
- players.Clear();
- SpawnPlayer(connection);
- ResetAI();
- gameOn = true;
- maxEnemies = initialMaxEnemies;
- 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, 0.97, 0);
- else
- spawnPosition = Vector3(Random(spawnAreaSize) - spawnAreaSize * 0.5, 0.97, Random(spawnAreaSize) - spawnAreaSize);
- Node@ playerNode = SpawnObject(spawnPosition, Quaternion(), "Ninja");
- // Set owner connection. Owned nodes are always updated to the owner at full frequency
- playerNode.owner = connection;
- playerNode.name = "Player";
- // Initialize variables
- Ninja@ playerNinja = cast<Ninja>(playerNode.scriptObject);
- playerNinja.health = playerNinja.maxHealth = playerHealth;
- playerNinja.side = SIDE_PLAYER;
- // Make sure the player can not shoot on first frame by holding the button down
- if (connection is null)
- playerNinja.controls = playerNinja.prevControls = playerControls;
- else
- playerNinja.controls = playerNinja.prevControls = connection.controls;
- // Check if player entry already exists
- int playerIndex = -1;
- for (uint i = 0; i < players.length; ++i)
- {
- if (players[i].connection is connection)
- {
- playerIndex = i;
- break;
- }
- }
- // Does not exist, create new
- if (playerIndex < 0)
- {
- playerIndex = players.length;
- players.Resize(players.length + 1);
- players[playerIndex].connection = connection;
- if (connection !is null)
- {
- 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.id;
- players[playerIndex].score = 0;
- if (connection !is null)
- {
- // 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.id;
- connection.SendRemoteEvent("PlayerSpawned", true, eventData);
- // Create name tag (Text3D component) for players in multiplayer
- Node@ textNode = playerNode.CreateChild("NameTag");
- textNode.position = Vector3(0, 1.2, 0);
- Text3D@ text3D = textNode.CreateComponent("Text3D");
- Font@ font = cache.GetResource("Font", "Fonts/BlueHighway.ttf");
- text3D.SetFont(font, 24);
- text3D.color = Color(1, 1, 0);
- text3D.text = players[playerIndex].name;
- text3D.horizontalAlignment = HA_CENTER;
- text3D.verticalAlignment = VA_CENTER;
- text3D.faceCamera = true;
- }
- }
- void HandleUpdate(StringHash eventType, VariantMap& eventData)
- {
- float timeStep = eventData["TimeStep"].GetFloat();
-
- UpdateControls();
- CheckEndAndRestart();
- if (engine.headless)
- {
- String command = GetConsoleInput();
- if (command.length > 0)
- script.Execute(command);
- }
- else
- {
- if (debugHud.mode != DEBUGHUD_SHOW_NONE)
- {
- Node@ playerNode = FindOwnNode();
- if (playerNode !is null)
- {
- debugHud.SetAppStats("Player Pos", playerNode.worldPosition.ToString());
- debugHud.SetAppStats("Player Yaw", Variant(playerNode.worldRotation.yaw));
- }
- 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()
- {
- UpdateCamera();
- UpdateStatus();
- }
- void HandlePostRenderUpdate()
- {
- if (engine.headless)
- return;
- if (drawDebug)
- gameScene.physicsWorld.DrawDebugGeometry(true);
- if (drawOctreeDebug)
- gameScene.octree.DrawDebugGeometry(true);
- }
- void HandleTouchBegin(StringHash eventType, VariantMap& eventData)
- {
- // On some platforms like Windows the presence of touch input can only be detected dynamically
- if (!touchEnabled)
- InitTouchInput();
- int touchID = eventData["TouchID"].GetInt();
- IntVector2 pos(eventData["X"].GetInt(), eventData["Y"].GetInt());
- UIElement@ element = ui.GetElementAt(pos, false);
- if (element is moveButton)
- moveTouchID = touchID;
- else if (element is fireButton)
- fireTouchID = touchID;
- else
- rotateTouchID = touchID;
- }
- void HandleTouchEnd(StringHash eventType, VariantMap& eventData)
- {
- int touchID = eventData["TouchID"].GetInt();
- if (touchID == moveTouchID)
- moveTouchID = -1;
- if (touchID == rotateTouchID)
- rotateTouchID = -1;
- if (touchID == fireTouchID)
- fireTouchID = -1;
- }
- void HandleKeyDown(StringHash eventType, VariantMap& eventData)
- {
- int key = eventData["Key"].GetInt();
- if (key == KEY_ESC)
- {
- if (!console.visible)
- engine.Exit();
- else
- console.visible = false;
- }
- if (key == KEY_F1)
- console.Toggle();
- if (key == KEY_F2)
- debugHud.ToggleAll();
- if (key == KEY_F3)
- drawDebug = !drawDebug;
- if (key == KEY_F4)
- drawOctreeDebug = !drawOctreeDebug;
- // Allow pause only in singleplayer
- if (key == 'P' && singlePlayer && !console.visible && gameOn)
- {
- gameScene.updateEnabled = !gameScene.updateEnabled;
- if (!gameScene.updateEnabled)
- SetMessage("PAUSED");
- else
- SetMessage("");
- }
- }
- void HandlePoints(StringHash eventType, VariantMap& eventData)
- {
- if (eventData["DamageSide"].GetInt() == SIDE_PLAYER)
- {
- // Get node ID of the object that should receive points -> use it to find player index
- int playerIndex = FindPlayerIndex(eventData["Receiver"].GetInt());
- if (playerIndex >= 0)
- {
- players[playerIndex].score += eventData["Points"].GetInt();
- SendScore(playerIndex);
- bool newHiscore = CheckHiscore(playerIndex);
- if (newHiscore)
- SendHiscores(-1);
- }
- }
- }
- void HandleKill(StringHash eventType, VariantMap& eventData)
- {
- if (eventData["DamageSide"].GetInt() == SIDE_PLAYER)
- {
- MakeAIHarder();
- // Increment amount of simultaneous enemies after enough kills
- incrementCounter++;
- if (incrementCounter >= incrementEach)
- {
- incrementCounter = 0;
- if (maxEnemies < finalMaxEnemies)
- maxEnemies++;
- }
- }
- }
- void HandleClientIdentity(StringHash eventType, VariantMap& eventData)
- {
- Connection@ connection = GetEventSender();
- // If user has empty name, invent one
- if (connection.identity["UserName"].GetString().Trimmed().empty)
- connection.identity["UserName"] = "user" + RandomInt(1000);
- // Assign scene to begin replicating it to the client
- connection.scene = 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 = GetEventSender();
- if (players.empty)
- StartGame(connection);
- else
- SpawnPlayer(connection);
- }
- void HandleClientDisconnected(StringHash eventType, VariantMap& eventData)
- {
- Connection@ connection = GetEventSender();
- // Erase the player entry, and make the player's ninja commit seppuku (if exists)
- for (uint i = 0; i < players.length; ++i)
- {
- if (players[i].connection is connection)
- {
- players[i].connection = null;
- Node@ playerNode = FindPlayerNode(i);
- if (playerNode !is null)
- {
- Ninja@ playerNinja = cast<Ninja>(playerNode.scriptObject);
- 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"].GetInt();
- gameOn = true;
- SetMessage("");
- // Copy initial yaw from the player node (we should have it replicated now)
- Node@ playerNode = FindOwnNode();
- if (playerNode !is null)
- {
- playerControls.yaw = playerNode.rotation.yaw;
- playerControls.pitch = 0;
- // Disable the nametag from own character
- Node@ nameTag = playerNode.GetChild("NameTag");
- nameTag.enabled = false;
- }
- }
- void HandleUpdateScore(StringHash eventType, VariantMap& eventData)
- {
- clientScore = eventData["Score"].GetInt();
- scoreText.text = "Score " + clientScore;
- }
- void HandleUpdateHiscores(StringHash eventType, VariantMap& eventData)
- {
- VectorBuffer data = eventData["Hiscores"].GetBuffer();
- hiscores.Resize(data.ReadVLE());
- for (uint i = 0; i < hiscores.length; ++i)
- {
- hiscores[i].name = data.ReadString();
- hiscores[i].score = data.ReadInt();
- }
- String allHiscores;
- for (uint i = 0; i < hiscores.length; ++i)
- allHiscores += hiscores[i].name + " " + hiscores[i].score + "\n";
- hiscoreText.text = allHiscores;
- }
- void HandleNetworkUpdateSent()
- {
- // Clear accumulated buttons from the network controls
- if (network.serverConnection !is null)
- network.serverConnection.controls.Set(CTRL_ALL, false);
- }
- void HandleParticleEffect(StringHash eventType, VariantMap& eventData)
- {
- Vector3 position = eventData["Position"].GetVector3();
- String effectName = eventData["EffectName"].GetString();
- float duration = eventData["Duration"].GetFloat();
- SpawnParticleEffect(position, effectName, duration, LOCAL);
- }
- int FindPlayerIndex(uint nodeID)
- {
- for (uint i = 0; i < players.length; ++i)
- {
- if (players[i].nodeID == nodeID)
- return i;
- }
- return -1;
- }
- Node@ FindPlayerNode(int playerIndex)
- {
- if (playerIndex >= 0 && playerIndex < int(players.length))
- return gameScene.GetNode(players[playerIndex].nodeID);
- else
- return null;
- }
- Node@ FindOwnNode()
- {
- if (singlePlayer)
- return gameScene.GetChild("Player", true);
- else
- return gameScene.GetNode(clientNodeID);
- }
- bool CheckHiscore(int playerIndex)
- {
- for (uint i = 0; i < hiscores.length; ++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 (int i = 1; i < int(hiscores.length); ++i)
- {
- HiscoreEntry temp = hiscores[i];
- int j = i;
- while (j > 0 && temp.score > hiscores[j - 1].score)
- {
- hiscores[j] = hiscores[j - 1];
- --j;
- }
- hiscores[j] = temp;
- }
- }
- void SendScore(int playerIndex)
- {
- if (!runServer || playerIndex < 0 || playerIndex >= int(players.length))
- 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.length);
- for (uint i = 0; i < hiscores.length; ++i)
- {
- data.WriteString(hiscores[i].name);
- data.WriteInt(hiscores[i].score);
- }
- VariantMap eventData;
- eventData["Hiscores"] = data;
- if (playerIndex >= 0 && playerIndex < int(players.length))
- players[playerIndex].connection.SendRemoteEvent("UpdateHiscores", true, eventData);
- else
- network.BroadcastRemoteEvent(gameScene, "UpdateHiscores", true, eventData); // Send to all in scene
- }
- Node@ SpawnObject(const Vector3&in position, const Quaternion&in rotation, const String&in className)
- {
- XMLFile@ xml = cache.GetResource("XMLFile", "Objects/" + className + ".xml");
- return scene.InstantiateXML(xml, position, rotation);
- }
- Node@ SpawnParticleEffect(const Vector3&in position, const String&in effectName, float duration, CreateMode mode = REPLICATED)
- {
- if (runServer && mode == REPLICATED)
- {
- VariantMap eventData;
- eventData["Position"] = position;
- eventData["EffectName"] = effectName;
- eventData["Duration"] = duration;
- network.BroadcastRemoteEvent(gameScene, "ParticleEffect", false, eventData);
- }
- Node@ newNode = scene.CreateChild("Effect", LOCAL);
- newNode.position = position;
- // Create the particle emitter
- ParticleEmitter@ emitter = newNode.CreateComponent("ParticleEmitter");
- emitter.Load(cache.GetResource("XMLFile", effectName));
- // Create a GameObject for managing the effect lifetime
- GameObject@ object = cast<GameObject>(newNode.CreateScriptObject(scriptFile, "GameObject", LOCAL));
- object.duration = duration;
- return newNode;
- }
- Node@ SpawnSound(const Vector3&in position, const String&in soundName, float duration)
- {
- Node@ newNode = scene.CreateChild();
- newNode.position = position;
- // Create the sound source
- SoundSource3D@ source = newNode.CreateComponent("SoundSource3D");
- Sound@ sound = cache.GetResource("Sound", soundName);
- source.SetDistanceAttenuation(200, 5000, 1);
- source.Play(sound);
- // Create a GameObject for managing the sound lifetime
- GameObject@ object = cast<GameObject>(newNode.CreateScriptObject(scriptFile, "GameObject", LOCAL));
- object.duration = duration;
- return newNode;
- }
- void SpawnObjects(float timeStep)
- {
- // If game not running, run only the random generator
- if (!gameOn)
- {
- Random();
- return;
- }
- // Spawn powerups
- powerupSpawnTimer += timeStep;
- if (powerupSpawnTimer >= powerupSpawnRate)
- {
- powerupSpawnTimer = 0;
- int numPowerups = gameScene.GetChildrenWithScript("SnowCrate", true).length + gameScene.GetChildrenWithScript("Potion", true).length;
- if (numPowerups < maxPowerups)
- {
- const float maxOffset = 40;
- float xOffset = Random(maxOffset * 2.0) - maxOffset;
- float zOffset = Random(maxOffset * 2.0) - maxOffset;
- SpawnObject(Vector3(xOffset, 50, zOffset), Quaternion(), "SnowCrate");
- }
- }
- // Spawn enemies
- enemySpawnTimer += timeStep;
- if (enemySpawnTimer > enemySpawnRate)
- {
- enemySpawnTimer = 0;
- int numEnemies = 0;
- Array<Node@> ninjaNodes = gameScene.GetChildrenWithScript("Ninja", true);
- for (uint i = 0; i < ninjaNodes.length; ++i)
- {
- Ninja@ ninja = cast<Ninja>(ninjaNodes[i].scriptObject);
- if (ninja.side == SIDE_ENEMY)
- ++numEnemies;
- }
- if (numEnemies < maxEnemies)
- {
- const float maxOffset = 40;
- float offset = Random(maxOffset * 2.0) - maxOffset;
- // Random north/east/south/west direction
- int dir = RandomInt() & 3;
- dir *= 90;
- Quaternion rotation(0, dir, 0);
- Node@ enemyNode = SpawnObject(rotation * Vector3(offset, 10, -120), rotation, "Ninja");
- // Initialize variables
- Ninja@ enemyNinja = cast<Ninja>(enemyNode.scriptObject);
- enemyNinja.side = SIDE_ENEMY;
- @enemyNinja.controller = AIController();
- RigidBody@ enemyBody = enemyNode.GetComponent("RigidBody");
- enemyBody.linearVelocity = rotation * Vector3(0, 10, 30);
- }
- }
- }
- 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 is null)
- {
- 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(null);
- }
- void UpdateControls()
- {
- if (singlePlayer || runClient)
- {
- prevPlayerControls = playerControls;
- playerControls.Set(CTRL_ALL, false);
- if (touchEnabled)
- {
- for (uint i = 0; i < input.numTouches; ++i)
- {
- TouchState@ touch = input.touches[i];
- if (touch.touchID == rotateTouchID)
- {
- playerControls.yaw += touchSensitivity * gameCamera.fov / graphics.height * touch.delta.x;
- playerControls.pitch += touchSensitivity * gameCamera.fov / graphics.height * touch.delta.y;
- }
- if (touch.touchID == moveTouchID)
- {
- int relX = touch.position.x - moveButton.screenPosition.x - touchButtonSize / 2;
- int relY = touch.position.y - moveButton.screenPosition.y - touchButtonSize / 2;
- if (relY < 0 && Abs(relX * 3 / 2) < Abs(relY))
- playerControls.Set(CTRL_UP, true);
- if (relY > 0 && Abs(relX * 3 / 2) < Abs(relY))
- playerControls.Set(CTRL_DOWN, true);
- if (relX < 0 && Abs(relY * 3 / 2) < Abs(relX))
- playerControls.Set(CTRL_LEFT, true);
- if (relX > 0 && Abs(relY * 3 / 2) < Abs(relX))
- playerControls.Set(CTRL_RIGHT, true);
- }
- }
- if (fireTouchID >= 0)
- playerControls.Set(CTRL_FIRE, true);
- }
- if (input.numJoysticks > 0)
- {
- JoystickState@ joystick = input.joysticks[0];
- if (joystick.numButtons > 0)
- {
- if (joystick.buttonDown[0])
- playerControls.Set(CTRL_JUMP, true);
- if (joystick.buttonDown[1])
- playerControls.Set(CTRL_FIRE, true);
- if (joystick.numButtons >= 6)
- {
- if (joystick.buttonDown[4])
- playerControls.Set(CTRL_JUMP, true);
- if (joystick.buttonDown[5])
- playerControls.Set(CTRL_FIRE, true);
- }
- if (joystick.numHats > 0)
- {
- if (joystick.hatPosition[0] & HAT_LEFT != 0)
- playerControls.Set(CTRL_LEFT, true);
- if (joystick.hatPosition[0] & HAT_RIGHT != 0)
- playerControls.Set(CTRL_RIGHT, true);
- if (joystick.hatPosition[0] & HAT_UP != 0)
- playerControls.Set(CTRL_UP, true);
- if (joystick.hatPosition[0] & HAT_DOWN != 0)
- playerControls.Set(CTRL_DOWN, true);
- }
- if (joystick.numAxes >= 2)
- {
- if (joystick.axisPosition[0] < -joyMoveDeadZone)
- playerControls.Set(CTRL_LEFT, true);
- if (joystick.axisPosition[0] > joyMoveDeadZone)
- playerControls.Set(CTRL_RIGHT, true);
- if (joystick.axisPosition[1] < -joyMoveDeadZone)
- playerControls.Set(CTRL_UP, true);
- if (joystick.axisPosition[1] > joyMoveDeadZone)
- playerControls.Set(CTRL_DOWN, true);
- }
- if (joystick.numAxes >= 4)
- {
- float lookX = joystick.axisPosition[joystick.numAxes - 2];
- float lookY = joystick.axisPosition[joystick.numAxes - 1];
- if (lookX < -joyLookDeadZone)
- playerControls.yaw -= joySensitivity * lookX * lookX;
- if (lookX > joyLookDeadZone)
- playerControls.yaw += joySensitivity * lookX * lookX;
- if (lookY < -joyLookDeadZone)
- playerControls.pitch -= joySensitivity * lookY * lookY;
- if (lookY > joyLookDeadZone)
- playerControls.pitch += joySensitivity * 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 is null) || (!console.visible))
- {
- if (input.keyDown['W'])
- playerControls.Set(CTRL_UP, true);
- if (input.keyDown['S'])
- playerControls.Set(CTRL_DOWN, true);
- if (input.keyDown['A'])
- playerControls.Set(CTRL_LEFT, true);
- if (input.keyDown['D'])
- playerControls.Set(CTRL_RIGHT, true);
- if (input.keyDown[KEY_LCTRL] || input.keyPress[KEY_LCTRL])
- playerControls.Set(CTRL_FIRE, true);
- if (input.keyDown[' '] || input.keyPress[' '])
- playerControls.Set(CTRL_JUMP, true);
- }
- if (input.mouseButtonDown[MOUSEB_LEFT] || input.mouseButtonPress[MOUSEB_LEFT])
- playerControls.Set(CTRL_FIRE, true);
- if (input.mouseButtonDown[MOUSEB_RIGHT] || input.mouseButtonPress[MOUSEB_RIGHT])
- playerControls.Set(CTRL_JUMP, true);
- playerControls.yaw += mouseSensitivity * input.mouseMoveX;
- playerControls.pitch += mouseSensitivity * input.mouseMoveY;
- playerControls.pitch = Clamp(playerControls.pitch, -60.0, 60.0);
- // 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 !is null)
- {
- Ninja@ playerNinja = cast<Ninja>(playerNode.scriptObject);
- playerNinja.controls = playerControls;
- }
- }
- else if (network.serverConnection !is null)
- {
- // Set the latest yaw & pitch to server controls, and accumulate the buttons so that we do not miss any presses
- network.serverConnection.controls.yaw = playerControls.yaw;
- network.serverConnection.controls.pitch = playerControls.pitch;
- network.serverConnection.controls.buttons |= playerControls.buttons;
- // Tell the camera position to server for interest management
- network.serverConnection.position = gameCameraNode.worldPosition;
- }
- }
- if (runServer)
- {
- // Apply each connection's controls to the ninja they control
- for (uint i = 0; i < players.length; ++i)
- {
- Node@ playerNode = FindPlayerNode(i);
- if (playerNode !is null)
- {
- Ninja@ playerNinja = cast<Ninja>(playerNode.scriptObject);
- 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 (engine.headless)
- return;
- // On the server, use a simple freelook camera
- if (runServer)
- {
- UpdateFreelookCamera();
- return;
- }
- Node@ playerNode = FindOwnNode();
- if (playerNode is null)
- return;
- Vector3 pos = playerNode.position;
- Quaternion dir;
- // Make controls seem more immediate by forcing the current mouse yaw to player ninja's Y-axis rotation
- if (playerNode.vars["Health"].GetInt() > 0)
- playerNode.rotation = Quaternion(0, playerControls.yaw, 0);
- dir = dir * Quaternion(playerNode.rotation.yaw, Vector3(0, 1, 0));
- dir = dir * Quaternion(playerControls.pitch, Vector3(1, 0, 0));
- Vector3 aimPoint = pos + Vector3(0, 1, 0);
- Vector3 minDist = aimPoint + dir * Vector3(0, 0, -cameraMinDist);
- Vector3 maxDist = aimPoint + dir * Vector3(0, 0, -cameraMaxDist);
- // Collide camera ray with static objects (collision mask 2)
- Vector3 rayDir = (maxDist - minDist).Normalized();
- float rayDistance = cameraMaxDist - cameraMinDist + cameraSafetyDist;
- PhysicsRaycastResult result = gameScene.physicsWorld.RaycastSingle(Ray(minDist, rayDir), rayDistance, 2);
- if (result.body !is null)
- rayDistance = Min(rayDistance, result.distance - cameraSafetyDist);
- gameCameraNode.position = minDist + rayDir * rayDistance;
- gameCameraNode.rotation = dir;
- }
- void UpdateFreelookCamera()
- {
- float timeStep = time.timeStep;
- float speedMultiplier = 1.0;
- if (input.keyDown[KEY_LSHIFT])
- speedMultiplier = 5.0;
- if (input.keyDown[KEY_LCTRL])
- speedMultiplier = 0.1;
- if (input.keyDown['W'])
- gameCameraNode.TranslateRelative(Vector3(0, 0, 10) * timeStep * speedMultiplier);
- if (input.keyDown['S'])
- gameCameraNode.TranslateRelative(Vector3(0, 0, -10) * timeStep * speedMultiplier);
- if (input.keyDown['A'])
- gameCameraNode.TranslateRelative(Vector3(-10, 0, 0) * timeStep * speedMultiplier);
- if (input.keyDown['D'])
- gameCameraNode.TranslateRelative(Vector3(10, 0, 0) * timeStep * speedMultiplier);
- playerControls.yaw += mouseSensitivity * input.mouseMoveX;
- playerControls.pitch += mouseSensitivity * input.mouseMoveY;
- playerControls.pitch = Clamp(playerControls.pitch, -90.0, 90.0);
- gameCameraNode.rotation = Quaternion(playerControls.pitch, playerControls.yaw, 0);
- }
- void UpdateStatus()
- {
- if (engine.headless || runServer)
- return;
- if (singlePlayer)
- {
- if (players.length > 0)
- scoreText.text = "Score " + players[0].score;
- if (hiscores.length > 0)
- hiscoreText.text = "Hiscore " + hiscores[0].score;
- }
- Node@ playerNode = FindOwnNode();
- if (playerNode !is null)
- {
- int health = 0;
- if (singlePlayer)
- {
- GameObject@ object = cast<GameObject>(playerNode.scriptObject);
- health = object.health;
- }
- else
- {
- // In multiplayer the client does not have script logic components, but health is replicated via node user variables
- health = playerNode.vars["Health"].GetInt();
- }
- healthBar.width = 116 * health / playerHealth;
- }
- }
- void HandleScreenMode()
- {
- int height = graphics.height / 22;
- if (height > 64)
- height = 64;
- sight.SetSize(height, height);
- messageText.SetPosition(0, -height * 2);
- }
|