8061 lines
288 KiB
C++
8061 lines
288 KiB
C++
#define URHO3D_MAIN_HOOK() \
|
|
do \
|
|
{ \
|
|
} \
|
|
while(0)
|
|
|
|
#include <boost/algorithm/string.hpp>
|
|
#include <boost/filesystem.hpp>
|
|
#include <Urho3D/Core/CoreEvents.h>
|
|
#include <Urho3D/Core/Thread.h>
|
|
#include <Urho3D/Scene/Node.h>
|
|
#include <Urho3D/Scene/Scene.h>
|
|
#include <Urho3D/Scene/SceneEvents.h>
|
|
#include <Urho3D/Input/Input.h>
|
|
#include <Urho3D/IO/FileSystem.h>
|
|
#include <Urho3D/IO/File.h>
|
|
#include <Urho3D/IO/FileWatcher.h>
|
|
#include <Urho3D/Graphics/Graphics.h>
|
|
#include <Urho3D/Graphics/Viewport.h>
|
|
#include <Urho3D/Graphics/Camera.h>
|
|
#include <Urho3D/Graphics/Octree.h>
|
|
#include <Urho3D/Graphics/OctreeQuery.h>
|
|
#include <Urho3D/Graphics/RenderPath.h>
|
|
#include <Urho3D/Graphics/Renderer.h>
|
|
#include <Urho3D/Graphics/DebugRenderer.h>
|
|
#include <Urho3D/Graphics/Zone.h>
|
|
#include <Urho3D/Graphics/Skybox.h>
|
|
#include <Urho3D/Graphics/Texture2D.h>
|
|
#include <Urho3D/UI/Sprite.h>
|
|
#include <Urho3D/Engine/EngineDefs.h>
|
|
#include <Urho3D/Engine/Application.h>
|
|
#include <Urho3D/Engine/DebugHud.h>
|
|
#include <Urho3D/Engine/EngineEvents.h>
|
|
#include <Urho3D/UI/UIEvents.h>
|
|
#include <tb_node_tree.h>
|
|
#include <tb_widgets_reader.h>
|
|
#include <tb/tb_font_renderer.h>
|
|
#include "ProcSky.h"
|
|
#include "string_tools.h"
|
|
#include "file_io_utils.h"
|
|
#include "cryptonote_basic/cryptonote_format_utils.h"
|
|
#include "citymesh-urho3d.h"
|
|
#include "ui-urho3d.h"
|
|
#include "ui-console.h"
|
|
#include "ui-new-account.h"
|
|
#include "game-state.h"
|
|
#include "misc_log_ex.h"
|
|
#include "common/util.h"
|
|
#include "cc/cc.h"
|
|
#include "cc/cc_badge.h"
|
|
#include "cc/cc_special_events.h"
|
|
#include "cc/cc_palette.h"
|
|
#include "cc/cc_invitation.h"
|
|
#include "cc/cc_script.h"
|
|
#include "cc/cc_runestone.h"
|
|
#include "cc/cc_discoveries.h"
|
|
#include "cc/cc_influence.h"
|
|
#include "selection.h"
|
|
#include "game-util.h"
|
|
#include "game-wallet.h"
|
|
#include "undo.h"
|
|
#include "magica.h"
|
|
#include "material-style.h"
|
|
#include "gif.h"
|
|
#include "camera-controller.h"
|
|
#include "free-camera.h"
|
|
#include "walker-camera.h"
|
|
#include "orbit-camera.h"
|
|
#include "cloud-cover.h"
|
|
#include "horizon.h"
|
|
#include "controls.h"
|
|
#include "audio.h"
|
|
#include "block_model.h"
|
|
#include "custom-model.h"
|
|
#include "boid.h"
|
|
#include "UTBRendererBatcher.h"
|
|
#include "ui-file-selector.h"
|
|
#include "ui-tb-message-box.h"
|
|
#include "ui-tb-window.h"
|
|
#include "ui-tooltip.h"
|
|
#include "ui-wallet-info.h"
|
|
#include "tutorial.h"
|
|
#include "daemon-controller.h"
|
|
|
|
#define USE_PROC_SKY
|
|
|
|
using namespace Urho3D;
|
|
using namespace tb;
|
|
|
|
const float TOUCH_SENSITIVITY = 2.0f;
|
|
|
|
#define CLOUDS_LIGHT_DISTANCE 12000.0f
|
|
|
|
#define START_POSITION Vector3(0.01f, 10.0f, -100.01f)
|
|
#define START_ROTATION Quaternion(0.0f, 0.0f, 0.0f)
|
|
|
|
#define BOID_BASE_MIN_HEIGHT 1100.0f
|
|
#define BOID_BASE_MAX_HEIGHT 1350.0f
|
|
#define BOID_ORIGIN_DISTANCE 2500.0f
|
|
#define BOID_DESTINATION_DISTANCE 3500.0f
|
|
|
|
#define CONFIG_GRAPHICS_SECTION "graphics"
|
|
#define CONFIG_CONTROLS_SECTION "controls"
|
|
#define CONFIG_AUDIO_SECTION "audio"
|
|
#define CONFIG_UI_SECTION "ui"
|
|
#define CONFIG_NODE_SECTION "node"
|
|
#define CONFIG_TUTORIAL_SECTION "tutorial"
|
|
#define CONFIG_TUTORIAL_STATE_SECTION "tutorial-state"
|
|
|
|
#define CONFIG_SHADOWS "shadows"
|
|
#define DEFAULT_SHADOWS TBIDC("shadows-pcf-24bit")
|
|
#define CONFIG_SHADOW_MAP_SIZE "shadow-map-size"
|
|
#define DEFAULT_SHADOW_MAP_SIZE TBIDC("shadowmap-2048")
|
|
#define CONFIG_CLOUDS "clouds"
|
|
#define DEFAULT_CLOUDS true
|
|
#define CONFIG_HORIZON "horizon"
|
|
#define DEFAULT_HORIZON false
|
|
#define CONFIG_DYNAMIC_SKY "dynamic-sky"
|
|
#define DEFAULT_DYNAMIC_SKY true
|
|
#define CONFIG_TIME_OF_DAY "time-of-day"
|
|
#define DEFAULT_TIME_OF_DAY 1.48f
|
|
#define CONFIG_SHOW_FLAGS "show-flags"
|
|
#define DEFAULT_SHOW_FLAGS true
|
|
#define CONFIG_GRAPHICS_MODE "graphics-mode"
|
|
#define DEFAULT_GRAPHICS_MODE String("")
|
|
#define CONFIG_MAX_ACTIVE_FPS "max-active-fps"
|
|
#define DEFAULT_MAX_ACTIVE_FPS 30
|
|
#define CONFIG_MAX_INACTIVE_FPS "max-inactive-fps"
|
|
#define DEFAULT_MAX_INACTIVE_FPS 5
|
|
#define CONFIG_FONT_SIZE "font-size"
|
|
#define DEFAULT_FONT_SIZE 14
|
|
#define CONFIG_VIEW_DISTANCE "view-distance"
|
|
#define DEFAULT_VIEW_DISTANCE 4
|
|
#define CONFIG_DEBUG "debug"
|
|
#define CONFIG_INVERT_MOUSE_Y "invert-mouse-y"
|
|
#define CONFIG_MOUSE_SENSITIVITY "mouse-sensitivity"
|
|
#define CONFIG_TURN_RATE "turn-rate"
|
|
#define CONFIG_MUSIC_TRACK "music-track"
|
|
#define DEFAULT_MUSIC_TRACK String("music-track-random-no-chiptunes")
|
|
#define CONFIG_MUSIC_VOLUME "music-volume"
|
|
#define DEFAULT_MUSIC_VOLUME 0.4f
|
|
#define CONFIG_PLAY_SFX_ON_NEW_BLOCK "play-sfx-on-new-block"
|
|
#define DEFAULT_PLAY_SFX_ON_NEW_BLOCK true
|
|
#define CONFIG_SFX_VOLUME "sfx-volume"
|
|
#define DEFAULT_SFX_VOLUME 0.5f
|
|
#define CONFIG_INCLUDE_UNMATCHED_TRADES_IN_QUEUED_COMMANDS "include-unmatched-trades"
|
|
#define DEFAULT_INCLUDE_UNMATCHED_TRADES_IN_QUEUED_COMMANDS false
|
|
#define CONFIG_COMPASS "compass"
|
|
#define DEFAULT_COMPASS TBIDC("compass-medium")
|
|
#define CONFIG_CALENDAR "calendar"
|
|
#define DEFAULT_CALENDAR TBIDC("calendar-medium")
|
|
#define CONFIG_NOTIFICATION_LIFTIME "notification-lifetime"
|
|
#define DEFAULT_NOTIFICATION_LIFTIME 5
|
|
#define CONFIG_THERMOMETER "thermometer"
|
|
#define DEFAULT_THERMOMETER TBIDC("thermometer-medium")
|
|
#define CONFIG_PANEL "panel"
|
|
#define DEFAULT_PANEL String("")
|
|
#define CONFIG_CONTROL_DAEMON "control-daemon"
|
|
#define DEFAULT_CONTROL_DAEMON true
|
|
#define CONFIG_DAEMON_DATA_DIR "daemon-data-dir"
|
|
#define DEFAULT_DAEMON_DATA_DIR String("")
|
|
#define CONFIG_DAEMON_LOG "daemon-log"
|
|
#define DEFAULT_DAEMON_LOG String("")
|
|
#define CONFIG_ENABLE_TUTORIAL "enable-tutorial"
|
|
#define DEFAULT_ENABLE_TUTORIAL true
|
|
|
|
enum SelectionMode
|
|
{
|
|
SM_NORMAL,
|
|
SM_UNION,
|
|
SM_DIFFERENCE,
|
|
};
|
|
|
|
class WalletRefresher : public RefCounted, public Thread
|
|
{
|
|
public:
|
|
WalletRefresher() {}
|
|
|
|
void SetWallet(const std::shared_ptr<GameWallet> &w)
|
|
{
|
|
if (wallet)
|
|
Stop();
|
|
wallet = w;
|
|
if (wallet)
|
|
Run();
|
|
}
|
|
|
|
void ThreadFunction() override
|
|
{
|
|
counter = 100000;
|
|
save = false;
|
|
while (shouldRun_)
|
|
{
|
|
Time::Sleep(100);
|
|
if (counter++ < 50)
|
|
continue;
|
|
try { wallet->refresh(); }
|
|
catch (const std::exception &e) {}
|
|
counter = 0;
|
|
if (save)
|
|
{
|
|
try { wallet->save(); }
|
|
catch (const std::exception &e) {}
|
|
save = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void RequestRefresh() { counter = 100000; }
|
|
void RequestSave() { save = true; }
|
|
|
|
private:
|
|
std::shared_ptr<GameWallet> wallet;
|
|
std::atomic<int> counter;
|
|
std::atomic<bool> save;
|
|
};
|
|
|
|
class CryptoCityUrho3D;
|
|
struct QueuedCommand
|
|
{
|
|
virtual ~QueuedCommand() {}
|
|
virtual void execute(CryptoCityUrho3D&) = 0;
|
|
};
|
|
|
|
class CryptoCityUrho3D: public Application
|
|
{
|
|
URHO3D_OBJECT(CryptoCityUrho3D, Application);
|
|
|
|
public:
|
|
CryptoCityUrho3D(Context *ctx);
|
|
virtual ~CryptoCityUrho3D();
|
|
|
|
public:
|
|
virtual String GetScreenJoystickPatchString() const;
|
|
void HandleSceneUpdate(StringHash /*eventType*/, VariantMap& eventData);
|
|
void HandleKeyUp(StringHash eventType, VariantMap& eventData);
|
|
void HandleKeyDown(StringHash eventType, VariantMap& eventData);
|
|
void HandleUpdate(StringHash eventType, VariantMap& eventData);
|
|
void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData);
|
|
void HandleMouseModeRequest(StringHash eventType, VariantMap& eventData);
|
|
void HandleMouseModeChange(StringHash eventType, VariantMap& eventData);
|
|
void HandleMouseButtonDown(StringHash eventType, VariantMap& eventData);
|
|
void HandleMouseButtonUp(StringHash eventType, VariantMap& eventData);
|
|
void HandleMouseMotion(StringHash eventType, VariantMap& eventData);
|
|
|
|
void HandleLoadWallet(StringHash eventType, VariantMap& eventData);
|
|
void HandleDeposit(StringHash eventType, VariantMap& eventData);
|
|
void HandleWithdraw(StringHash eventType, VariantMap& eventData);
|
|
void HandleBuyLand(StringHash eventType, VariantMap& eventData);
|
|
void HandleBuildingSettings(StringHash eventType, VariantMap& eventData);
|
|
void HandleRename(StringHash eventType, VariantMap& eventData);
|
|
void HandleRepair(StringHash eventType, VariantMap& eventData);
|
|
void HandleBuyItems(StringHash eventType, VariantMap& eventData);
|
|
void HandleBuy(StringHash eventType, VariantMap& eventData);
|
|
void HandleSell(StringHash eventType, VariantMap& eventData);
|
|
void HandleAssignItems(StringHash eventType, VariantMap& eventData);
|
|
void HandleDestroyItems(StringHash eventType, VariantMap& eventData);
|
|
void HandleDefineAttribute(StringHash eventType, VariantMap& eventData);
|
|
void HandleIncreaseAttribute(StringHash eventType, VariantMap& eventData);
|
|
void HandleAddCitySpecialization(StringHash eventType, VariantMap& eventData);
|
|
void HandleDiceRoll(StringHash eventType, VariantMap& eventData);
|
|
void HandleHunt(StringHash eventType, VariantMap& eventData);
|
|
void HandleSavePlayerProfile(StringHash eventType, VariantMap& eventData);
|
|
void HandleUpdateItem(StringHash eventType, VariantMap& eventData);
|
|
void HandleAddBlock(StringHash eventType, VariantMap& eventData);
|
|
void HandleRemoveBlock(StringHash eventType, VariantMap& eventData);
|
|
void HandleRemoveHiddenBlocks(StringHash eventType, VariantMap& eventData);
|
|
void HandleDemolish(StringHash eventType, VariantMap& eventData);
|
|
void HandleDestroyFlag(StringHash eventType, VariantMap& eventData);
|
|
void HandleUndo(StringHash eventType, VariantMap& eventData);
|
|
void HandleRedo(StringHash eventType, VariantMap& eventData);
|
|
void HandleLoadModel(StringHash eventType, VariantMap& eventData);
|
|
void HandleSaveModel(StringHash eventType, VariantMap& eventData);
|
|
void HandleApproveBuild(StringHash eventType, VariantMap& eventData);
|
|
void HandleAbandonBuild(StringHash eventType, VariantMap& eventData);
|
|
void HandleExit(StringHash eventType, VariantMap& eventData);
|
|
void HandleExitRequest(StringHash eventType, VariantMap& eventData);
|
|
void HandleExitConfirmation(StringHash eventType, VariantMap& eventData);
|
|
void HandleShowFlags(StringHash eventType, VariantMap& eventData);
|
|
void HandleGetMusicTracks(StringHash eventType, VariantMap& eventData);
|
|
void HandleMaterialSelected(StringHash eventType, VariantMap& eventData);
|
|
void HandleVariantSelected(StringHash eventType, VariantMap& eventData);
|
|
void HandleWalletFileSelected(StringHash eventType, VariantMap& eventData);
|
|
void HandleEditModeChanged(StringHash eventType, VariantMap& eventData);
|
|
void HandleExtendSelection(StringHash eventType, VariantMap& eventData);
|
|
void HandleExtendSelectionMaximally(StringHash eventType, VariantMap& eventData);
|
|
void HandleMoveSelection(StringHash eventType, VariantMap& eventData);
|
|
void HandleGrowSelection(StringHash eventType, VariantMap& eventData);
|
|
void HandleSelectWholeFlag(StringHash eventType, VariantMap& eventData);
|
|
void HandleShrinkSelection(StringHash eventType, VariantMap& eventData);
|
|
void HandleSelectEdge(StringHash eventType, VariantMap& eventData);
|
|
void HandleGoToFlag(StringHash eventType, VariantMap& eventData);
|
|
void HandleGoToVista(StringHash eventType, VariantMap& eventData);
|
|
void HandleGetVista(StringHash eventType, VariantMap& eventData);
|
|
void HandleGetGameUpdateEvents(StringHash eventType, VariantMap& eventData);
|
|
void HandleRequestPlayerData(StringHash eventType, VariantMap& eventData);
|
|
void HandleRequestItemData(StringHash eventType, VariantMap& eventData);
|
|
void HandleRequestScriptData(StringHash eventType, VariantMap& eventData);
|
|
void HandleRequestBadgeData(StringHash eventType, VariantMap& eventData);
|
|
void HandleConsoleCommand(StringHash eventType, VariantMap& eventData);
|
|
void HandleConsoleSource(StringHash eventType, VariantMap& eventData);
|
|
void HandleGetConsoleFilter(StringHash eventType, VariantMap& eventData);
|
|
void HandleSetConsoleFilter(StringHash eventType, VariantMap& eventData);
|
|
void HandleChat(StringHash eventType, VariantMap& eventData);
|
|
void HandleResearch(StringHash eventType, VariantMap& eventData);
|
|
void HandleResized(StringHash eventType, VariantMap& eventData);
|
|
void HandleEnableShadows(StringHash eventType, VariantMap& eventData);
|
|
void HandleSetShadowMapSize(StringHash eventType, VariantMap& eventData);
|
|
void HandleEnableClouds(StringHash eventType, VariantMap& eventData);
|
|
void HandleSetMaxActiveFPS(StringHash eventType, VariantMap& eventData);
|
|
void HandleSetMaxInactiveFPS(StringHash eventType, VariantMap& eventData);
|
|
void HandleSetFontSize(StringHash eventType, VariantMap& eventData);
|
|
void HandleSetViewDistance(StringHash eventType, VariantMap& eventData);
|
|
void HandleGraphicsMode(StringHash eventType, VariantMap& eventData);
|
|
void HandleEnableHorizon(StringHash eventType, VariantMap& eventData);
|
|
void HandleEnableDynamicSky(StringHash eventType, VariantMap& eventData);
|
|
void HandleTimeOfDay(StringHash eventType, VariantMap& eventData);
|
|
void HandleServicePrice(StringHash eventType, VariantMap& eventData);
|
|
void HandleSow(StringHash eventType, VariantMap& eventData);
|
|
void HandleHarvest(StringHash eventType, VariantMap& eventData);
|
|
void HandleMusicTrack(StringHash eventType, VariantMap& eventData);
|
|
void HandleMusicVolume(StringHash eventType, VariantMap& eventData);
|
|
void HandleSFXVolume(StringHash eventType, VariantMap& eventData);
|
|
void HandlePlaySFXOnNewBlock(StringHash eventType, VariantMap& eventData);
|
|
void HandleMusicSkipTrack(StringHash eventType, VariantMap& eventData);
|
|
void HandleIncludeUnmatchedTradesInQueuedComands(StringHash eventType, VariantMap& eventData);
|
|
void HandleCompass(StringHash eventType, VariantMap& eventData);
|
|
void HandleCalendar(StringHash eventType, VariantMap& eventData);
|
|
void HandleThermometer(StringHash eventType, VariantMap& eventData);
|
|
void HandleNotificationLifetime(StringHash eventType, VariantMap& eventData);
|
|
void HandleMint(StringHash eventType, VariantMap& eventData);
|
|
void HandleSmelt(StringHash eventType, VariantMap& eventData);
|
|
void HandleCancelNonce(StringHash eventType, VariantMap& eventData);
|
|
void HandleNewScript(StringHash eventType, VariantMap& eventData);
|
|
void HandlePlayScript(StringHash eventType, VariantMap& eventData);
|
|
void HandleEnableScript(StringHash eventType, VariantMap& eventData);
|
|
void HandleScriptChoice(StringHash eventType, VariantMap& eventData);
|
|
void HandleSetGlobalVariable(StringHash eventType, VariantMap& eventData);
|
|
void HandleGetGlobalVariable(StringHash eventType, VariantMap& eventData);
|
|
void HandleChopWood(StringHash eventType, VariantMap& eventData);
|
|
void HandleCarveRunestone(StringHash eventType, VariantMap& eventData);
|
|
void HandleNewAuctionFlag(StringHash eventType, VariantMap& eventData);
|
|
void HandleNewAuctionItem(StringHash eventType, VariantMap& eventData);
|
|
void HandleBidOnAuction(StringHash eventType, VariantMap& eventData);
|
|
void HandleAllowStyling(StringHash eventType, VariantMap& eventData);
|
|
void HandleAudioPlayingTrack(StringHash eventType, VariantMap& eventData);
|
|
void HandleControlDaemon(StringHash eventType, VariantMap& eventData);
|
|
void HandleRestartDaemon(StringHash eventType, VariantMap& eventData);
|
|
void HandleEnableTutorial(StringHash eventType, VariantMap& eventData);
|
|
void HandleResetTutorial(StringHash eventType, VariantMap& eventData);
|
|
void HandleGetTutorials(StringHash eventType, VariantMap& eventData);
|
|
void HandleTutorialTrigger(StringHash eventType, VariantMap& eventData);
|
|
|
|
void HandleLoadingWallet(StringHash eventType, VariantMap& eventData);
|
|
void HandleNewWallet(StringHash eventType, VariantMap& eventData);
|
|
void HandleNewSnapshot(StringHash eventType, VariantMap& eventData);
|
|
void HandleNewBlock(StringHash eventType, VariantMap& eventData);
|
|
void HandleNewChatLine(StringHash eventType, VariantMap& eventData);
|
|
void HandleNewTradeCommand(StringHash eventType, VariantMap& eventData);
|
|
void HandleWalletSynced(StringHash eventType, VariantMap& eventData);
|
|
void HandleNewPoolCommand(StringHash eventType, VariantMap& eventData);
|
|
void HandlePoolCommandGone(StringHash eventType, VariantMap& eventData);
|
|
void HandleGameNotification(StringHash eventType, VariantMap& eventData);
|
|
void HandleSetNodeConfig(StringHash eventType, VariantMap& eventData);
|
|
void HandleFoundCity(StringHash eventType, VariantMap& eventData);
|
|
void HandleTravelToCity(StringHash eventType, VariantMap& eventData);
|
|
void HandleGiveItems(StringHash eventType, VariantMap& eventData);
|
|
void HandleGiveMoney(StringHash eventType, VariantMap& eventData);
|
|
void HandleGiveFlag(StringHash eventType, VariantMap& eventData);
|
|
void HandleNewItem(StringHash eventType, VariantMap& eventData);
|
|
void HandleDividend(StringHash eventType, VariantMap& eventData);
|
|
void HandleNewMortgage(StringHash eventType, VariantMap& eventData);
|
|
void HandleGetIgnoreSettings(StringHash eventType, VariantMap& eventData);
|
|
void HandleSetIgnoreSettings(StringHash eventType, VariantMap& eventData);
|
|
void HandleEventBadge(StringHash eventType, VariantMap& eventData);
|
|
void HandleCameraType(StringHash eventType, VariantMap& eventData);
|
|
void HandleGroundType(StringHash eventType, VariantMap& eventData);
|
|
void HandleResizeFlag(StringHash eventType, VariantMap& eventData);
|
|
void HandleControlsChanged(StringHash eventType, VariantMap& eventData);
|
|
void HandleGetTextures(StringHash eventType, VariantMap& eventData);
|
|
void HandlePalette(StringHash eventType, VariantMap& eventData);
|
|
void HandleFightFire(StringHash eventType, VariantMap& eventData);
|
|
void HandleGenerateInvitation(StringHash eventType, VariantMap& eventData);
|
|
void HandleAcceptInvitation(StringHash eventType, VariantMap& eventData);
|
|
void HandleRequestInvitationStatus(StringHash eventType, VariantMap& eventData);
|
|
void HandleCreateNewWallet(StringHash eventType, VariantMap& eventData);
|
|
void HandleStartMining(StringHash eventType, VariantMap& eventData);
|
|
void HandleStopMining(StringHash eventType, VariantMap& eventData);
|
|
void HandleRestoreModelBackup(StringHash eventType, VariantMap& eventData);
|
|
void HandleMainPanelGeometry(StringHash eventType, VariantMap& eventData);
|
|
void HandleBuildingStats(StringHash eventType, VariantMap& eventData);
|
|
void HandlePlaceModel(StringHash eventType, VariantMap& eventData);
|
|
|
|
void MoveCamera(float timeStep);
|
|
void CloseBuildingTooltip();
|
|
void CloseSelectionTooltip();
|
|
void MoveCameraToStartPosition(bool hug_terrain);
|
|
void SetNoFlagUnderConstruction();
|
|
void ApproveBuildAdd(const std::shared_ptr<Flag> &flag);
|
|
void RestoreFlagUnderConstruction();
|
|
void UpdateTargetting();
|
|
void PickMaterial();
|
|
void TriggerIfRunestone();
|
|
void OnRunestoneTriggered(const std::shared_ptr<Flag> &flag, uint8_t x, uint8_t y, uint16_t h);
|
|
void Undo();
|
|
void Redo();
|
|
void UpdateBudget(const std::shared_ptr<Flag> &flag, const std::vector<std::vector<uint8_t>> &from_tiles, const std::vector<std::vector<uint8_t>> &to_tiles, bool undo);
|
|
void UpdateBudget(const std::shared_ptr<Flag> &flag, const std::shared_ptr<TileData> &from_tiles, const std::shared_ptr<TileData> &to_tiles, bool undo);
|
|
uint8_t get_block_from_palette_index(const std::shared_ptr<Flag> &flag, uint8_t idx) const;
|
|
bool SendCommand(cryptonote::cc_command_t cmd, uint64_t *nonce = NULL);
|
|
|
|
void Setup() override;
|
|
void Start() override;
|
|
void Stop() override;
|
|
String GetTitle() const { return "Townforge"; }
|
|
|
|
void UpdateConsoleColorMenu();
|
|
void ToggleConsole();
|
|
|
|
bool Pick(Drawable *&d, uint32_t &tile_x, uint32_t &tile_y, uint32_t &tile_h, uint32_t &top_x, uint32_t &top_y, uint32_t &top_h, Vector3 *point = NULL);
|
|
|
|
Selection GetEffectiveSelection() const;
|
|
std::shared_ptr<Flag> GetHoverFlag() const;
|
|
std::shared_ptr<Flag> GetSelectedFlag() const;
|
|
void SelectWholeFlag();
|
|
|
|
private:
|
|
void InitTouchInput();
|
|
void InitMouseMode(MouseMode mode);
|
|
void HandleTouchBegin(StringHash /*eventType*/, VariantMap& eventData);
|
|
void SetWindowTitleAndIcon();
|
|
void ShowSplashScreen();
|
|
void CreateDebugHud();
|
|
void CreateScene();
|
|
void SetupViewport();
|
|
void SubscribeToEvents();
|
|
void LoadEffects();
|
|
void SetupUI();
|
|
void SetupWallet();
|
|
void SetupDaemon();
|
|
void LoadTutorials();
|
|
void UnsetFocus();
|
|
void RebuildMap(bool save = false);
|
|
void SelectNextMaterial(bool next);
|
|
void AddBlock(bool use_selection, bool flat);
|
|
void RemoveBlock(bool use_selection, bool top_level);
|
|
void RemoveHiddenBlocks();
|
|
std::vector<std::pair<std::shared_ptr<tb::TBBitmap>, unsigned int>> LoadImage(const String &filename);
|
|
void EnableShadows(TBID type);
|
|
void SetShadowMapSize(TBID size);
|
|
void EnableClouds(bool enable);
|
|
void SetMaxActiveFPS(unsigned fps);
|
|
void SetMaxInactiveFPS(unsigned fps);
|
|
void SetFontSize(unsigned size);
|
|
void SetViewDistance(unsigned distance);
|
|
bool SetGraphicsMode(int monitor, int width, int height, int refresh_rate);
|
|
void EnableHorizon(bool enable);
|
|
void EnableDynamicSky(bool enable);
|
|
void ShowFlags(bool enable);
|
|
void SetTimeOfDay(float t);
|
|
void SetMusicTrack(const String &track);
|
|
void SetMusicVolume(float volume);
|
|
void SetSFXVolume(float volume);
|
|
void SetPlaySFXOnNewBlock(bool play);
|
|
void SetControlDaemon(bool control);
|
|
void SetRestartDaemon(bool control);
|
|
void SetEnableTutorial(bool enable);
|
|
void SetCompass(TBID compass);
|
|
void SetCalendar(TBID calendar);
|
|
void SetThermometer(TBID calendar);
|
|
void SetNotificationLifetime(unsigned seconds);
|
|
void UpdateSky();
|
|
void LoadControls();
|
|
bool GetKeyDown(Urho3D::Key key);
|
|
bool IsActionActive(Controls::Action action);
|
|
void Welcome();
|
|
uint8_t FindFreePaletteSlot(const std::shared_ptr<Flag> &flag) const;
|
|
void GoToCity(uint32_t city);
|
|
void GoToFlag(const std::shared_ptr<Flag> &flag);
|
|
void GoToNextFlag(bool next, bool same_role);
|
|
void OnNewBlock();
|
|
void NotifyGameUpdate(const cryptonote::block *b);
|
|
void NotifyEvents(const cc::game_events_t &events, std::set<uint64_t> &processed);
|
|
float GetSnowiness() const;
|
|
float GetSummerness() const;
|
|
void UpdateBoids();
|
|
void SaveBackup();
|
|
void RemoveBackup();
|
|
bool SaveModelData(const std::shared_ptr<Flag> &flag, std::string &data, bool selection_only, bool silent = false);
|
|
bool LoadModelData(const std::shared_ptr<Flag> &flag, const std::string &data, std::map<uint32_t, uint32_t> existing_tiles = {}, bool silent = false);
|
|
void CheckModelBackup();
|
|
bool IsCommandOurs(const cryptonote::cc_command_t *cmd) const;
|
|
void EndModelPlacement();
|
|
VariantVector GetMusicTracks();
|
|
|
|
template<typename T> T GetConfigValue(const String &filename, SharedPtr<JSONFile> &config, const char *section, const char *key, const T &default_value);
|
|
template<typename T> T GetConfigValue(const char *section, const char *key, const T &default_value);
|
|
template<typename T> T SetConfigValue(const char *section, const char *key, const T &value);
|
|
|
|
private:
|
|
GameState gameState;
|
|
Map ↦
|
|
|
|
SharedPtr<Scene> scene_;
|
|
SharedPtr<Node> cameraNode_;
|
|
SharedPtr<Node> cloudsLightNode_;
|
|
SharedPtr<CameraController> camera_;
|
|
SharedPtr<Viewport> viewport;
|
|
|
|
SharedPtr<Node> cityNode;
|
|
CityMeshUrho3D *cityMesh;
|
|
SharedPtr<UIUrho3D> ui;
|
|
bool rightButtonPressed;
|
|
bool leftButtonPressed;
|
|
|
|
unsigned screenJoystickIndex_;
|
|
unsigned screenJoystickSettingsIndex_;
|
|
bool touchEnabled_;
|
|
MouseMode useMouseMode_;
|
|
bool paused_;
|
|
bool drawDebugGeometry_;
|
|
|
|
bool hasSelection_;
|
|
uint32_t mouse_x, mouse_y, mouse_h;
|
|
uint32_t top_x, top_y, top_h;
|
|
uint32_t sx0r, sy0r, sx1r, sy1r;
|
|
uint32_t sx0, sy0, sx1, sy1;
|
|
uint16_t currentBlockVariant;
|
|
|
|
std::shared_ptr<GameWallet> wallet;
|
|
WalletRefresher walletRefresher_;
|
|
|
|
boost::optional<std::tuple<uint32_t, std::vector<uint16_t>, std::shared_ptr<TileData>, std::shared_ptr<Flag::unpacker>>> flagUnderConstruction;
|
|
UndoableStack undo;
|
|
bool mark_new_block;
|
|
|
|
Selection base_selection;
|
|
Selection selection;
|
|
SelectionMode selectionMode;
|
|
EditMode editMode;
|
|
float timeOfDay;
|
|
bool needSkyUpdate_;
|
|
bool newCity_;
|
|
Vector3 startPosition_;
|
|
bool startPositionHugTerrain_;
|
|
Quaternion startRotation_;
|
|
|
|
bool buildMode_;
|
|
Urho3D::SharedPtr<Urho3D::Sprite> crosshair_;
|
|
|
|
UIConsole *console;
|
|
bool mentioned_in_chat;
|
|
Urho3D::SharedPtr<Node> mainLightNode;
|
|
Urho3D::SharedPtr<Node> procskyNode;
|
|
CloudCover clouds;
|
|
Horizon horizon;
|
|
UITooltip *building_tooltip;
|
|
UITooltip *selection_tooltip;
|
|
std::shared_ptr<Flag> building_tooltip_flag;
|
|
|
|
std::shared_ptr<Flag> previous_selected_flag;
|
|
std::shared_ptr<Flag> last_selected_flag;
|
|
|
|
std::set<Urho3D::Key> keyWasDown_;
|
|
|
|
boost::optional<VariantMap> pending_invitation;
|
|
|
|
::Audio audio;
|
|
|
|
String config_filename;
|
|
String building_backup_filename;
|
|
|
|
std::map<uint32_t, uint64_t> flags_for_sale;
|
|
std::map<String, uint32_t> flag_for_sale_commands;
|
|
|
|
std::map<uint32_t, uint32_t> flags_with_pending_build_commands;
|
|
std::map<String, uint32_t> flags_with_pending_build_commands_commands;
|
|
|
|
bool play_sfx_on_new_block;
|
|
|
|
SharedPtr<JSONFile> config;
|
|
FileWatcher config_watcher;
|
|
|
|
Urho3D::Timer boidUpdateTimer_;
|
|
|
|
bool placing_model;
|
|
std::string placing_model_data;
|
|
std::shared_ptr<Flag> placing_model_flag;
|
|
int placing_model_dx;
|
|
int placing_model_dy;
|
|
int placing_model_dh;
|
|
int placing_model_dr;
|
|
|
|
bool enableUserLight_;
|
|
|
|
std::unique_ptr<DaemonController> daemon_controller;
|
|
|
|
bool showWalletInfo_;
|
|
|
|
Urho3D::Timer startupTutorialTimer_;
|
|
bool startupTutorialTriggered_;
|
|
bool selectionTutorialTriggered_;
|
|
|
|
bool notification_sent_by_command_processing;
|
|
|
|
std::vector<std::tuple<std::string, Color, bool>> chat_colors;
|
|
|
|
std::list<std::shared_ptr<QueuedCommand>> queued_commands;
|
|
|
|
struct AddBlockCommand: public QueuedCommand
|
|
{
|
|
AddBlockCommand(bool use_selection, bool flat): use_selection(use_selection), flat(flat) {}
|
|
virtual void execute(CryptoCityUrho3D &e) {
|
|
e.AddBlock(use_selection, flat);
|
|
}
|
|
bool use_selection;
|
|
bool flat;
|
|
};
|
|
|
|
struct RemoveBlockCommand: public QueuedCommand
|
|
{
|
|
RemoveBlockCommand(bool use_selection, bool top_level): use_selection(use_selection), top_level(top_level) {}
|
|
virtual void execute(CryptoCityUrho3D &e) {
|
|
e.RemoveBlock(use_selection, top_level);
|
|
}
|
|
bool use_selection, top_level;
|
|
};
|
|
|
|
struct UndoCommand: public QueuedCommand
|
|
{
|
|
UndoCommand() {}
|
|
virtual void execute(CryptoCityUrho3D &e) {
|
|
e.Undo();
|
|
}
|
|
};
|
|
|
|
struct RedoCommand: public QueuedCommand
|
|
{
|
|
RedoCommand() {}
|
|
virtual void execute(CryptoCityUrho3D &e) {
|
|
e.Redo();
|
|
}
|
|
};
|
|
|
|
};
|
|
|
|
CryptoCityUrho3D::CryptoCityUrho3D(Context *ctx):
|
|
Application(ctx),
|
|
gameState(ctx),
|
|
map(gameState.map),
|
|
cityMesh(NULL),
|
|
rightButtonPressed(false),
|
|
leftButtonPressed(false),
|
|
screenJoystickIndex_(M_MAX_UNSIGNED),
|
|
screenJoystickSettingsIndex_(M_MAX_UNSIGNED),
|
|
touchEnabled_(false),
|
|
useMouseMode_(MM_FREE),
|
|
paused_(false),
|
|
drawDebugGeometry_(false),
|
|
hasSelection_(false),
|
|
mouse_x(0),
|
|
mouse_y(0),
|
|
mouse_h(0),
|
|
top_x(0),
|
|
top_y(0),
|
|
top_h(0),
|
|
sx0r(0),
|
|
sy0r(0),
|
|
sx1r(0),
|
|
sy1r(0),
|
|
sx0(0),
|
|
sy0(0),
|
|
sx1(0),
|
|
sy1(0),
|
|
currentBlockVariant(cc::BLOCK_VARIANT_NONE),
|
|
undo(ctx),
|
|
mark_new_block(false),
|
|
selectionMode(SM_NORMAL),
|
|
editMode(EM_SELECT_RECTANGLE),
|
|
timeOfDay(0),
|
|
needSkyUpdate_(true),
|
|
buildMode_(false),
|
|
console(NULL),
|
|
mentioned_in_chat(false),
|
|
building_tooltip(NULL),
|
|
selection_tooltip(NULL),
|
|
audio(ctx),
|
|
play_sfx_on_new_block(true),
|
|
config_watcher(ctx),
|
|
newCity_(true),
|
|
startPosition_(START_POSITION),
|
|
startRotation_(START_ROTATION),
|
|
startPositionHugTerrain_(false),
|
|
boidUpdateTimer_(),
|
|
placing_model(false),
|
|
placing_model_dx(0),
|
|
placing_model_dy(0),
|
|
placing_model_dh(0),
|
|
placing_model_dr(0),
|
|
enableUserLight_(false),
|
|
showWalletInfo_(false),
|
|
startupTutorialTriggered_(false),
|
|
selectionTutorialTriggered_(false),
|
|
notification_sent_by_command_processing(false)
|
|
{
|
|
}
|
|
|
|
CryptoCityUrho3D::~CryptoCityUrho3D()
|
|
{
|
|
}
|
|
|
|
bool CryptoCityUrho3D::Pick(Drawable *&d, uint32_t &sel_tile_x, uint32_t &sel_tile_y, uint32_t &sel_tile_h, uint32_t &top_tile_x, uint32_t &top_tile_y, uint32_t &top_tile_h, Vector3 *point)
|
|
{
|
|
d = NULL;
|
|
|
|
if (gameState.cityState.ox == 0 || gameState.cityState.oy == 0)
|
|
return false;
|
|
|
|
auto* ui = GetSubsystem<Urho3D::UI>();
|
|
IntVector2 pos = buildMode_ ? IntVector2(ui->GetRoot()->GetSize() / 2) : ui->GetCursorPosition();
|
|
// Check the cursor is visible and there is no UI element in front of the cursor
|
|
if (!ui->GetCursor()->IsVisible() || ui->GetElementAt(pos, true))
|
|
return false;
|
|
TBWidget *w = UTBRendererBatcher::Singleton().Root().GetWidgetAt(pos.x_, pos.y_, true);
|
|
if (w && (!building_tooltip || !building_tooltip->IsAncestorOf(w)) && (!selection_tooltip || !selection_tooltip->IsAncestorOf(w)) && !this->ui->IsClickPassthroughWidget(w))
|
|
return false;
|
|
|
|
auto* graphics = GetSubsystem<Graphics>();
|
|
auto* camera = cameraNode_->GetComponent<Camera>();
|
|
Ray cameraRay = camera->GetScreenRay((float)pos.x_ / graphics->GetWidth(), (float)pos.y_ / graphics->GetHeight());
|
|
// Pick only geometry objects, not eg. zones or lights, only get the first (closest) hit
|
|
PODVector<RayQueryResult> results;
|
|
int maxDistance = 100000;
|
|
RayOctreeQuery query(results, cameraRay, RAY_TRIANGLE, maxDistance, DRAWABLE_GEOMETRY);
|
|
scene_->GetComponent<Octree>()->Raycast(query);
|
|
for (unsigned int i = 0; i < results.Size(); ++i)
|
|
{
|
|
RayQueryResult& result = results[i];
|
|
if (cityMesh->IsSelectable(result.drawable_->GetNode()))
|
|
{
|
|
d = result.drawable_;
|
|
sel_tile_x = (uint32_t)(int32_t)floor(result.position_.x_ - result.normal_.x_ / 128) + gameState.cityState.ox;
|
|
sel_tile_y = (uint32_t)(int32_t)floor(result.position_.z_ - result.normal_.z_ / 128) + gameState.cityState.oy;
|
|
sel_tile_h = result.position_.y_ < 0.5f ? 0 : (uint32_t)(int32_t)floor(result.position_.y_ - result.normal_.y_ / 128);
|
|
top_tile_x = (uint32_t)(int32_t)floor(result.position_.x_ + result.normal_.x_ / 128) + gameState.cityState.ox;
|
|
top_tile_y = (uint32_t)(int32_t)floor(result.position_.z_ + result.normal_.z_ / 128) + gameState.cityState.oy;
|
|
top_tile_h = (uint32_t)(int32_t)floor(result.position_.y_ + result.normal_.y_ / 128);
|
|
if (point)
|
|
*point = result.position_;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
Selection CryptoCityUrho3D::GetEffectiveSelection() const
|
|
{
|
|
Selection current_selection = selection;
|
|
|
|
if (selectionMode == SM_UNION)
|
|
{
|
|
Selection sel = base_selection;
|
|
sel.set_union(current_selection);
|
|
return sel;
|
|
}
|
|
else if (selectionMode == SM_DIFFERENCE)
|
|
{
|
|
Selection sel = base_selection;
|
|
sel.set_difference(current_selection);
|
|
return sel;
|
|
}
|
|
return current_selection;
|
|
}
|
|
|
|
std::shared_ptr<Flag> CryptoCityUrho3D::GetHoverFlag() const
|
|
{
|
|
if (mouse_x == 0 || mouse_y == 0)
|
|
return NULL;
|
|
return map.get_flag(mouse_x, mouse_y);
|
|
}
|
|
|
|
std::shared_ptr<Flag> CryptoCityUrho3D::GetSelectedFlag() const
|
|
{
|
|
if (selection.is_empty())
|
|
return NULL;
|
|
return map.get_flag(selection.x0, selection.y0);
|
|
}
|
|
|
|
void CryptoCityUrho3D::Setup(void)
|
|
{
|
|
// Modify engine startup parameters
|
|
engineParameters_[EP_WINDOW_TITLE] = "Townforge";
|
|
engineParameters_[EP_LOG_NAME] = GetSubsystem<FileSystem>()->GetAppPreferencesDir("townforge", "logs") + "townforge.log";
|
|
engineParameters_[EP_FULL_SCREEN] = false;
|
|
engineParameters_[EP_HEADLESS] = false;
|
|
engineParameters_[EP_SOUND] = true;
|
|
engineParameters_[EP_WINDOW_RESIZABLE] = true;
|
|
|
|
// Find the config file manually so we can tell the engine what graphics mode we want,
|
|
// since we can't tell it headless then switch to graphics
|
|
String config_dir = GetSubsystem<FileSystem>()->GetProgramDir() + "../GameData";
|
|
config_filename = config_dir + "/config.json";
|
|
if (!epee::file_io_utils::is_file_exist(config_filename.CString()))
|
|
{
|
|
config_dir = GetSubsystem<FileSystem>()->GetProgramDir();
|
|
config_filename = GetSubsystem<FileSystem>()->GetProgramDir() + "/config.json";
|
|
}
|
|
printf("using config: %s\n", config_filename.CString());
|
|
if (!epee::file_io_utils::is_file_exist(config_filename.CString()))
|
|
SetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_GRAPHICS_MODE, DEFAULT_GRAPHICS_MODE);
|
|
const String mode = GetConfigValue(config_filename, config, CONFIG_GRAPHICS_SECTION, CONFIG_GRAPHICS_MODE, DEFAULT_GRAPHICS_MODE);
|
|
int monitor = -1, width = -1, height = -1, refresh_rate = -1;
|
|
sscanf(mode.CString(), "%d:%dx%d@%d", &monitor, &width, &height, &refresh_rate);
|
|
engineParameters_[EP_FULL_SCREEN] = monitor >= 0;
|
|
if (width > 0)
|
|
engineParameters_[EP_WINDOW_WIDTH] = width;
|
|
if (height > 0)
|
|
engineParameters_[EP_WINDOW_HEIGHT] = height;
|
|
if (refresh_rate > 0)
|
|
engineParameters_[EP_REFRESH_RATE] = refresh_rate;
|
|
|
|
const String paths = GetSubsystem<FileSystem>()->GetProgramDir() + ";" + GetSubsystem<FileSystem>()->GetProgramDir() + "../";
|
|
if (!engineParameters_.Contains(EP_RESOURCE_PREFIX_PATHS))
|
|
engineParameters_[EP_RESOURCE_PREFIX_PATHS] = paths;
|
|
if (!engineParameters_.Contains(EP_RESOURCE_PATHS))
|
|
{
|
|
String resources = "CoreData;Data;GameData;";
|
|
if (epee::file_io_utils::is_file_exist((config_dir + "/ExtraMusic.pak").CString()))
|
|
resources += "ExtraMusic;";
|
|
if (epee::file_io_utils::is_file_exist((config_dir + "/Tutorial.pak").CString()))
|
|
resources += "Tutorial;";
|
|
engineParameters_[EP_RESOURCE_PATHS] = resources;
|
|
}
|
|
|
|
//context_->RegisterFactory<ProcSky>();
|
|
ProcSky::RegisterObject(context_);
|
|
BlockModel::RegisterObject(context_);
|
|
CustomModel::RegisterObject(context_);
|
|
BoidModel::RegisterObject(context_);
|
|
Boid::RegisterObject(context_);
|
|
|
|
const auto path = GetPath(config_filename);
|
|
if (!config_watcher.StartWatching(path, false))
|
|
printf("Failed to start watching config file\n");
|
|
|
|
engine_->SetAutoExit(false);
|
|
GetSubsystem<Input>()->SubscribeToEvent(E_EXITREQUESTED, URHO3D_HANDLER(CryptoCityUrho3D, HandleExitRequest));
|
|
}
|
|
|
|
void CryptoCityUrho3D::Start()
|
|
{
|
|
if (GetPlatform() == "Android" || GetPlatform() == "iOS")
|
|
// On mobile platform, enable touch by adding a screen joystick
|
|
InitTouchInput();
|
|
else if (GetSubsystem<Input>()->GetNumJoysticks() == 0)
|
|
// On desktop platform, do not detect touch when we already got a joystick
|
|
SubscribeToEvent(E_TOUCHBEGIN, URHO3D_HANDLER(CryptoCityUrho3D, HandleTouchBegin));
|
|
|
|
// Set custom window Title & Icon
|
|
SetWindowTitleAndIcon();
|
|
|
|
ShowSplashScreen();
|
|
|
|
SetupUI();
|
|
|
|
// Create console and debug HUD
|
|
CreateDebugHud();
|
|
|
|
// Subscribe mouse button events
|
|
SubscribeToEvent(E_MOUSEBUTTONDOWN, URHO3D_HANDLER(CryptoCityUrho3D, HandleMouseButtonDown));
|
|
SubscribeToEvent(E_MOUSEBUTTONUP, URHO3D_HANDLER(CryptoCityUrho3D, HandleMouseButtonUp));
|
|
SubscribeToEvent(E_MOUSEMOVE, URHO3D_HANDLER(CryptoCityUrho3D, HandleMouseMotion));
|
|
// Subscribe key down event
|
|
SubscribeToEvent(E_KEYDOWN, URHO3D_HANDLER(CryptoCityUrho3D, HandleKeyDown));
|
|
// Subscribe key up event
|
|
SubscribeToEvent(E_KEYUP, URHO3D_HANDLER(CryptoCityUrho3D, HandleKeyUp));
|
|
// Subscribe scene update event
|
|
SubscribeToEvent(E_SCENEUPDATE, URHO3D_HANDLER(CryptoCityUrho3D, HandleSceneUpdate));
|
|
SubscribeToEvent(E_POSTRENDERUPDATE, URHO3D_HANDLER(CryptoCityUrho3D, HandlePostRenderUpdate));
|
|
|
|
SubscribeToEvent(GetSubsystem<UI>()->GetRoot(), E_RESIZED, URHO3D_HANDLER(CryptoCityUrho3D, HandleResized));
|
|
|
|
// Setup the viewport for displaying the scene
|
|
SetupViewport();
|
|
|
|
LoadEffects();
|
|
|
|
// Create the scene content
|
|
CreateScene();
|
|
|
|
// Hook up to the frame update events
|
|
SubscribeToEvents();
|
|
|
|
// Set the mouse mode to use in the sample
|
|
InitMouseMode(MM_FREE);
|
|
|
|
// Start with no UI focus
|
|
UnsetFocus();
|
|
|
|
LoadTutorials();
|
|
|
|
// setup wallet
|
|
SetupWallet();
|
|
|
|
SetupDaemon();
|
|
|
|
// Start refreshing
|
|
walletRefresher_.SetWallet(wallet);
|
|
}
|
|
|
|
void CryptoCityUrho3D::SetupDaemon()
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
return;
|
|
|
|
const std::string wallet_daemon_address = wallet->get_daemon_address();
|
|
const char *ptr = strrchr(wallet_daemon_address.c_str(), ':');
|
|
const std::string host = ptr ? wallet_daemon_address.substr(0, ptr - wallet_daemon_address.c_str()) : wallet_daemon_address;
|
|
const uint16_t port = ptr ? atoi(ptr + 1) : 0;
|
|
|
|
const String path = GetSubsystem<FileSystem>()->GetProgramDir();
|
|
daemon_controller.reset(new DaemonController(wallet->nettype(), host, port, path.CString()));
|
|
const std::string data_dir = GetConfigValue(CONFIG_NODE_SECTION, CONFIG_DAEMON_DATA_DIR, DEFAULT_DAEMON_DATA_DIR).CString();
|
|
const std::string log = GetConfigValue(CONFIG_NODE_SECTION, CONFIG_DAEMON_LOG, DEFAULT_DAEMON_LOG).CString();
|
|
daemon_controller->Configure(data_dir, log);
|
|
if (GetConfigValue(CONFIG_NODE_SECTION, CONFIG_CONTROL_DAEMON, DEFAULT_CONTROL_DAEMON) && !daemon_controller->IsRunning())
|
|
{
|
|
printf("Starting daemon\n");
|
|
std::vector<std::string> errors = daemon_controller->Start();
|
|
if (!errors.empty())
|
|
{
|
|
errors.insert(errors.begin(), "Error starting townforged - you will have to run it manuallly");
|
|
new MessageBox(context_, boost::join(errors, "\n"));
|
|
}
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::LoadTutorials()
|
|
{
|
|
auto* cache = GetSubsystem<ResourceCache>();
|
|
SharedPtr<JSONFile> tutorials(cache->GetResource<JSONFile>("TB/Tutorial/tutorials.json", false));
|
|
load_tutorials(tutorials);
|
|
}
|
|
|
|
void CryptoCityUrho3D::SetupWallet()
|
|
{
|
|
const auto args = Urho3D::GetArguments();
|
|
const int argc = args.Size() + 1;
|
|
std::vector<const char*> argv;
|
|
argv.push_back("townforge");
|
|
for (unsigned int i = 0; i < args.Size(); ++i)
|
|
{
|
|
argv.push_back(args[i].CString());
|
|
}
|
|
try { wallet.reset(new GameWallet(context_)); }
|
|
catch (const std::exception &e) { MERROR("Wallet not created: " << e.what()); engine_->Exit(); return; }
|
|
|
|
SubscribeToEvent(wallet.get(), E_WALLET_LOADING_WALLET, URHO3D_HANDLER(CryptoCityUrho3D, HandleLoadingWallet));
|
|
SubscribeToEvent(wallet.get(), E_WALLET_NEW_WALLET, URHO3D_HANDLER(CryptoCityUrho3D, HandleNewWallet));
|
|
SubscribeToEvent(wallet.get(), E_WALLET_NEW_SNAPSHOT, URHO3D_HANDLER(CryptoCityUrho3D, HandleNewSnapshot));
|
|
SubscribeToEvent(wallet.get(), E_WALLET_NEW_BLOCK, URHO3D_HANDLER(CryptoCityUrho3D, HandleNewBlock));
|
|
SubscribeToEvent(wallet.get(), E_WALLET_NEW_CHAT_LINE, URHO3D_HANDLER(CryptoCityUrho3D, HandleNewChatLine));
|
|
SubscribeToEvent(wallet.get(), E_WALLET_NEW_TRADE_COMMAND, URHO3D_HANDLER(CryptoCityUrho3D, HandleNewTradeCommand));
|
|
SubscribeToEvent(wallet.get(), E_WALLET_SYNCED, URHO3D_HANDLER(CryptoCityUrho3D, HandleWalletSynced));
|
|
SubscribeToEvent(wallet.get(), E_WALLET_NEW_POOL_COMMAND, URHO3D_HANDLER(CryptoCityUrho3D, HandleNewPoolCommand));
|
|
SubscribeToEvent(wallet.get(), E_WALLET_POOL_COMMAND_GONE, URHO3D_HANDLER(CryptoCityUrho3D, HandlePoolCommandGone));
|
|
|
|
try { wallet->init(argc, argv.data()); }
|
|
catch (const std::exception &e) { MERROR("Wallet not initialized: " << e.what()); engine_->Exit(); return; }
|
|
}
|
|
|
|
void CryptoCityUrho3D::UnsetFocus()
|
|
{
|
|
GetSubsystem<Urho3D::UI>()->SetFocusElement(nullptr);
|
|
TBWidget::focused_widget = nullptr;
|
|
}
|
|
|
|
std::vector<std::pair<std::shared_ptr<tb::TBBitmap>, unsigned int>> CryptoCityUrho3D::LoadImage(const String &filename)
|
|
{
|
|
std::vector<std::pair<std::shared_ptr<tb::TBBitmap>, unsigned int>> textures;
|
|
auto* cache = GetSubsystem<ResourceCache>();
|
|
|
|
if (!filename.EndsWith(".gif"))
|
|
{
|
|
textures.push_back(std::make_pair(std::shared_ptr<tb::TBBitmap>(new UTBBitmap(context_, cache->GetResource<Texture2D>(filename))), 0));
|
|
return textures;
|
|
}
|
|
|
|
String full_filename = cache->GetResourceFileName(filename);
|
|
if (full_filename.Empty())
|
|
{
|
|
printf("NOT FOUND: %s\n", filename.CString());
|
|
return {};
|
|
}
|
|
|
|
GIF gif;
|
|
bool ret = load_gif(full_filename.CString(), &gif);
|
|
if (!ret)
|
|
{
|
|
MERROR("Failed to load " << full_filename.CString());
|
|
return {};
|
|
}
|
|
uint8_t *rgb = new uint8_t[gif.width * gif.height * 4];
|
|
memset(rgb, 0, gif.width * gif.height * 4);
|
|
for (uint16_t i = 0; i < gif.nimages; ++i)
|
|
{
|
|
SharedPtr<Texture2D> texture(new Texture2D(context_));
|
|
texture->SetFilterMode(Urho3D::TextureFilterMode::FILTER_DEFAULT);
|
|
texture->SetNumLevels(1);
|
|
texture->SetSize(gif.width, gif.height, Graphics::GetRGBAFormat());
|
|
const auto &image = gif.images[i];
|
|
int idx = 0;
|
|
for (int y = 0; y < image.height; ++y)
|
|
{
|
|
for (int x = 0; x < image.width; ++x)
|
|
{
|
|
int pidx = (y + image.dy) * gif.width + x + image.dx;
|
|
if (image.pixel_data[idx] != image.transparent_color)
|
|
{
|
|
rgb[pidx*4] = gif.colors[image.pixel_data[idx]].r;
|
|
rgb[pidx*4+1] = gif.colors[image.pixel_data[idx]].g;
|
|
rgb[pidx*4+2] = gif.colors[image.pixel_data[idx]].b;
|
|
rgb[pidx*4+3] = 0xff;
|
|
rgb[pidx*4+3] = image.pixel_data[idx] == gif.background_color ? 0 : 0xff;
|
|
}
|
|
++idx;
|
|
}
|
|
}
|
|
std::shared_ptr<tb::TBBitmap> bitmap(UTBRendererBatcher::Singleton().CreateBitmap(gif.width, gif.height, (uint32_t*)rgb));
|
|
unsigned int delay = image.delay;
|
|
textures.push_back(std::make_pair(std::move(bitmap), delay));
|
|
|
|
// does not seem to match the spec, but this fixes a test case
|
|
if (image.disposal == 1)
|
|
memset(rgb, 0, gif.width * gif.height * 4);
|
|
}
|
|
delete[] rgb;
|
|
for (unsigned i = 0; i < gif.nimages; ++i)
|
|
free(gif.images[i].pixel_data);
|
|
free(gif.images);
|
|
return textures;
|
|
}
|
|
|
|
void CryptoCityUrho3D::SetupUI()
|
|
{
|
|
auto* cache = GetSubsystem<ResourceCache>();
|
|
auto* style = cache->GetResource<XMLFile>("UI/DefaultStyle.xml");
|
|
SharedPtr<Cursor> cursor(new Cursor(context_));
|
|
cursor->SetStyleAuto(style);
|
|
GetSubsystem<Urho3D::UI>()->SetCursor(cursor);
|
|
GetSubsystem<Urho3D::UI>()->SetUseSystemClipboard(true);
|
|
|
|
// create TB render batcher
|
|
Graphics *graphics = GetSubsystem<Graphics>();
|
|
UTBRendererBatcher::Create(GetContext(), graphics->GetWidth(), graphics->GetHeight());
|
|
|
|
UTBRendererBatcher::Singleton().Init();
|
|
|
|
const float crosshair_size = graphics->GetWidth() / 32.0f;
|
|
crosshair_ = GetSubsystem<Urho3D::UI>()->GetRoot()->CreateChild<Sprite>();
|
|
crosshair_->SetTexture(cache->GetResource<Texture2D>("Textures/crosshair.png"));
|
|
crosshair_->SetSize(crosshair_size, crosshair_size);
|
|
crosshair_->SetPriority(-100);
|
|
crosshair_->SetHotSpot(crosshair_size / 2.0f, crosshair_size / 2.0f);
|
|
crosshair_->SetPosition(graphics->GetWidth() / 2.0f, graphics->GetHeight() / 2.0f);
|
|
crosshair_->SetVisible(buildMode_);
|
|
|
|
console = new UIConsole(context_);
|
|
console->SetNumRows(GetSubsystem<Graphics>()->GetHeight() / 16 / 4);
|
|
console->SetNumBufferedRows(8 * console->GetNumRows());
|
|
console->SetCommandInterpreter(GetTypeName());
|
|
console->SetVisible(false);
|
|
console->AddInterpreter("Chat");
|
|
console->AddInterpreter("Emote");
|
|
console->SetPrefixStyle("ChatPrefixStyle");
|
|
|
|
SharedPtr<JSONFile> smilies_desc(cache->GetResource<JSONFile>("UI/chat/noto/smilies.json"));
|
|
if (smilies_desc)
|
|
{
|
|
std::vector<std::pair<std::string, std::vector<std::pair<std::shared_ptr<tb::TBBitmap>, unsigned int>>>> images;
|
|
const JSONValue &root = smilies_desc->GetRoot();
|
|
if (root.IsObject() && root.Get("images").IsArray())
|
|
{
|
|
const JSONArray &array = root.Get("images").GetArray();
|
|
for (unsigned i = 0; i < array.Size(); ++i)
|
|
if (array[i].Get("text").IsString() && array[i].Get("image").IsString())
|
|
images.push_back(std::make_pair(array[i].Get("text").GetString().CString(), LoadImage(array[i].Get("image").GetString())));
|
|
console->AddImages(images, true);
|
|
}
|
|
}
|
|
|
|
ui = new UIUrho3D(context_, &gameState);
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_LOAD_WALLET, URHO3D_HANDLER(CryptoCityUrho3D, HandleLoadWallet));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_DEPOSIT, URHO3D_HANDLER(CryptoCityUrho3D, HandleDeposit));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_WITHDRAW, URHO3D_HANDLER(CryptoCityUrho3D, HandleWithdraw));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_BUY_LAND, URHO3D_HANDLER(CryptoCityUrho3D, HandleBuyLand));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_BUILDING_SETTINGS, URHO3D_HANDLER(CryptoCityUrho3D, HandleBuildingSettings));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_BUILDING_STATS, URHO3D_HANDLER(CryptoCityUrho3D, HandleBuildingStats));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_RENAME_FLAG, URHO3D_HANDLER(CryptoCityUrho3D, HandleRename));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_REPAIR, URHO3D_HANDLER(CryptoCityUrho3D, HandleRepair));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_BUY_ITEMS, URHO3D_HANDLER(CryptoCityUrho3D, HandleBuyItems));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_BUY, URHO3D_HANDLER(CryptoCityUrho3D, HandleBuy));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_SELL, URHO3D_HANDLER(CryptoCityUrho3D, HandleSell));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_ASSIGN_ITEMS, URHO3D_HANDLER(CryptoCityUrho3D, HandleAssignItems));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_DESTROY_ITEMS, URHO3D_HANDLER(CryptoCityUrho3D, HandleDestroyItems));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_DEFINE_ATTRIBUTE, URHO3D_HANDLER(CryptoCityUrho3D, HandleDefineAttribute));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_INCREASE_ATTRIBUTE, URHO3D_HANDLER(CryptoCityUrho3D, HandleIncreaseAttribute));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_ADD_CITY_SPECIALIZATION, URHO3D_HANDLER(CryptoCityUrho3D, HandleAddCitySpecialization));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_DICE_ROLL, URHO3D_HANDLER(CryptoCityUrho3D, HandleDiceRoll));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_SAVE_PLAYER_PROFILE, URHO3D_HANDLER(CryptoCityUrho3D, HandleSavePlayerProfile));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_UPDATE_ITEM, URHO3D_HANDLER(CryptoCityUrho3D, HandleUpdateItem));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_ADD_BLOCK, URHO3D_HANDLER(CryptoCityUrho3D, HandleAddBlock));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_REMOVE_BLOCK, URHO3D_HANDLER(CryptoCityUrho3D, HandleRemoveBlock));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_REMOVE_HIDDEN_BLOCKS, URHO3D_HANDLER(CryptoCityUrho3D, HandleRemoveHiddenBlocks));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_DEMOLISH_FLAG, URHO3D_HANDLER(CryptoCityUrho3D, HandleDemolish));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_DESTROY_FLAG, URHO3D_HANDLER(CryptoCityUrho3D, HandleDestroyFlag));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_UNDO, URHO3D_HANDLER(CryptoCityUrho3D, HandleUndo));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_REDO, URHO3D_HANDLER(CryptoCityUrho3D, HandleRedo));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_LOAD_MODEL, URHO3D_HANDLER(CryptoCityUrho3D, HandleLoadModel));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_SAVE_MODEL, URHO3D_HANDLER(CryptoCityUrho3D, HandleSaveModel));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_APPROVE_BUILD, URHO3D_HANDLER(CryptoCityUrho3D, HandleApproveBuild));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_ABANDON_BUILD, URHO3D_HANDLER(CryptoCityUrho3D, HandleAbandonBuild));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_EXIT, URHO3D_HANDLER(CryptoCityUrho3D, HandleExit));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_MATERIAL_SELECTED, URHO3D_HANDLER(CryptoCityUrho3D, HandleMaterialSelected));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_VARIANT_SELECTED, URHO3D_HANDLER(CryptoCityUrho3D, HandleVariantSelected));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_EDIT_MODE_CHANGED, URHO3D_HANDLER(CryptoCityUrho3D, HandleEditModeChanged));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_SELECT_EXTEND, URHO3D_HANDLER(CryptoCityUrho3D, HandleExtendSelection));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_SELECT_EXTEND_MAXIMALLY, URHO3D_HANDLER(CryptoCityUrho3D, HandleExtendSelectionMaximally));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_SELECT_SHRINK, URHO3D_HANDLER(CryptoCityUrho3D, HandleShrinkSelection));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_SELECT_MOVE, URHO3D_HANDLER(CryptoCityUrho3D, HandleMoveSelection));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_SELECT_WHOLE_FLAG, URHO3D_HANDLER(CryptoCityUrho3D, HandleSelectWholeFlag));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_GROW_SELECTION, URHO3D_HANDLER(CryptoCityUrho3D, HandleGrowSelection));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_SELECT_EDGE, URHO3D_HANDLER(CryptoCityUrho3D, HandleSelectEdge));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_GO_TO_FLAG, URHO3D_HANDLER(CryptoCityUrho3D, HandleGoToFlag));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_GO_TO_VISTA, URHO3D_HANDLER(CryptoCityUrho3D, HandleGoToVista));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_GET_VISTA, URHO3D_HANDLER(CryptoCityUrho3D, HandleGetVista));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_GET_GAME_UPDATE_EVENTS, URHO3D_HANDLER(CryptoCityUrho3D, HandleGetGameUpdateEvents));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_CHAT, URHO3D_HANDLER(CryptoCityUrho3D, HandleChat));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_RESEARCH, URHO3D_HANDLER(CryptoCityUrho3D, HandleResearch));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_ENABLE_SHADOWS, URHO3D_HANDLER(CryptoCityUrho3D, HandleEnableShadows));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_SET_SHADOW_MAP_SIZE, URHO3D_HANDLER(CryptoCityUrho3D, HandleSetShadowMapSize));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_ENABLE_CLOUDS, URHO3D_HANDLER(CryptoCityUrho3D, HandleEnableClouds));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_SET_MAX_ACTIVE_FPS, URHO3D_HANDLER(CryptoCityUrho3D, HandleSetMaxActiveFPS));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_SET_MAX_INACTIVE_FPS, URHO3D_HANDLER(CryptoCityUrho3D, HandleSetMaxInactiveFPS));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_SET_FONT_SIZE, URHO3D_HANDLER(CryptoCityUrho3D, HandleSetFontSize));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_SET_VIEW_DISTANCE, URHO3D_HANDLER(CryptoCityUrho3D, HandleSetViewDistance));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_GRAPHICS_MODE, URHO3D_HANDLER(CryptoCityUrho3D, HandleGraphicsMode));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_ENABLE_HORIZON, URHO3D_HANDLER(CryptoCityUrho3D, HandleEnableHorizon));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_ENABLE_DYNAMIC_SKY, URHO3D_HANDLER(CryptoCityUrho3D, HandleEnableDynamicSky));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_TIME_OF_DAY, URHO3D_HANDLER(CryptoCityUrho3D, HandleTimeOfDay));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_SHOW_FLAGS, URHO3D_HANDLER(CryptoCityUrho3D, HandleShowFlags));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_GET_MUSIC_TRACKS, URHO3D_HANDLER(CryptoCityUrho3D, HandleGetMusicTracks));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_REQUEST_PLAYER_DATA, URHO3D_HANDLER(CryptoCityUrho3D, HandleRequestPlayerData));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_SET_NODE_CONFIG, URHO3D_HANDLER(CryptoCityUrho3D, HandleSetNodeConfig));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_FOUND_CITY, URHO3D_HANDLER(CryptoCityUrho3D, HandleFoundCity));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_TRAVEL_TO_CITY, URHO3D_HANDLER(CryptoCityUrho3D, HandleTravelToCity));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_GIVE_ITEMS, URHO3D_HANDLER(CryptoCityUrho3D, HandleGiveItems));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_GIVE_MONEY, URHO3D_HANDLER(CryptoCityUrho3D, HandleGiveMoney));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_GIVE_FLAG, URHO3D_HANDLER(CryptoCityUrho3D, HandleGiveFlag));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_NEW_ITEM, URHO3D_HANDLER(CryptoCityUrho3D, HandleNewItem));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_DIVIDEND, URHO3D_HANDLER(CryptoCityUrho3D, HandleDividend));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_NEW_MORTGAGE, URHO3D_HANDLER(CryptoCityUrho3D, HandleNewMortgage));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_EVENT_BADGE, URHO3D_HANDLER(CryptoCityUrho3D, HandleEventBadge));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_GET_IGNORE_SETTINGS, URHO3D_HANDLER(CryptoCityUrho3D, HandleGetIgnoreSettings));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_SET_IGNORE_SETTINGS, URHO3D_HANDLER(CryptoCityUrho3D, HandleSetIgnoreSettings));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_CAMERA_TYPE, URHO3D_HANDLER(CryptoCityUrho3D, HandleCameraType));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_GROUND_TYPE, URHO3D_HANDLER(CryptoCityUrho3D, HandleGroundType));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_RESIZE_FLAG, URHO3D_HANDLER(CryptoCityUrho3D, HandleResizeFlag));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_CONTROLS_CHANGED, URHO3D_HANDLER(CryptoCityUrho3D, HandleControlsChanged));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_HUNT, URHO3D_HANDLER(CryptoCityUrho3D, HandleHunt));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_GET_TEXTURES, URHO3D_HANDLER(CryptoCityUrho3D, HandleGetTextures));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_PALETTE, URHO3D_HANDLER(CryptoCityUrho3D, HandlePalette));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_FIGHT_FIRE, URHO3D_HANDLER(CryptoCityUrho3D, HandleFightFire));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_SERVICE_PRICE, URHO3D_HANDLER(CryptoCityUrho3D, HandleServicePrice));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_SOW, URHO3D_HANDLER(CryptoCityUrho3D, HandleSow));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_HARVEST, URHO3D_HANDLER(CryptoCityUrho3D, HandleHarvest));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_GENERATE_INVITATION, URHO3D_HANDLER(CryptoCityUrho3D, HandleGenerateInvitation));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_ACCEPT_INVITATION, URHO3D_HANDLER(CryptoCityUrho3D, HandleAcceptInvitation));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_REQUEST_INVITATION_STATUS, URHO3D_HANDLER(CryptoCityUrho3D, HandleRequestInvitationStatus));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_CREATE_NEW_WALLET, URHO3D_HANDLER(CryptoCityUrho3D, HandleCreateNewWallet));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_START_MINING, URHO3D_HANDLER(CryptoCityUrho3D, HandleStartMining));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_STOP_MINING, URHO3D_HANDLER(CryptoCityUrho3D, HandleStopMining));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_MUSIC_TRACK, URHO3D_HANDLER(CryptoCityUrho3D, HandleMusicTrack));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_MUSIC_VOLUME, URHO3D_HANDLER(CryptoCityUrho3D, HandleMusicVolume));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_MUSIC_SKIP_TRACK, URHO3D_HANDLER(CryptoCityUrho3D, HandleMusicSkipTrack));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_INCLUDE_UNMATCHED_TRADES_IN_QUEUED_COMMANDS, URHO3D_HANDLER(CryptoCityUrho3D, HandleIncludeUnmatchedTradesInQueuedComands));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_COMPASS, URHO3D_HANDLER(CryptoCityUrho3D, HandleCompass));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_CALENDAR, URHO3D_HANDLER(CryptoCityUrho3D, HandleCalendar));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_THERMOMETER, URHO3D_HANDLER(CryptoCityUrho3D, HandleThermometer));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_NOTIFICATION_LIFETIME, URHO3D_HANDLER(CryptoCityUrho3D, HandleNotificationLifetime));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_SFX_VOLUME, URHO3D_HANDLER(CryptoCityUrho3D, HandleSFXVolume));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_PLAY_SFX_ON_NEW_BLOCK, URHO3D_HANDLER(CryptoCityUrho3D, HandlePlaySFXOnNewBlock));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_RESTORE_MODEL_BACKUP, URHO3D_HANDLER(CryptoCityUrho3D, HandleRestoreModelBackup));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_MAIN_PANEL_GEOMETRY, URHO3D_HANDLER(CryptoCityUrho3D, HandleMainPanelGeometry));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_MINT, URHO3D_HANDLER(CryptoCityUrho3D, HandleMint));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_SMELT, URHO3D_HANDLER(CryptoCityUrho3D, HandleSmelt));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_CANCEL_NONCE, URHO3D_HANDLER(CryptoCityUrho3D, HandleCancelNonce));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_PLACE_MODEL, URHO3D_HANDLER(CryptoCityUrho3D, HandlePlaceModel));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_NEW_SCRIPT, URHO3D_HANDLER(CryptoCityUrho3D, HandleNewScript));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_PLAY_SCRIPT, URHO3D_HANDLER(CryptoCityUrho3D, HandlePlayScript));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_ENABLE_SCRIPT, URHO3D_HANDLER(CryptoCityUrho3D, HandleEnableScript));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_SCRIPT_CHOICE, URHO3D_HANDLER(CryptoCityUrho3D, HandleScriptChoice));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_SET_GLOBAL_VARIABLE, URHO3D_HANDLER(CryptoCityUrho3D, HandleSetGlobalVariable));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_GET_GLOBAL_VARIABLE, URHO3D_HANDLER(CryptoCityUrho3D, HandleGetGlobalVariable));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_CHOP_WOOD, URHO3D_HANDLER(CryptoCityUrho3D, HandleChopWood));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_CARVE_RUNESTONE, URHO3D_HANDLER(CryptoCityUrho3D, HandleCarveRunestone));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_NEW_AUCTION_FLAG, URHO3D_HANDLER(CryptoCityUrho3D, HandleNewAuctionFlag));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_NEW_AUCTION_ITEM, URHO3D_HANDLER(CryptoCityUrho3D, HandleNewAuctionItem));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_BID_ON_AUCTION, URHO3D_HANDLER(CryptoCityUrho3D, HandleBidOnAuction));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_ALLOW_STYLING, URHO3D_HANDLER(CryptoCityUrho3D, HandleAllowStyling));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_CONTROL_DAEMON, URHO3D_HANDLER(CryptoCityUrho3D, HandleControlDaemon));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_RESTART_DAEMON, URHO3D_HANDLER(CryptoCityUrho3D, HandleRestartDaemon));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_ENABLE_TUTORIAL, URHO3D_HANDLER(CryptoCityUrho3D, HandleEnableTutorial));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_RESET_TUTORIAL, URHO3D_HANDLER(CryptoCityUrho3D, HandleResetTutorial));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_GET_TUTORIALS, URHO3D_HANDLER(CryptoCityUrho3D, HandleGetTutorials));
|
|
SubscribeToEvent(ui, E_CRYPTOCITY_TUTORIAL_TRIGGER, URHO3D_HANDLER(CryptoCityUrho3D, HandleTutorialTrigger));
|
|
SubscribeToEvent(&gameState, E_CRYPTOCITY_REQUEST_PLAYER_DATA, URHO3D_HANDLER(CryptoCityUrho3D, HandleRequestPlayerData));
|
|
SubscribeToEvent(&gameState, E_CRYPTOCITY_REQUEST_ITEM_DATA, URHO3D_HANDLER(CryptoCityUrho3D, HandleRequestItemData));
|
|
SubscribeToEvent(&gameState, E_CRYPTOCITY_REQUEST_SCRIPT_DATA, URHO3D_HANDLER(CryptoCityUrho3D, HandleRequestScriptData));
|
|
SubscribeToEvent(&gameState, E_CRYPTOCITY_REQUEST_BADGE_DATA, URHO3D_HANDLER(CryptoCityUrho3D, HandleRequestBadgeData));
|
|
SubscribeToEvent(&gameState, E_CRYPTOCITY_GAME_NOTIFICATION, URHO3D_HANDLER(CryptoCityUrho3D, HandleGameNotification));
|
|
SubscribeToEvent(&gameState, E_CRYPTOCITY_TUTORIAL_TRIGGER, URHO3D_HANDLER(CryptoCityUrho3D, HandleTutorialTrigger));
|
|
SubscribeToEvent(console, E_UI_CONSOLE_COMMAND, URHO3D_HANDLER(CryptoCityUrho3D, HandleConsoleCommand));
|
|
SubscribeToEvent(console, E_UI_CONSOLE_GET_FILTER, URHO3D_HANDLER(CryptoCityUrho3D, HandleGetConsoleFilter));
|
|
SubscribeToEvent(console, E_UI_CONSOLE_SET_FILTER, URHO3D_HANDLER(CryptoCityUrho3D, HandleSetConsoleFilter));
|
|
SubscribeToEvent(console, E_UI_CONSOLE_SOURCE, URHO3D_HANDLER(CryptoCityUrho3D, HandleConsoleSource));
|
|
SubscribeToEvent(&audio, E_AUDIO_PLAYING_TRACK, URHO3D_HANDLER(CryptoCityUrho3D, HandleAudioPlayingTrack));
|
|
|
|
ui->Configure();
|
|
|
|
LoadControls();
|
|
}
|
|
|
|
void CryptoCityUrho3D::LoadEffects()
|
|
{
|
|
auto* cache = GetSubsystem<ResourceCache>();
|
|
auto* graphics = GetSubsystem<Graphics>();
|
|
|
|
// post-process glow
|
|
SharedPtr<RenderPath> effectRenderPath = viewport->GetRenderPath()->Clone();
|
|
effectRenderPath->Append(cache->GetResource<XMLFile>("MaterialEffects/PostProcess/Glow.xml"));
|
|
|
|
// set BlurHInvSize to proper value
|
|
effectRenderPath->SetShaderParameter("BlurHInvSize", Vector2(1.0f/(float)(graphics->GetWidth()), 1.0f/(float)(graphics->GetHeight())));
|
|
effectRenderPath->SetEnabled("Glow", true);
|
|
viewport->SetRenderPath(effectRenderPath);
|
|
}
|
|
|
|
void CryptoCityUrho3D::InitMouseMode(MouseMode mode)
|
|
{
|
|
useMouseMode_ = mode;
|
|
|
|
Input* input = GetSubsystem<Input>();
|
|
|
|
if (GetPlatform() != "Web")
|
|
{
|
|
if (useMouseMode_ == MM_FREE)
|
|
input->SetMouseVisible(true);
|
|
|
|
if (useMouseMode_ != MM_ABSOLUTE)
|
|
{
|
|
input->SetMouseMode(useMouseMode_);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
input->SetMouseVisible(true);
|
|
SubscribeToEvent(E_MOUSEBUTTONDOWN, URHO3D_HANDLER(CryptoCityUrho3D, HandleMouseModeRequest));
|
|
SubscribeToEvent(E_MOUSEMODECHANGED, URHO3D_HANDLER(CryptoCityUrho3D, HandleMouseModeChange));
|
|
}
|
|
}
|
|
|
|
// If the user clicks the canvas, attempt to switch to relative mouse mode on web platform
|
|
void CryptoCityUrho3D::HandleMouseModeRequest(StringHash /*eventType*/, VariantMap& eventData)
|
|
{
|
|
Input* input = GetSubsystem<Input>();
|
|
if (useMouseMode_ == MM_ABSOLUTE)
|
|
input->SetMouseVisible(false);
|
|
else if (useMouseMode_ == MM_FREE)
|
|
input->SetMouseVisible(true);
|
|
input->SetMouseMode(useMouseMode_);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleMouseModeChange(StringHash /*eventType*/, VariantMap& eventData)
|
|
{
|
|
Input* input = GetSubsystem<Input>();
|
|
bool mouseLocked = eventData[MouseModeChanged::P_MOUSELOCKED].GetBool();
|
|
input->SetMouseVisible(!mouseLocked);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
IntVector2 pos = GetSubsystem<Urho3D::UI>()->GetCursorPosition();
|
|
Input* input = GetSubsystem<Input>();
|
|
|
|
TBWidget *widget = UTBRendererBatcher::Singleton().Root().GetWidgetAt(pos.x_, pos.y_, true);
|
|
if (widget && ui->IsClickPassthroughWidget(widget))
|
|
widget = NULL;
|
|
|
|
if (!widget)
|
|
{
|
|
if (input->GetMouseButtonDown(MOUSEB_RIGHT))
|
|
rightButtonPressed = true;
|
|
if (input->GetMouseButtonDown(MOUSEB_LEFT))
|
|
leftButtonPressed = true;
|
|
}
|
|
|
|
if (UITBWindow::HasModalDialog() || TBWidget::focused_widget || widget || ui->IsSelectMaterialOpen())
|
|
return;
|
|
|
|
if (input->GetMouseButtonDown(MOUSEB_LEFT))
|
|
{
|
|
Drawable *d = NULL;
|
|
uint32_t tile_x, tile_y, tile_h;
|
|
uint32_t top_x, top_y, top_h;
|
|
Vector3 point;
|
|
if (Pick(d, tile_x, tile_y, tile_h, top_x, top_y, top_h, &point))
|
|
{
|
|
sx0r = sx1r = sx0 = sx1 = tile_x;
|
|
sy0r = sy1r = sy0 = sy1 = tile_y;
|
|
if (input->GetQualifierDown(QUAL_SHIFT))
|
|
{
|
|
base_selection = selection;
|
|
selectionMode = SM_UNION;
|
|
}
|
|
else if (input->GetQualifierDown(QUAL_CTRL))
|
|
{
|
|
base_selection = selection;
|
|
selectionMode = SM_DIFFERENCE;
|
|
}
|
|
else
|
|
{
|
|
base_selection.clear();
|
|
selectionMode = SM_NORMAL;
|
|
}
|
|
selection.set_rectangle(sx0, sy0, sx1, sy1);
|
|
hasSelection_ = true;
|
|
|
|
if (camera_)
|
|
camera_->set_target(point);
|
|
|
|
TriggerIfRunestone();
|
|
}
|
|
else
|
|
{
|
|
selection.clear();
|
|
hasSelection_ = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleMouseButtonUp(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
Input* input = GetSubsystem<Input>();
|
|
if (!input->GetMouseButtonDown(MOUSEB_RIGHT))
|
|
rightButtonPressed = false;
|
|
if (!input->GetMouseButtonDown(MOUSEB_LEFT))
|
|
leftButtonPressed = false;
|
|
selection = GetEffectiveSelection();
|
|
selectionMode = SM_NORMAL;
|
|
|
|
if (!selectionTutorialTriggered_ && selection.get_num_selected_points() > 1)
|
|
{
|
|
VariantMap eventData;
|
|
eventData[TutorialTrigger::P_TAG] = "selection";
|
|
HandleTutorialTrigger(E_CRYPTOCITY_TUTORIAL_TRIGGER, eventData);
|
|
selectionTutorialTriggered_ = true;
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleMouseMotion(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UpdateTargetting();
|
|
}
|
|
|
|
void CryptoCityUrho3D::UpdateTargetting()
|
|
{
|
|
if (GetSubsystem<Urho3D::UI>()->GetFocusElement() || TBWidget::focused_widget || UITBWindow::HasModalDialog() || ui->IsSelectMaterialOpen())
|
|
{
|
|
CloseBuildingTooltip();
|
|
CloseSelectionTooltip();
|
|
return;
|
|
}
|
|
|
|
Drawable *d = NULL;
|
|
uint32_t tile_x, tile_y, tile_h;
|
|
uint32_t t_x, t_y, t_h;
|
|
if (Pick(d, tile_x, tile_y, tile_h, t_x, t_y, t_h))
|
|
{
|
|
mouse_x = tile_x;
|
|
mouse_y = tile_y;
|
|
mouse_h = tile_h;
|
|
top_x = t_x;
|
|
top_y = t_y;
|
|
top_h = t_h;
|
|
std::shared_ptr<Flag> hover_flag = GetHoverFlag();
|
|
if (leftButtonPressed)
|
|
{
|
|
sx1r = tile_x;
|
|
sy1r = tile_y;
|
|
if (sx0r == 0)
|
|
sx0r = sx1r;
|
|
if (sy0r == 0)
|
|
sy0r = sy1r;
|
|
sx0 = std::min(sx0r, sx1r);
|
|
sy0 = std::min(sy0r, sy1r);
|
|
sx1 = std::max(sx0r, sx1r);
|
|
sy1 = std::max(sy0r, sy1r);
|
|
selection.set_rectangle(sx0, sy0, sx1, sy1);
|
|
if (editMode == EM_SELECT_ELLIPSE)
|
|
selection.make_circular();
|
|
}
|
|
auto* ui = GetSubsystem<Urho3D::UI>();
|
|
IntVector2 pos = buildMode_ ? IntVector2(ui->GetRoot()->GetSize() / 2) : ui->GetCursorPosition();
|
|
TBWidget *w = UTBRendererBatcher::Singleton().Root().GetWidgetAt(pos.x_, pos.y_, true);
|
|
if (!hover_flag || !this->ui->IsFlagTooltipPassthrough(w))
|
|
{
|
|
CloseBuildingTooltip();
|
|
}
|
|
else if (!building_tooltip || hover_flag != building_tooltip_flag)
|
|
{
|
|
CloseBuildingTooltip();
|
|
std::string text =
|
|
game_util::get_building_name(&gameState, hover_flag) + "\n" +
|
|
gameState.get_player_name(hover_flag->owner) + "\n" +
|
|
cc::get_role_name(hover_flag->role);
|
|
const auto it = flags_for_sale.find(hover_flag->id);
|
|
if (it != flags_for_sale.end())
|
|
text += "\nFor sale, price: " + cryptonote::print_money(it->second);
|
|
if (hover_flag->fire_state > 0 && hover_flag->fire_state < 128)
|
|
text += "\nOn fire!";
|
|
building_tooltip = new UITooltip(context_, -1, -1, text);
|
|
building_tooltip_flag = hover_flag;
|
|
SubscribeToEvent(building_tooltip, E_TB_WINDOW_DEAD, [this](StringHash eventType, VariantMap& eventData){ building_tooltip = NULL; building_tooltip_flag = NULL; });
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mouse_x = 0;
|
|
mouse_y = 0;
|
|
mouse_h = 0;
|
|
if (building_tooltip)
|
|
{
|
|
CloseBuildingTooltip();
|
|
}
|
|
}
|
|
|
|
const uint32_t sel_w = selection.x1 - selection.x0 + 1;
|
|
const uint32_t sel_h = selection.y1 - selection.y0 + 1;
|
|
if (leftButtonPressed && hasSelection_ && (sel_w > 1 || sel_h > 1))
|
|
{
|
|
if (!selection_tooltip)
|
|
{
|
|
selection_tooltip = new UITooltip(context_, -1, -1, "");
|
|
selection_tooltip->SetSkinBg(TBIDC("SelectionTooltipWindow"));
|
|
selection_tooltip->SetTop(true);
|
|
SubscribeToEvent(selection_tooltip, E_TB_WINDOW_DEAD, [this](StringHash eventType, VariantMap& eventData){ selection_tooltip = NULL; });
|
|
}
|
|
std::string s = std::to_string(sel_w) + " x " + std::to_string(sel_h);
|
|
bool error = sel_w > 256 || sel_h > 256;
|
|
if (!error)
|
|
{
|
|
std::set<std::shared_ptr<Flag>> flags;
|
|
map.get_flags(selection.x0, selection.y0, selection.x1, selection.y1, flags);
|
|
if (flags.size() > 1)
|
|
error = true;
|
|
else if (flags.size() == 1)
|
|
{
|
|
const std::shared_ptr<Flag> f = *flags.begin();
|
|
const uint32_t squares = cc::intersection_squares(f->x0, f->y0, f->x1, f->y1, selection.x0, selection.y0, selection.x1, selection.y1);
|
|
if (squares != sel_w * sel_h)
|
|
error = true;
|
|
}
|
|
|
|
if (flags.empty())
|
|
{
|
|
uint32_t ox, oy;
|
|
uint64_t cost = 0;
|
|
cc::get_city_origin(0, ox, oy);
|
|
if (!cc::get_new_flag_cost(selection.x0, selection.y0, selection.x1, selection.y1, ox, oy, cost))
|
|
cost = 0;
|
|
const bool too_expensive = cost > gameState.playerState.balance;
|
|
s += std::string("\n") + (too_expensive ? "<color #f00000>" : "") + "Cost: " + cryptonote::print_money(cost) + (too_expensive ? "</color>" : "");
|
|
}
|
|
if (error)
|
|
s = "<color #f04040>" + s + "</color>";
|
|
}
|
|
selection_tooltip->SetText(s.c_str());
|
|
}
|
|
else
|
|
{
|
|
CloseSelectionTooltip();
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::InitTouchInput()
|
|
{
|
|
touchEnabled_ = true;
|
|
|
|
ResourceCache* cache = GetSubsystem<ResourceCache>();
|
|
Input* input = GetSubsystem<Input>();
|
|
XMLFile* layout = cache->GetResource<XMLFile>("UI/ScreenJoystick_Samples.xml");
|
|
const String& patchString = GetScreenJoystickPatchString();
|
|
if (!patchString.Empty())
|
|
{
|
|
// Patch the screen joystick layout further on demand
|
|
SharedPtr<XMLFile> patchFile(new XMLFile(context_));
|
|
if (patchFile->FromString(patchString))
|
|
layout->Patch(patchFile);
|
|
}
|
|
screenJoystickIndex_ = (unsigned)input->AddScreenJoystick(layout, cache->GetResource<XMLFile>("UI/DefaultStyle.xml"));
|
|
input->SetScreenJoystickVisible(screenJoystickSettingsIndex_, true);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleTouchBegin(StringHash /*eventType*/, VariantMap& eventData)
|
|
{
|
|
// On some platforms like Windows the presence of touch input can only be detected dynamically
|
|
InitTouchInput();
|
|
UnsubscribeFromEvent("TouchBegin");
|
|
}
|
|
|
|
void CryptoCityUrho3D::EnableShadows(TBID method)
|
|
{
|
|
const bool enable = method != TBIDC("shadows-off");
|
|
Light *light = mainLightNode->GetComponent<Light>();
|
|
if (light)
|
|
{
|
|
light->SetCastShadows(enable);
|
|
light->SetShadowBias(BiasParameters(0.00325f, 0.5f));
|
|
light->SetPerVertex(!enable);
|
|
light->SetShadowIntensity(0.35f);
|
|
}
|
|
|
|
auto* renderer = GetSubsystem<Renderer>();
|
|
|
|
renderer->SetDrawShadows(enable);
|
|
|
|
if (method == TBIDC("shadows-off"));
|
|
else if (method == TBIDC("shadows-simple-16bit")) renderer->SetShadowQuality(SHADOWQUALITY_SIMPLE_16BIT);
|
|
else if (method == TBIDC("shadows-simple-24bit")) renderer->SetShadowQuality(SHADOWQUALITY_SIMPLE_24BIT);
|
|
else if (method == TBIDC("shadows-pcf-16bit")) renderer->SetShadowQuality(SHADOWQUALITY_PCF_16BIT);
|
|
else if (method == TBIDC("shadows-pcf-24bit")) renderer->SetShadowQuality(SHADOWQUALITY_PCF_24BIT);
|
|
else if (method == TBIDC("shadows-vsm")) renderer->SetShadowQuality(SHADOWQUALITY_VSM);
|
|
else if (method == TBIDC("shadows-blur-vsm")) renderer->SetShadowQuality(SHADOWQUALITY_BLUR_VSM);
|
|
else printf("Invalid shadow quality type\n");
|
|
}
|
|
|
|
void CryptoCityUrho3D::SetShadowMapSize(TBID size)
|
|
{
|
|
auto* renderer = GetSubsystem<Renderer>();
|
|
|
|
if (size == TBIDC("shadowmap-2048")) renderer->SetShadowMapSize(2048);
|
|
else if (size == TBIDC("shadowmap-1024")) renderer->SetShadowMapSize(1024);
|
|
else if (size == TBIDC("shadowmap-512")) renderer->SetShadowMapSize(512);
|
|
else printf("Invalid shadow map size\n");
|
|
}
|
|
|
|
void CryptoCityUrho3D::EnableClouds(bool enable)
|
|
{
|
|
if (enable)
|
|
{
|
|
if (clouds.GetNumActiveClouds() == 0)
|
|
{
|
|
clouds.SetWind(Vector3(10, 0, 4), Vector3(6, 0, 3));
|
|
clouds.SetClouds(40);
|
|
clouds.Init(scene_, cameraNode_->GetPosition());
|
|
}
|
|
if (cloudsLightNode_)
|
|
scene_->AddChild(cloudsLightNode_);
|
|
}
|
|
else
|
|
{
|
|
clouds.SetClouds(0);
|
|
clouds.RemoveAll();
|
|
if (cloudsLightNode_)
|
|
scene_->RemoveChild(cloudsLightNode_);
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::SetMaxActiveFPS(unsigned fps)
|
|
{
|
|
engine_->SetMaxFps(fps);
|
|
}
|
|
|
|
void CryptoCityUrho3D::SetMaxInactiveFPS(unsigned fps)
|
|
{
|
|
engine_->SetMaxInactiveFps(fps);
|
|
}
|
|
|
|
void CryptoCityUrho3D::SetFontSize(unsigned size)
|
|
{
|
|
TBFontDescription fd;
|
|
#ifdef TB_FONT_RENDERER_TBBF
|
|
fd.SetID(TBIDC("Segoe"));
|
|
#else
|
|
fd.SetID(TBIDC("Vera"));
|
|
#endif
|
|
fd.SetSize(g_tb_skin->GetDimensionConverter()->DpToPx(size));
|
|
g_font_manager->SetDefaultFontDescription(fd);
|
|
|
|
// Create the font if needed.
|
|
TBFontFace *font = NULL;
|
|
if (!g_font_manager->HasFontFace(g_font_manager->GetDefaultFontDescription()))
|
|
font = g_font_manager->CreateFontFace(g_font_manager->GetDefaultFontDescription());
|
|
|
|
// Render some glyphs in one go now since we know we are going to use them. It would work fine
|
|
// without this since glyphs are rendered when needed, but with some extra updating of the glyph bitmap.
|
|
if ( font )
|
|
{
|
|
font->RenderGlyphs(" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~•·åäöÅÄÖ");
|
|
}
|
|
|
|
UTBRendererBatcher::Singleton().Root().InvokeFontChanged();
|
|
}
|
|
|
|
void CryptoCityUrho3D::SetViewDistance(unsigned distance)
|
|
{
|
|
cityMesh->SetViewDistance(distance);
|
|
|
|
Node* zoneNode = scene_->GetChild("Zone");
|
|
auto* zone = zoneNode ? zoneNode->GetComponent<Zone>() : NULL;
|
|
if (zone)
|
|
{
|
|
const float D = SECTION_SIZE * (distance + 0.5f);
|
|
zone->SetBoundingBox(BoundingBox(Vector3(-D * 2, -1.0f, -D * 2), Vector3(D * 2, 80000.0f, D * 2)));
|
|
zone->SetFogStart(D * .85f);
|
|
zone->SetFogEnd(D);
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::EnableHorizon(bool enable)
|
|
{
|
|
horizon.Enable(enable);
|
|
}
|
|
|
|
void CryptoCityUrho3D::EnableDynamicSky(bool enable)
|
|
{
|
|
auto* cache = GetSubsystem<ResourceCache>();
|
|
|
|
if (enable)
|
|
{
|
|
Node *skyNode = scene_->GetChild("Sky");
|
|
if (skyNode)
|
|
skyNode->Remove();
|
|
|
|
if (!procskyNode)
|
|
{
|
|
procskyNode = scene_->CreateChild("procsky");
|
|
auto procsky = procskyNode->CreateComponent<ProcSky>();
|
|
Node *zoneNode = scene_->GetChild("Zone");
|
|
Zone *zone = zoneNode ? zoneNode->GetComponent<Zone>() : NULL;
|
|
procsky->Initialize("DirectionalLight", zone, "Textures/Space_Skybox/Space_Skybox.xml");
|
|
}
|
|
viewport->GetRenderPath()->SetEnabled("ProcSky", true);
|
|
needSkyUpdate_ = true;
|
|
}
|
|
else
|
|
{
|
|
if (procskyNode)
|
|
{
|
|
viewport->GetRenderPath()->SetEnabled("ProcSky", false);
|
|
procskyNode->Remove();
|
|
procskyNode = NULL;
|
|
}
|
|
|
|
Node* skyNode = scene_->CreateChild("Sky");
|
|
skyNode->SetScale(500.0f); // The scale actually does not matter
|
|
auto* skybox = skyNode->CreateComponent<Skybox>();
|
|
skybox->SetModel(cache->GetResource<Model>("Models/Box.mdl"));
|
|
skybox->SetMaterial(cache->GetResource<Material>("Materials/CryptoCitySkybox.xml"));
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::SetTimeOfDay(float t)
|
|
{
|
|
needSkyUpdate_ = true;
|
|
}
|
|
|
|
void CryptoCityUrho3D::ShowFlags(bool enable)
|
|
{
|
|
cityMesh->setDisplayMode(enable ? DisplayModeShowFlags : DisplayModeNormal);
|
|
}
|
|
|
|
bool CryptoCityUrho3D::SetGraphicsMode(int monitor, int width, int height, int refresh_rate)
|
|
{
|
|
Graphics *graphics = GetSubsystem<Graphics>();
|
|
const bool highDPI = true;
|
|
const bool vsync = true;
|
|
const bool triple_buffer = true;
|
|
const bool multi_sample = true;
|
|
if (monitor < 0)
|
|
{
|
|
width = std::max(width, 640);
|
|
height = std::max(height, 480);
|
|
}
|
|
printf("Setting graphics mode: %dx%d, %s\n",
|
|
width, height, monitor < 0 ? "windowed" : ("fullscreen on monitor " + std::to_string(monitor + 1) + " at " + std::to_string(refresh_rate) + " Hz").c_str());
|
|
return graphics->SetMode(width, height, monitor >= 0, false, monitor < 0, highDPI, vsync, triple_buffer, multi_sample, monitor, refresh_rate);
|
|
}
|
|
|
|
void CryptoCityUrho3D::SetMusicTrack(const String &track)
|
|
{
|
|
VariantVector tracks = GetMusicTracks();
|
|
std::vector<std::pair<String, String>> playlist;
|
|
if (track == "music-track-none")
|
|
{
|
|
}
|
|
else if (track == "music-track-random")
|
|
{
|
|
for (unsigned i = 0; i < tracks.Size(); ++i)
|
|
playlist.push_back(std::make_pair(tracks[i].GetVariantMap()["filename"]->GetString(), tracks[i].GetVariantMap()["title"]->GetString()));
|
|
}
|
|
else if (track == "music-track-random-no-chiptunes")
|
|
{
|
|
for (unsigned i = 0; i < tracks.Size(); ++i)
|
|
if (!tracks[i].GetVariantMap()["chiptune"]->GetBool())
|
|
playlist.push_back(std::make_pair(tracks[i].GetVariantMap()["filename"]->GetString(), tracks[i].GetVariantMap()["title"]->GetString()));
|
|
}
|
|
else
|
|
{
|
|
for (unsigned i = 0; i < tracks.Size(); ++i)
|
|
if (tracks[i].GetVariantMap()["filename"]->GetString() == track)
|
|
playlist.push_back(std::make_pair(tracks[i].GetVariantMap()["filename"]->GetString(), tracks[i].GetVariantMap()["title"]->GetString()));
|
|
if (playlist.empty())
|
|
printf("Track not found: %s\n", track.CString());
|
|
}
|
|
audio.SetMusicTrack(playlist);
|
|
}
|
|
|
|
void CryptoCityUrho3D::SetMusicVolume(float volume)
|
|
{
|
|
audio.SetMusicVolume(volume);
|
|
}
|
|
|
|
void CryptoCityUrho3D::SetSFXVolume(float volume)
|
|
{
|
|
audio.SetSFXVolume(volume);
|
|
}
|
|
|
|
void CryptoCityUrho3D::SetPlaySFXOnNewBlock(bool play)
|
|
{
|
|
play_sfx_on_new_block = play;
|
|
}
|
|
|
|
void CryptoCityUrho3D::SetControlDaemon(bool control)
|
|
{
|
|
if (control && !daemon_controller->IsRunning())
|
|
{
|
|
std::vector<std::string> errors = daemon_controller->Start();
|
|
if (!errors.empty())
|
|
{
|
|
errors.insert(errors.begin(), "Error starting townforged - you will have to run it manuallly");
|
|
new MessageBox(context_, boost::join(errors, "\n"));
|
|
}
|
|
}
|
|
else if (!control && daemon_controller->IsControlling())
|
|
{
|
|
if (!daemon_controller->Stop())
|
|
new MessageBox(context_, "Failed to stop daemon");
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::SetEnableTutorial(bool enable)
|
|
{
|
|
}
|
|
|
|
void CryptoCityUrho3D::SetCompass(TBID compass)
|
|
{
|
|
ui->SetCompassType(compass);
|
|
}
|
|
|
|
void CryptoCityUrho3D::SetCalendar(TBID calendar)
|
|
{
|
|
ui->SetCalendarType(calendar);
|
|
}
|
|
|
|
void CryptoCityUrho3D::SetThermometer(TBID thermometer)
|
|
{
|
|
ui->SetThermometerType(thermometer);
|
|
}
|
|
|
|
void CryptoCityUrho3D::SetNotificationLifetime(unsigned seconds)
|
|
{
|
|
ui->SetNotificationLifetime(seconds);
|
|
}
|
|
|
|
void CryptoCityUrho3D::CreateScene()
|
|
{
|
|
if (!scene_)
|
|
{
|
|
scene_ = new Scene(context_);
|
|
viewport->SetScene(scene_);
|
|
}
|
|
else
|
|
{
|
|
scene_->Clear();
|
|
}
|
|
|
|
scene_->CreateComponent<DebugRenderer>();
|
|
|
|
// Create the Octree component to the scene so that drawable objects can be rendered. Use default volume
|
|
// (-1000, -1000, -1000) to (1000, 1000, 1000)
|
|
auto octree = scene_->CreateComponent<Octree>();
|
|
octree->SetSize(BoundingBox({-100000, -1000, -100000}, {100000, 66000, 100000}), octree->GetNumLevels() + 2);
|
|
|
|
// Create a Zone for ambient light & fog control
|
|
Node* zoneNode = scene_->CreateChild("Zone");
|
|
auto* zone = zoneNode->CreateComponent<Zone>();
|
|
zone->SetBoundingBox(BoundingBox(Vector3(-2000.0f, -1.0f, -2000.0f), Vector3(2000.0f, 80000.0f, 2000.0f)));
|
|
zone->SetAmbientColor(Color(0.7f, 0.7f, 0.7f));
|
|
zone->SetFogColor(Color(0.2f, 0.2f, 0.2f));
|
|
// fog distance will be set by SetViewDistance
|
|
zone->SetPriority(100);
|
|
|
|
// Create a Zone for clouds only
|
|
Node* cloudsZoneNode = scene_->CreateChild("CloudsZone");
|
|
auto* cloudsZone = cloudsZoneNode->CreateComponent<Zone>();
|
|
cloudsZone->SetBoundingBox(BoundingBox(Vector3(-200000.0f, -1000.0f, -200000.0f), Vector3(200000.0f, 2000000.0f, 200000.0f)));
|
|
cloudsZone->SetLightMask(0xffff | LIGHT_MASK_CLOUDS);
|
|
cloudsZone->SetFogStart(2000000.0f);
|
|
cloudsZone->SetFogEnd(2500000.0f);
|
|
cloudsZone->SetPriority(20);
|
|
|
|
// Create a node for the directional light, UpdateSky will take care of the rest
|
|
mainLightNode = scene_->CreateChild("DirectionalLight");
|
|
|
|
// A light just for clouds
|
|
cloudsLightNode_ = scene_->CreateChild("CloudsLight");
|
|
Light *cloudsLight = cloudsLightNode_->CreateComponent<Light>();
|
|
cloudsLight->SetLightMask(LIGHT_MASK_CLOUDS);
|
|
cloudsLight->SetLightType(LIGHT_POINT);
|
|
cloudsLight->SetColor(Color(1.0f, .0f, .0f, 1.0f));
|
|
cloudsLight->SetSpecularIntensity(0.0f);
|
|
cloudsLight->SetRange(CLOUDS_LIGHT_DISTANCE * 0.95f);
|
|
cloudsLight->SetCastShadows(false);
|
|
cloudsLight->SetPerVertex(true);
|
|
|
|
Node *horizonNode = scene_->CreateChild("HorizonNode");
|
|
horizon.Init(horizonNode, 2500, 1600, 1900);
|
|
|
|
scene_->CreateChild("boids");
|
|
|
|
uint32_t ox, oy;
|
|
cc::get_city_origin(0, ox, oy);
|
|
|
|
// add the city mesh
|
|
if (!cityMesh)
|
|
{
|
|
cityNode = scene_->CreateChild("City");
|
|
cityMesh = new CityMeshUrho3D(cityNode, &gameState);
|
|
}
|
|
RebuildMap();
|
|
|
|
// Add the camera node to the scene and attach a camera controller
|
|
cameraNode_->SetScene(scene_);
|
|
camera_ = new WalkerCamera(cameraNode_);
|
|
camera_->set_target(Vector3::ZERO);
|
|
camera_->set_collidable_root(cityMesh->GetSelectableNode());
|
|
ui->SetCameraType(TBIDC("walker"));
|
|
|
|
EnableShadows(GetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_SHADOWS, DEFAULT_SHADOWS));
|
|
SetShadowMapSize(GetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_SHADOW_MAP_SIZE, DEFAULT_SHADOW_MAP_SIZE));
|
|
EnableClouds(GetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_CLOUDS, DEFAULT_CLOUDS));
|
|
EnableHorizon(GetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_HORIZON, DEFAULT_HORIZON));
|
|
EnableDynamicSky(GetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_DYNAMIC_SKY, DEFAULT_DYNAMIC_SKY));
|
|
SetTimeOfDay(GetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_TIME_OF_DAY, DEFAULT_TIME_OF_DAY));
|
|
ShowFlags(GetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_SHOW_FLAGS, DEFAULT_SHOW_FLAGS));
|
|
SetMusicTrack(GetConfigValue(CONFIG_AUDIO_SECTION, CONFIG_MUSIC_TRACK, DEFAULT_MUSIC_TRACK));
|
|
SetMusicVolume(GetConfigValue(CONFIG_AUDIO_SECTION, CONFIG_MUSIC_VOLUME, DEFAULT_MUSIC_VOLUME));
|
|
SetCompass(GetConfigValue(CONFIG_UI_SECTION, CONFIG_COMPASS, DEFAULT_COMPASS));
|
|
SetCalendar(GetConfigValue(CONFIG_UI_SECTION, CONFIG_CALENDAR, DEFAULT_CALENDAR));
|
|
SetThermometer(GetConfigValue(CONFIG_UI_SECTION, CONFIG_THERMOMETER, DEFAULT_THERMOMETER));
|
|
SetNotificationLifetime(GetConfigValue(CONFIG_UI_SECTION, CONFIG_NOTIFICATION_LIFTIME, DEFAULT_NOTIFICATION_LIFTIME));
|
|
SetSFXVolume(GetConfigValue(CONFIG_AUDIO_SECTION, CONFIG_SFX_VOLUME, DEFAULT_SFX_VOLUME));
|
|
SetPlaySFXOnNewBlock(GetConfigValue(CONFIG_AUDIO_SECTION, CONFIG_PLAY_SFX_ON_NEW_BLOCK, DEFAULT_PLAY_SFX_ON_NEW_BLOCK));
|
|
SetMaxActiveFPS(GetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_MAX_ACTIVE_FPS, DEFAULT_MAX_ACTIVE_FPS));
|
|
SetMaxInactiveFPS(GetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_MAX_INACTIVE_FPS, DEFAULT_MAX_INACTIVE_FPS));
|
|
SetFontSize(GetConfigValue(CONFIG_UI_SECTION, CONFIG_FONT_SIZE, DEFAULT_FONT_SIZE));
|
|
SetViewDistance(GetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_VIEW_DISTANCE, DEFAULT_VIEW_DISTANCE));
|
|
}
|
|
|
|
void CryptoCityUrho3D::MoveCameraToStartPosition(bool hug_terrain)
|
|
{
|
|
float dy = 0.0f;
|
|
if (hug_terrain)
|
|
{
|
|
const uint32_t x = (uint32_t)(int32_t)floor(startPosition_.x_) + gameState.cityState.ox;
|
|
const uint32_t y = (uint32_t)(int32_t)floor(startPosition_.z_) + gameState.cityState.oy;
|
|
const uint16_t terrain_height = cityMesh->GetTerrainHeight(x, y);
|
|
dy = terrain_height;
|
|
}
|
|
|
|
cameraNode_->SetPosition(startPosition_ + Vector3(0.0f, dy, 0.0f));
|
|
cameraNode_->SetRotation(startRotation_);
|
|
|
|
if (camera_)
|
|
camera_->teleport();
|
|
}
|
|
|
|
void CryptoCityUrho3D::SetupViewport()
|
|
{
|
|
auto* renderer = GetSubsystem<Renderer>();
|
|
|
|
// Create the camera. Create it outside the scene so that we can clear the whole scene without affecting it
|
|
if (!cameraNode_)
|
|
{
|
|
cameraNode_ = new Node(context_);
|
|
cameraNode_->SetPosition(Vector3(0.0f, -399868928.f, 0.0f));
|
|
auto* camera = cameraNode_->CreateComponent<Camera>();
|
|
camera->SetNearClip(0.25f);
|
|
camera->SetFarClip(10000.0f);
|
|
}
|
|
|
|
// Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
|
|
viewport = new Viewport(context_, scene_, cameraNode_->GetComponent<Camera>());
|
|
renderer->SetViewport(0, viewport);
|
|
}
|
|
|
|
void CryptoCityUrho3D::RebuildMap(bool save)
|
|
{
|
|
if (save)
|
|
cityMesh->StartSavingScene("/home/user/tmp");
|
|
std::vector<std::shared_ptr<Flag>> res;
|
|
map.get_all_flags(res);
|
|
std::set<uint32_t> current_flags;
|
|
cityMesh->GetFlags(current_flags);
|
|
for (const auto &f: res)
|
|
current_flags.erase(f->id);
|
|
if (!current_flags.empty())
|
|
cityMesh->ClearFlags(current_flags);
|
|
cityMesh->RebuildFlags(res, GetEffectiveSelection(), false);
|
|
gameState.clear_dirty_flags();
|
|
mark_new_block = true;
|
|
if (save)
|
|
cityMesh->StopSavingScene();
|
|
}
|
|
|
|
void CryptoCityUrho3D::SubscribeToEvents()
|
|
{
|
|
// Subscribe HandleUpdate() function for processing update events
|
|
SubscribeToEvent(E_UPDATE, URHO3D_HANDLER(CryptoCityUrho3D, HandleUpdate));
|
|
}
|
|
|
|
// work around some X11 bug where key repeat sometimes claims a key up event then down again
|
|
bool CryptoCityUrho3D::GetKeyDown(Urho3D::Key key)
|
|
{
|
|
auto* input = GetSubsystem<Input>();
|
|
|
|
bool down = input->GetKeyDown(key);
|
|
bool wasDown = keyWasDown_.find(key) != keyWasDown_.end();
|
|
if (down && !wasDown)
|
|
keyWasDown_.insert(key);
|
|
else if (!down && wasDown)
|
|
keyWasDown_.erase(key);
|
|
return down || wasDown;
|
|
}
|
|
|
|
bool CryptoCityUrho3D::IsActionActive(Controls::Action action)
|
|
{
|
|
const Controls::Input input = Controls::Get().Get(action);
|
|
if (input == Controls::INPUT_NONE)
|
|
return false;
|
|
if (input == Controls::INPUT_MOUSE_LEFT)
|
|
return GetSubsystem<Input>()->GetMouseButtonDown(MOUSEB_LEFT);
|
|
else if (input == Controls::INPUT_MOUSE_MIDDLE)
|
|
return GetSubsystem<Input>()->GetMouseButtonDown(MOUSEB_MIDDLE);
|
|
else if (input == Controls::INPUT_MOUSE_RIGHT)
|
|
return GetSubsystem<Input>()->GetMouseButtonDown(MOUSEB_RIGHT);
|
|
else
|
|
{
|
|
Urho3D::Key key = Controls::Get().GetKeyForInput(input);
|
|
if (key == KEY_UNKNOWN)
|
|
return false;
|
|
return GetKeyDown(key);
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::MoveCamera(float timeStep)
|
|
{
|
|
if (!camera_)
|
|
return;
|
|
|
|
// Do not move if the UI has a focused element (the console)
|
|
if (GetSubsystem<Urho3D::UI>()->GetFocusElement() || TBWidget::focused_widget || UITBWindow::HasModalDialog() || ui->IsSelectMaterialOpen())
|
|
{
|
|
// still update though
|
|
camera_->update(timeStep);
|
|
return;
|
|
}
|
|
|
|
auto* input = GetSubsystem<Input>();
|
|
|
|
// Movement speed as world units per second
|
|
const float MOVE_SPEED = 20.0f;
|
|
// Mouse sensitivity as degrees per pixel
|
|
const float MOUSE_SENSITIVITY = 0.1f;
|
|
// Keyboard sensitivity as degrees per second
|
|
const float KEYBOARD_SENSITIVITY = 25.0f;
|
|
|
|
float yaw = 0.0f, pitch = 0.0f;
|
|
|
|
// Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
|
|
if (rightButtonPressed || buildMode_)
|
|
{
|
|
const IntVector2 mouseMove = input->GetMouseMove();
|
|
const float mouse_sensitivity = exp(Controls::Get().GetMouseSensitivity() / 35.0f);
|
|
yaw += MOUSE_SENSITIVITY * mouseMove.x_ * mouse_sensitivity;
|
|
pitch += MOUSE_SENSITIVITY * mouseMove.y_ * mouse_sensitivity * (Controls::Get().InvertMouseY() ? -1.0f : 1.0f);
|
|
}
|
|
|
|
float speed = 1.0f;
|
|
if (input->GetQualifierDown(QUAL_SHIFT))
|
|
speed *= 10.0f;
|
|
if (input->GetQualifierDown(QUAL_CTRL))
|
|
speed /= 10.0f;
|
|
|
|
const float turn_rate = exp(Controls::Get().GetTurnRate() / 35.0f);
|
|
if (IsActionActive(Controls::ACTION_LOOK_DOWN))
|
|
pitch += KEYBOARD_SENSITIVITY * timeStep * turn_rate;
|
|
if (IsActionActive(Controls::ACTION_LOOK_UP))
|
|
pitch -= KEYBOARD_SENSITIVITY * timeStep * turn_rate;
|
|
if (IsActionActive(Controls::ACTION_LOOK_LEFT))
|
|
yaw -= KEYBOARD_SENSITIVITY * timeStep * turn_rate;
|
|
if (IsActionActive(Controls::ACTION_LOOK_RIGHT))
|
|
yaw += KEYBOARD_SENSITIVITY * timeStep * turn_rate;
|
|
|
|
// Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
|
|
camera_->rotate(yaw, pitch, 0.0f, 1.0f);
|
|
|
|
// Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
|
|
if (IsActionActive(Controls::ACTION_FORWARD))
|
|
camera_->move(Vector3::FORWARD, timeStep, MOVE_SPEED * speed);
|
|
if (IsActionActive(Controls::ACTION_BACKWARD))
|
|
camera_->move(Vector3::BACK, timeStep, MOVE_SPEED * speed);
|
|
if (IsActionActive(Controls::ACTION_STRAFE_LEFT))
|
|
camera_->move(Vector3::LEFT, timeStep, MOVE_SPEED * speed);
|
|
if (IsActionActive(Controls::ACTION_STRAFE_RIGHT))
|
|
camera_->move(Vector3::RIGHT, timeStep, MOVE_SPEED * speed);
|
|
if (IsActionActive(Controls::ACTION_UP))
|
|
camera_->move(Vector3::UP, timeStep, MOVE_SPEED * speed);
|
|
if (IsActionActive(Controls::ACTION_DOWN))
|
|
camera_->move(Vector3::DOWN, timeStep, MOVE_SPEED * speed);
|
|
|
|
camera_->update(timeStep);
|
|
}
|
|
|
|
void CryptoCityUrho3D::CloseBuildingTooltip()
|
|
{
|
|
if (!building_tooltip)
|
|
return;
|
|
building_tooltip->Close();
|
|
building_tooltip = NULL;
|
|
building_tooltip_flag = NULL;
|
|
}
|
|
|
|
void CryptoCityUrho3D::CloseSelectionTooltip()
|
|
{
|
|
if (!selection_tooltip)
|
|
return;
|
|
selection_tooltip->Close();
|
|
selection_tooltip = NULL;
|
|
}
|
|
|
|
bool CryptoCityUrho3D::SendCommand(cryptonote::cc_command_t cmd, uint64_t *nonce)
|
|
{
|
|
if (!wallet->send_command(cmd, nonce))
|
|
return false;
|
|
walletRefresher_.RequestRefresh();
|
|
return true;
|
|
}
|
|
|
|
void CryptoCityUrho3D::UpdateSky()
|
|
{
|
|
const bool realtime_sky = GetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_TIME_OF_DAY, DEFAULT_TIME_OF_DAY) == -1.0f;
|
|
time_t now = time(NULL);
|
|
bool disable_procsky = true;
|
|
|
|
if (realtime_sky)
|
|
{
|
|
const struct tm *tm = localtime(&now);
|
|
timeOfDay = (tm->tm_hour * 3600 + tm->tm_min * 60 + tm->tm_sec + 86400 * 3 / 4) % 86400 * 2.0f * M_PI / 86400;
|
|
}
|
|
else
|
|
{
|
|
timeOfDay = GetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_TIME_OF_DAY, DEFAULT_TIME_OF_DAY);
|
|
}
|
|
|
|
float sin_timeOfDay = sin(timeOfDay);
|
|
float interp = fmax(sin_timeOfDay, 0.0f);
|
|
float evening = fmax(cos(M_PI * .55f + timeOfDay), 0);
|
|
float clouds_reddening = 4 * fmax((cos(M_PI * 1.0f + timeOfDay) - .75f), 0.0f);
|
|
clouds_reddening *= clouds_reddening * clouds_reddening * clouds_reddening;
|
|
clouds_reddening *= clouds_reddening * clouds_reddening * clouds_reddening;
|
|
|
|
Color DARK_SUN(0.0, 0.0, 0.0);
|
|
//Color LIGHT_SUN(1, .9, .9);
|
|
Color LIGHT_SUN(1.1, 1.1, 1.1);
|
|
Color DARK_FOG(0.0, 0.0, 0.0);
|
|
Color LIGHT_FOG(1, 1, 1);
|
|
Color DARK_AMBIENT(0.2, 0.2, 0.25, 1);
|
|
Color LIGHT_AMBIENT(.5, .4, .45, 1);
|
|
//Color LIGHT_AMBIENT(1, .8, .9, 5);
|
|
Color EVENING_AMBIENT_INFLUENCE(.9, .2, .3, 1);
|
|
Color EVENING_CLOUDS_AMBIENT(1.1, .0, .0, 1);
|
|
|
|
Node *zoneNode = scene_->GetChild("Zone");
|
|
Zone* zone = zoneNode->GetComponent<Zone>();
|
|
Node *cloudsZoneNode = scene_->GetChild("CloudsZone");
|
|
Zone* cloudsZone = cloudsZoneNode->GetComponent<Zone>();
|
|
Light* sun = mainLightNode->GetComponent<Light>();
|
|
Light* cameraLight = cameraNode_->GetComponent<Light>();
|
|
Light* cloudsLight = cloudsLightNode_->GetComponent<Light>();
|
|
|
|
Color ambient = DARK_AMBIENT.Lerp(LIGHT_AMBIENT, interp);
|
|
zone->SetAmbientColor(ambient);
|
|
zone->SetFogColor(DARK_FOG.Lerp(LIGHT_FOG, interp));
|
|
Color cloudsColor = EVENING_CLOUDS_AMBIENT * clouds_reddening;
|
|
Color sunColor = DARK_SUN.Lerp(LIGHT_SUN, interp).Lerp(EVENING_AMBIENT_INFLUENCE, evening);
|
|
|
|
if (interp == 0.0f)
|
|
{
|
|
if (sun)
|
|
{
|
|
sun->Remove();
|
|
sun = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!sun)
|
|
{
|
|
sun = mainLightNode->CreateComponent<Light>();
|
|
sun->SetLightType(LIGHT_DIRECTIONAL);
|
|
EnableShadows(GetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_SHADOWS, DEFAULT_SHADOWS));
|
|
SetShadowMapSize(GetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_SHADOW_MAP_SIZE, DEFAULT_SHADOW_MAP_SIZE));
|
|
}
|
|
}
|
|
|
|
const float cloud_cover = clouds.GetCover(cameraNode_->GetPosition(), mainLightNode->GetRotation() * - Vector3::FORWARD);
|
|
|
|
if (sun)
|
|
{
|
|
sun->SetColor(sunColor);
|
|
sun->SetBrightness(1.0f - cloud_cover / 2);
|
|
sun->SetShadowIntensity(0.35f + cloud_cover * (1.0f - 0.35f));
|
|
sun->SetShadowCascade(CascadeParameters(25.0f, 100.0f, 400.0f, 0.0f, 0.8f));
|
|
}
|
|
|
|
horizon.SetColor(ambient + sunColor * interp);
|
|
|
|
const float cameraLightBrightness = std::max(0.1f - sin_timeOfDay, 0.0f);
|
|
if (enableUserLight_ || cameraLightBrightness > 0.0f)
|
|
{
|
|
if (!cameraLight)
|
|
{
|
|
cameraLight = cameraNode_->CreateComponent<Light>();
|
|
cameraLight->SetLightType(LIGHT_POINT);
|
|
if (enableUserLight_)
|
|
cameraLight->SetColor(Color(.72f, .72f, .54f));
|
|
else
|
|
cameraLight->SetColor(Color(.12f, .12f, .24f));
|
|
cameraLight->SetSpecularIntensity(0.0f);
|
|
cameraLight->SetRange(120);
|
|
cameraLight->SetCastShadows(enableUserLight_);
|
|
cameraLight->SetPerVertex(true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (cameraLight)
|
|
{
|
|
cameraLight->Remove();
|
|
cameraLight = NULL;
|
|
}
|
|
}
|
|
if (cameraLight)
|
|
cameraLight->SetBrightness(enableUserLight_ ? 1.0f : cameraLightBrightness);
|
|
|
|
Quaternion newRot(Quaternion(-90.0f, Vector3::UP) * Quaternion((timeOfDay / M_PI) * 180, Vector3(1, 0.3f, 0)));
|
|
mainLightNode->SetRotation(-newRot); //inverted since we are looking at direction from sun
|
|
|
|
if (procskyNode && needSkyUpdate_)
|
|
{
|
|
auto procsky = procskyNode->GetComponent<ProcSky>();
|
|
if (procsky)
|
|
{
|
|
viewport->GetRenderPath()->SetEnabled("ProcSky", true);
|
|
const float summerness = GetSummerness();
|
|
procsky->Kr_ = {Lerp(0.86f, 0.1887f, summerness), Lerp(0.88f, 0.49784f, summerness), Lerp(0.86f, 0.661606f, summerness)};
|
|
procsky->rayleighBrightness_ = Lerp(0.0f, 3.3f, summerness);
|
|
procsky->scatterStrength_ = Lerp(1.0f, 0.028f, summerness);
|
|
procsky->rayleighStrength_ = Lerp(0.474308f, 0.139f, summerness);
|
|
procsky->mieStrength_ = Lerp(0.0297f, 0.264f, summerness);
|
|
procsky->rayleighCollectionPower_ = Lerp(0.76087f, 0.81f, summerness);
|
|
procsky->mieCollectionPower_ = Lerp(0.434783f, 0.39f, summerness);
|
|
procsky->mieDistribution_ = Lerp(0.652174f, 0.63f, summerness);
|
|
procsky->SetTimeOfDay(timeOfDay);
|
|
procsky->Update();
|
|
needSkyUpdate_ = false;
|
|
disable_procsky = false;
|
|
}
|
|
}
|
|
|
|
Vector3 cloudsLightPosition = newRot * - Vector3::FORWARD * CLOUDS_LIGHT_DISTANCE;
|
|
cloudsLightPosition.y_ -= 1000;
|
|
cloudsLightNode_->SetPosition(cloudsLightPosition);
|
|
cloudsLight->SetColor(cloudsColor);
|
|
float cloudsInterp = std::max(0.0f, std::min(1.0f, (sin_timeOfDay + .1f) * 6));
|
|
Color cloudsAmbient = Color::BLACK.Lerp(Color::WHITE, cloudsInterp);
|
|
cloudsZone->SetAmbientColor(cloudsAmbient);
|
|
|
|
if (disable_procsky)
|
|
viewport->GetRenderPath()->SetEnabled("ProcSky", false);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleUpdate(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
using namespace Update;
|
|
|
|
static uint32_t update_count = 0;
|
|
|
|
if (update_count == 0)
|
|
startupTutorialTimer_.Reset();
|
|
else if (!startupTutorialTriggered_ && startupTutorialTimer_.GetMSec(false) >= 3000)
|
|
{
|
|
VariantMap eventData;
|
|
eventData[TutorialTrigger::P_TAG] = "startup";
|
|
HandleTutorialTrigger(E_CRYPTOCITY_TUTORIAL_TRIGGER, eventData);
|
|
startupTutorialTriggered_ = true;
|
|
}
|
|
|
|
if (++update_count == 2)
|
|
ui->RefreshNotifications();
|
|
if (newCity_ && gameState.cityState.ready)
|
|
{
|
|
MoveCameraToStartPosition(startPositionHugTerrain_);
|
|
Welcome();
|
|
newCity_ = false;
|
|
startPosition_ = START_POSITION;
|
|
startRotation_ = START_ROTATION;
|
|
startPositionHugTerrain_ = false;
|
|
}
|
|
|
|
gameState.debug = GetConfigValue(NULL, CONFIG_DEBUG, false);
|
|
|
|
if (TBWidget::focused_widget && !UITBWindow::HasModalDialog())
|
|
{
|
|
const bool is_focusable = TBSafeCast<TBEditField>(TBWidget::focused_widget) || TBSafeCast<TBSelectList>(TBWidget::focused_widget) || TBSafeCast<TBSelectDropdown>(TBWidget::focused_widget);
|
|
if (!is_focusable)
|
|
TBWidget::focused_widget = NULL;
|
|
}
|
|
|
|
if (wallet)
|
|
wallet->update();
|
|
|
|
// Take the frame time step, which is stored as a float
|
|
float timeStep = eventData[P_TIMESTEP].GetFloat();
|
|
|
|
// Move the camera, scale movement with time step
|
|
MoveCamera(timeStep);
|
|
|
|
while (!queued_commands.empty())
|
|
{
|
|
queued_commands.front()->execute(*this);
|
|
queued_commands.pop_front();
|
|
}
|
|
|
|
cityMesh->SetView(cameraNode_->GetWorldPosition());
|
|
cityMesh->Update(timeStep, GetEffectiveSelection(), mark_new_block);
|
|
|
|
{
|
|
const std::deque<std::pair<std::shared_ptr<Flag>, bool>> &dirty_flags = gameState.get_dirty_flags();
|
|
if (flagUnderConstruction)
|
|
{
|
|
const uint32_t id = std::get<0>(*flagUnderConstruction);
|
|
if (std::find_if(dirty_flags.begin(), dirty_flags.end(), [id](const std::pair<std::shared_ptr<Flag>, bool> &f) { return f.first->id == id && f.second; }) != dirty_flags.end())
|
|
{
|
|
new MessageBox(context_, "The building you were currently editing was changed on the blockchain. Changes automatically abandoned.");
|
|
RestoreFlagUnderConstruction();
|
|
}
|
|
}
|
|
|
|
std::deque<std::shared_ptr<Flag>> rebuild_flags;
|
|
std::shared_ptr<Flag> selected_flag = GetSelectedFlag();
|
|
if (selected_flag != previous_selected_flag)
|
|
{
|
|
if (selected_flag && std::find_if(dirty_flags.begin(), dirty_flags.end(), [&selected_flag](const std::pair<std::shared_ptr<Flag>, bool> &f) { return f.first == selected_flag; }) == dirty_flags.end())
|
|
rebuild_flags.push_back(selected_flag);
|
|
if (previous_selected_flag && std::find_if(dirty_flags.begin(), dirty_flags.end(), [this](const std::pair<std::shared_ptr<Flag>, bool> &f) { return f.first == previous_selected_flag; }) == dirty_flags.end())
|
|
rebuild_flags.push_back(previous_selected_flag);
|
|
previous_selected_flag = selected_flag;
|
|
if (selected_flag)
|
|
last_selected_flag = selected_flag;
|
|
}
|
|
|
|
std::set<uint32_t> clear_flags;
|
|
for (size_t i = 0; i < dirty_flags.size(); ++i)
|
|
{
|
|
const std::shared_ptr<Flag> &flag = dirty_flags[i].first;
|
|
if (flag != map.get_flag(flag->x0, flag->y0))
|
|
{
|
|
clear_flags.insert(flag->id);
|
|
}
|
|
else
|
|
{
|
|
rebuild_flags.push_back(flag);
|
|
}
|
|
}
|
|
|
|
if (!clear_flags.empty())
|
|
cityMesh->ClearFlags(clear_flags);
|
|
cityMesh->RebuildFlags(rebuild_flags, GetEffectiveSelection(), true);
|
|
gameState.clear_dirty_flags();
|
|
cityMesh->setHighlight(GetEffectiveSelection(), GetHoverFlag(), GetSelectedFlag(), flagUnderConstruction, mouse_x, mouse_y, mouse_h, top_x, top_y, top_h);
|
|
}
|
|
|
|
Node *zoneNode = scene_->GetChild("Zone");
|
|
if (zoneNode)
|
|
{
|
|
Vector3 pos = cameraNode_->GetPosition();
|
|
pos.y_ = 0.0f;
|
|
zoneNode->SetPosition(pos);
|
|
}
|
|
|
|
UpdateSky();
|
|
|
|
clouds.Update(timeStep, cameraNode_->GetPosition());
|
|
horizon.Update(timeStep, cameraNode_->GetPosition());
|
|
|
|
const bool is_real_wallet = wallet && !wallet->is_spectator() && gameState.playerState.has_wallet && gameState.playerState.id != 0;
|
|
console->SetInputEnabled(is_real_wallet);
|
|
console->Update();
|
|
const unsigned num_unread_lines = console->GetNumUnreadLines();
|
|
if (num_unread_lines == 0)
|
|
mentioned_in_chat = false;
|
|
|
|
if (!IsActionActive(Controls::ACTION_SELECT_MATERIAL))
|
|
ui->CloseSelectMaterial(true);
|
|
|
|
ui->Update(timeStep, mouse_x, mouse_y, selection.x0, selection.y0, selection.x1, selection.y1, cityMesh->getDisplayMode() == DisplayModeShowFlags, flagUnderConstruction != boost::none, num_unread_lines, mentioned_in_chat, wallet, cameraNode_->GetWorldDirection(), flagUnderConstruction);
|
|
mark_new_block = false;
|
|
|
|
RenderPath *effectRenderPath = viewport->GetRenderPath();
|
|
if (effectRenderPath)
|
|
effectRenderPath->SetEnabled("Glow", cityMesh->hasFlagsWithNew());
|
|
|
|
UpdateTargetting();
|
|
|
|
audio.Update(timeStep);
|
|
|
|
cityMesh->SetSnowiness(GetSnowiness());
|
|
|
|
UpdateBoids();
|
|
}
|
|
|
|
void CryptoCityUrho3D::UpdateBoids()
|
|
{
|
|
Boid::SetCameraPosition(cameraNode_->GetWorldPosition());
|
|
|
|
const unsigned ms = boidUpdateTimer_.GetMSec(false);
|
|
if (ms < 5000)
|
|
return;
|
|
|
|
if ((crypto::rand<uint32_t>() & 63) != 0)
|
|
return;
|
|
|
|
// pick a point on a circle around the camera, and one at an angle of 180 degrees, plus or minus 60,
|
|
// which should make the birds go roughly over, but not right above, which looks off
|
|
const float boid_height = BOID_BASE_MIN_HEIGHT + crypto::rand<uint32_t>() * (BOID_BASE_MAX_HEIGHT - BOID_BASE_MIN_HEIGHT) / 0xffffffff;
|
|
const Vector3 campos = cameraNode_->GetPosition();
|
|
const Vector3 camdir = cameraNode_->GetDirection();
|
|
const Vector3 hcamdir = Vector3(camdir.x_, 0.0f, camdir.z_).Normalized();
|
|
const Vector3 refpos = campos + hcamdir * 250;
|
|
const uint32_t origin_angle = crypto::rand<uint32_t>() % 360;
|
|
const Quaternion origin_rotation(origin_angle, Vector3::UP);
|
|
const Vector3 origin_dir = origin_rotation * hcamdir;
|
|
const Vector3 origin = refpos + origin_dir * BOID_ORIGIN_DISTANCE + Vector3::UP * boid_height;
|
|
const uint32_t destination_angle = ((uint32_t)(origin_angle + 180 + crypto::rand<uint32_t>() % 120 - 60)) % 360;
|
|
const Quaternion destination_rotation(destination_angle, Vector3::UP);
|
|
const Vector3 destination_dir = destination_rotation * hcamdir;
|
|
const Vector3 circle_destination = refpos + destination_dir * BOID_ORIGIN_DISTANCE + Vector3::UP * boid_height;
|
|
const Vector3 destination = origin + (circle_destination - origin).Normalized() * 250000.0f;
|
|
|
|
const unsigned r = crypto::rand<uint32_t>() & 7;
|
|
const unsigned int num_boids = r == 0 ? (8 + (crypto::rand<uint32_t>() & 15)) : r == 1 ? 1 : (1 + (crypto::rand<uint32_t>() & 7));
|
|
|
|
Node *boidsNode = scene_->GetChild("boids");
|
|
for (unsigned int i = 0; i < num_boids; ++i)
|
|
{
|
|
SharedPtr<Node> boidNode(boidsNode->CreateChild("boid"));
|
|
Boid *boid = boidNode->CreateComponent<Boid>();
|
|
boid->Init(origin + Vector3((crypto::rand<uint32_t>() & 127) - 64.0f, (crypto::rand<uint32_t>() & 127) - 64.0f, (crypto::rand<uint32_t>() & 127) - 64.0f));
|
|
boid->SetDestination(destination);
|
|
boid->SetAutoRemove(5000.0f);
|
|
}
|
|
boidUpdateTimer_.Reset();
|
|
}
|
|
|
|
float CryptoCityUrho3D::GetSnowiness() const
|
|
{
|
|
const int32_t temperature = gameState.cityState.temperature;
|
|
if (temperature < -50)
|
|
return 1.0f;
|
|
if (temperature > 30)
|
|
return 0.0f;
|
|
return (30 - temperature) / 80.0f;
|
|
}
|
|
|
|
float CryptoCityUrho3D::GetSummerness() const
|
|
{
|
|
static const float offset = -0.1f; // offset 10% so full summer falls a bit after mid year
|
|
float t = (gameState.top_height + CHAIN_START_DATE_OFFSET) / (float)BLOCKS_PER_GAME_YEAR + offset;
|
|
t -= truncf(t);
|
|
return (1.0f - cos(t * 2 * M_PI)) / 2.0f;
|
|
}
|
|
|
|
void CryptoCityUrho3D::Stop()
|
|
{
|
|
config_watcher.StopWatching();
|
|
if (wallet)
|
|
{
|
|
ui->AddNotification("Saving...");
|
|
try { wallet->save(); }
|
|
catch (const std::exception &e) {}
|
|
}
|
|
walletRefresher_.SetWallet(nullptr);
|
|
if (daemon_controller && daemon_controller->IsControlling())
|
|
{
|
|
printf("stopping daemon\n");
|
|
if (!daemon_controller->Stop())
|
|
printf("Failed to stop daemon\n");
|
|
}
|
|
delete cityMesh;
|
|
ui.Reset();
|
|
UTBRendererBatcher::Destroy();
|
|
if (gameState.debug)
|
|
engine_->DumpResources(true);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleSceneUpdate(StringHash /*eventType*/, VariantMap& eventData)
|
|
{
|
|
// Move the camera by touch, if the camera node is initialized by descendant sample class
|
|
if (touchEnabled_ && cameraNode_)
|
|
{
|
|
Input* input = GetSubsystem<Input>();
|
|
for (unsigned i = 0; i < input->GetNumTouches(); ++i)
|
|
{
|
|
TouchState* state = input->GetTouch(i);
|
|
if (!state->touchedElement_) // Touch on empty space
|
|
{
|
|
if (state->delta_.x_ ||state->delta_.y_)
|
|
{
|
|
Camera* camera = cameraNode_->GetComponent<Camera>();
|
|
if (!camera)
|
|
return;
|
|
|
|
Graphics* graphics = GetSubsystem<Graphics>();
|
|
float yaw = TOUCH_SENSITIVITY * camera->GetFov() / graphics->GetHeight() * state->delta_.x_;
|
|
float pitch = TOUCH_SENSITIVITY * camera->GetFov() / graphics->GetHeight() * state->delta_.y_;
|
|
|
|
// Construct new orientation for the camera scene node from yaw and pitch; roll is fixed to zero
|
|
camera_->rotate(yaw, pitch, 0.0f);
|
|
}
|
|
else
|
|
{
|
|
// Move the cursor to the touch position
|
|
Cursor* cursor = GetSubsystem<Urho3D::UI>()->GetCursor();
|
|
if (cursor && cursor->IsVisible())
|
|
cursor->SetPosition(state->position_);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandlePostRenderUpdate(StringHash /*eventType*/, VariantMap& eventData)
|
|
{
|
|
if (drawDebugGeometry_)
|
|
GetSubsystem<Renderer>()->DrawDebugGeometry(false);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleLoadWallet(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
|
|
auto *fileSelector = new UIFileSelector(context_);
|
|
fileSelector->SetTitle("Select wallet to load");
|
|
fileSelector->SetButtonTexts("Load", "Cancel");
|
|
fileSelector->SetFilters(Vector<String>(1, "*.keys"), 0);
|
|
|
|
SubscribeToEvent(fileSelector, E_FILESELECTED, URHO3D_HANDLER(CryptoCityUrho3D, HandleWalletFileSelected));
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleDeposit(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to deposit");
|
|
return;
|
|
}
|
|
uint64_t amount = eventData[Deposit::P_AMOUNT].GetUInt64();
|
|
if (amount > gameState.playerState.wallet_balance)
|
|
{
|
|
new MessageBox(context_, "Not enough money in wallet");
|
|
return;
|
|
}
|
|
wallet->deposit(&gameState, amount);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleWithdraw(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to withdraw");
|
|
return;
|
|
}
|
|
uint64_t amount = eventData[Withdraw::P_AMOUNT].GetUInt64();
|
|
if (amount > gameState.playerState.balance)
|
|
{
|
|
new MessageBox(context_, "Not enough in-game balance");
|
|
return;
|
|
}
|
|
wallet->withdraw(amount);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleBuildingSettings(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
if (!hasSelection_)
|
|
return;
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to build");
|
|
return;
|
|
}
|
|
|
|
std::shared_ptr<Flag> flag = map.get_flag(selection.x0, selection.y0);
|
|
if (!flag)
|
|
{
|
|
new MessageBox(context_, "Select a flag to build on first");
|
|
return;
|
|
}
|
|
if (flag->owner != gameState.playerState.id)
|
|
{
|
|
new MessageBox(context_, "You do not own this flag");
|
|
return;
|
|
}
|
|
if (flag->role != ROLE_EMPTY)
|
|
{
|
|
new MessageBox(context_, "This flag already has a role");
|
|
return;
|
|
}
|
|
|
|
cryptonote::cc_command_building_settings_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.flag = flag->id;
|
|
cmd.role = eventData[BuildingSettings::P_ROLE].GetInt();
|
|
cmd.economic_power = eventData[BuildingSettings::P_ECONOMIC_POWER].GetInt();
|
|
cmd.name = eventData[BuildingSettings::P_NAME].GetString().CString();
|
|
cmd.construction_height = flag->construction_height;
|
|
if (SendCommand(cmd))
|
|
ui->AddToastNotification("Building settings set.\nIf the building's requirements are met,\nit will become active at next game update");
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleServicePrice(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
cryptonote::cc_command_service_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.flag = eventData[ServicePrice::P_FLAG].GetUInt();
|
|
std::shared_ptr<Flag> flag = map.get_flag(cmd.flag);
|
|
if (!flag)
|
|
{
|
|
new MessageBox(context_, "Flag not found");
|
|
return;
|
|
}
|
|
cmd.delta_service_price = (int64_t)(eventData[ServicePrice::P_PRICE].GetUInt64() - flag->service_price);
|
|
if (cmd.delta_service_price != 0)
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleSow(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
cryptonote::cc_command_sow_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.flag = eventData[Sow::P_FLAG].GetUInt();
|
|
cmd.crop = eventData[Sow::P_CROP].GetUInt();
|
|
std::shared_ptr<Flag> flag = map.get_flag(cmd.flag);
|
|
if (!flag)
|
|
{
|
|
new MessageBox(context_, "Flag not found");
|
|
return;
|
|
}
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleHarvest(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
cryptonote::cc_command_harvest_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.flag = eventData[Harvest::P_FLAG].GetUInt();
|
|
std::shared_ptr<Flag> flag = map.get_flag(cmd.flag);
|
|
if (!flag)
|
|
{
|
|
new MessageBox(context_, "Flag not found");
|
|
return;
|
|
}
|
|
cmd.crop = (uint32_t)flag->crop;
|
|
cmd.sow_height = (unsigned long long)flag->sow_height;
|
|
cmd.nutrients = (uint32_t)(flag->crop == CROP_VEGETABLES ? flag->vegetables_nutrients : flag->grain_nutrients);
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleMint(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
cryptonote::cc_command_mint_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.coin = eventData[Mint::P_COIN].GetUInt();
|
|
cmd.amount = eventData[Mint::P_AMOUNT].GetUInt();
|
|
String name = eventData[Mint::P_NAME].GetString();
|
|
if (SendCommand(cmd))
|
|
ui->AddToastNotification("Minting " + String(cmd.amount) + " " + name);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleSmelt(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
cryptonote::cc_command_smelt_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.coin = eventData[Smelt::P_COIN].GetUInt();
|
|
cmd.amount = eventData[Smelt::P_AMOUNT].GetUInt();
|
|
String name = eventData[Mint::P_NAME].GetString();
|
|
if (SendCommand(cmd))
|
|
ui->AddToastNotification("Smelting " + String(cmd.amount) + " " + name);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleCancelNonce(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
cryptonote::cc_command_cancel_nonce_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.nonce = eventData[CancelNonce::P_NONCE].GetUInt64();
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleNewScript(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
cryptonote::cc_command_create_script_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
String script = eventData[NewScript::P_SCRIPT].GetString();
|
|
cmd.blob.assign(script.CString(), script.Length());
|
|
const std::vector<cryptonote::blobdata> *blobs = (const std::vector<cryptonote::blobdata>*)eventData[NewScript::P_BLOBS].GetVoidPtr();
|
|
|
|
std::vector<crypto::hash> hashes;
|
|
hashes.reserve(blobs->size());
|
|
for (const cryptonote::blobdata &b: *blobs)
|
|
hashes.push_back(cc::script::get_blob_hash(b));
|
|
std::vector<uint32_t> refs;
|
|
if (!wallet->get_cc_blob_info(hashes, refs) || refs.size() != hashes.size())
|
|
{
|
|
new MessageBox(context_, "Error requesting blob info");
|
|
return;
|
|
}
|
|
for (size_t i = 0; i < blobs->size(); ++i)
|
|
if (refs[i] == 0)
|
|
cmd.blobs.push_back((*blobs)[i]);
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandlePlayScript(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
cryptonote::cc_command_start_script_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.script = eventData[PlayScript::P_SCRIPT].GetUInt();
|
|
cmd.city = eventData[PlayScript::P_CITY].GetUInt();
|
|
cmd.flag = eventData[PlayScript::P_FLAG].GetUInt();
|
|
cmd.x = eventData[PlayScript::P_X].GetUInt();
|
|
cmd.y = eventData[PlayScript::P_Y].GetUInt();
|
|
cmd.h = eventData[PlayScript::P_H].GetUInt();
|
|
const String script_name = eventData[PlayScript::P_SCRIPT_NAME].GetString();
|
|
SendCommand(cmd);
|
|
ui->AddToastNotification("Starting script '" + script_name + "'");
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleEnableScript(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
cryptonote::cc_command_enable_script_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.script = eventData[EnableScript::P_SCRIPT].GetUInt();
|
|
cmd.enable = eventData[EnableScript::P_ENABLE].GetBool();
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleScriptChoice(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
cryptonote::cc_command_script_choice_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.script = eventData[ScriptChoice::P_SCRIPT].GetUInt();
|
|
cmd.state = eventData[ScriptChoice::P_STATE].GetUInt();
|
|
cmd.choice = eventData[ScriptChoice::P_CHOICE].GetUInt();
|
|
cmd.city = eventData[ScriptChoice::P_CITY].GetUInt();
|
|
cmd.owner = eventData[ScriptChoice::P_OWNER].GetUInt();
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleSetGlobalVariable(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
uint64_t value = 0;
|
|
const std::string name = eventData[SetGlobalVariable::P_NAME].GetString().CString();
|
|
if (!wallet->get_cc_global_variable(name, value))
|
|
{
|
|
new MessageBox(context_, "Error looking up variable");
|
|
return;
|
|
}
|
|
cryptonote::cc_command_set_script_variable_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.name = name;
|
|
cmd.old_value = value;
|
|
cmd.new_value = eventData[SetGlobalVariable::P_VALUE].GetUInt64();
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleGetGlobalVariable(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to lookup a global variable");
|
|
return;
|
|
}
|
|
const String name = eventData[GetGlobalVariable::P_NAME].GetString();
|
|
uint64_t value = 0;
|
|
if (!wallet->get_cc_global_variable(name.CString(), value))
|
|
{
|
|
new MessageBox(context_, "Error looking up variable");
|
|
return;
|
|
}
|
|
eventData[GetGlobalVariable::P_VALUE] = (unsigned long long)value;
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandlePlaceModel(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
const std::shared_ptr<Flag> flag = gameState.map.get_flag(placing_model_flag->id);
|
|
if (!flag || flag != placing_model_flag || placing_model_flag->owner != gameState.playerState.id)
|
|
{
|
|
new MessageBox(context_, "Flag not found, model placement cancelled");
|
|
EndModelPlacement();
|
|
return;
|
|
}
|
|
const int dx = eventData[PlaceModel::P_DX].GetInt();
|
|
const int dy = eventData[PlaceModel::P_DY].GetInt();
|
|
const int dh = eventData[PlaceModel::P_DH].GetInt();
|
|
const int dr = eventData[PlaceModel::P_DR].GetInt();
|
|
if (dx == 0 && dy == 0 && dh == 0 && dr == 0)
|
|
{
|
|
EndModelPlacement();
|
|
}
|
|
else
|
|
{
|
|
placing_model_dx += dx;
|
|
placing_model_dy += dy;
|
|
placing_model_dh += dh;
|
|
placing_model_dr += dr;
|
|
Undo();
|
|
LoadModelData(placing_model_flag, placing_model_data, {}, true);
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::EndModelPlacement()
|
|
{
|
|
placing_model = false;
|
|
placing_model_data = "";
|
|
placing_model_flag = NULL;
|
|
placing_model_dx = 0;
|
|
placing_model_dy = 0;
|
|
placing_model_dh = 0;
|
|
placing_model_dr = 0;
|
|
ui->OnRequestModelPlacement(false);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleRename(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
if (!hasSelection_)
|
|
return;
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to rename");
|
|
return;
|
|
}
|
|
|
|
std::shared_ptr<Flag> flag = map.get_flag(selection.x0, selection.y0);
|
|
if (!flag)
|
|
{
|
|
new MessageBox(context_, "Select a flag first");
|
|
return;
|
|
}
|
|
if (flag->owner != gameState.playerState.id)
|
|
{
|
|
new MessageBox(context_, "You do not own this flag");
|
|
return;
|
|
}
|
|
if (flag->role == ROLE_EMPTY)
|
|
{
|
|
new MessageBox(context_, "This flag has no building");
|
|
return;
|
|
}
|
|
|
|
cryptonote::cc_command_rename_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.flag = flag->id;
|
|
cmd.old_name = flag->name_hidden;
|
|
cmd.new_name = eventData[RenameFlag::P_NAME].GetString().CString();
|
|
if (cmd.new_name != cmd.old_name)
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleRepair(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to build");
|
|
return;
|
|
}
|
|
|
|
cryptonote::cc_command_repair_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
const std::vector<std::pair<uint32_t, uint32_t>> *v = (const std::vector<std::pair<uint32_t, uint32_t>>*)eventData[Repair::P_FLAGS].GetVoidPtr();
|
|
bool send = false;
|
|
for (const auto &e: *v)
|
|
{
|
|
const std::shared_ptr<Flag> flag = gameState.map.get_flag(e.first);
|
|
if (!flag)
|
|
{
|
|
new MessageBox(context_, "Flag not found");
|
|
return;
|
|
}
|
|
if (flag->owner != gameState.playerState.id)
|
|
{
|
|
new MessageBox(context_, "You do not own this flag");
|
|
return;
|
|
}
|
|
if (flag->role == ROLE_EMPTY)
|
|
{
|
|
new MessageBox(context_, "This flag has no building to repair");
|
|
return;
|
|
}
|
|
cmd.flags.push_back({e.first, e.second});
|
|
if (e.second > 0)
|
|
send = true;
|
|
}
|
|
if (send)
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleBuyLand(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
if (!hasSelection_)
|
|
return;
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to buy land");
|
|
return;
|
|
}
|
|
if (selection.x1 - selection.x0 + 1 > 256 || selection.y1 - selection.y0 > 256)
|
|
{
|
|
new MessageBox(context_, "Max size is 256x256");
|
|
return;
|
|
}
|
|
cryptonote::cc_command_buy_land_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.city = gameState.cityState.id;
|
|
cmd.x = selection.x0;
|
|
cmd.y = selection.y0;
|
|
cmd.wm1 = selection.x1 - selection.x0;
|
|
cmd.hm1 = selection.y1 - selection.y0;
|
|
if (SendCommand(cmd))
|
|
ui->AddToastNotification("After the land purchase goes through,\nyou can select Building Settings\nto start building on your new land");
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleResizeFlag(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
if (!hasSelection_)
|
|
{
|
|
new MessageBox(context_, "Select the new area first, then click resize");
|
|
return;
|
|
}
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to buy land");
|
|
return;
|
|
}
|
|
if (selection.x1 - selection.x0 + 1 > 256 || selection.y1 - selection.y0 > 256)
|
|
{
|
|
new MessageBox(context_, "Max size is 256x256\nSelect the new area, then click resize");
|
|
return;
|
|
}
|
|
if (selection.x1 - selection.x0 + 1 < MIN_BUY_SIDE_SIZE || selection.y1 - selection.y0 + 1 < MIN_BUY_SIDE_SIZE)
|
|
{
|
|
new MessageBox(context_, "Min size is " + String(MIN_BUY_SIDE_SIZE) + "x" + String(MIN_BUY_SIDE_SIZE) + "\nSelect the new area, then click resize");
|
|
return;
|
|
}
|
|
|
|
std::set<std::shared_ptr<Flag>> flags;
|
|
map.get_flags(selection.x0, selection.y0, selection.x1, selection.y1, flags);
|
|
if (flags.empty())
|
|
{
|
|
new MessageBox(context_, "The selection does not intersect an existing flag");
|
|
return;
|
|
}
|
|
if (flags.size() > 1)
|
|
{
|
|
new MessageBox(context_, "The selection intersects more than one flag");
|
|
return;
|
|
}
|
|
const std::shared_ptr<Flag> flag = *flags.begin();
|
|
if (flag->owner != gameState.playerState.id)
|
|
{
|
|
new MessageBox(context_, "You do not own this flag");
|
|
return;
|
|
}
|
|
|
|
cryptonote::cc_command_resize_flag_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.flag = flag->id;
|
|
cmd.dx0 = (int8_t)((uint32_t)selection.x0 - (uint32_t)flag->x0);
|
|
cmd.dy0 = (int8_t)((uint32_t)selection.y0 - (uint32_t)flag->y0);
|
|
cmd.dx1 = (int8_t)((uint32_t)selection.x1 - (uint32_t)flag->x1);
|
|
cmd.dy1 = (int8_t)((uint32_t)selection.y1 - (uint32_t)flag->y1);
|
|
const uint32_t ox = gameState.cityState.ox;
|
|
const uint32_t oy = gameState.cityState.oy;
|
|
if (!cc::get_flag_resizing_cost(flag->x0, flag->y0, flag->x1, flag->y1, selection.x0, selection.y0, selection.x1, selection.y1, ox, oy, cmd.cost))
|
|
{
|
|
new MessageBox(context_, "Failed to calculate cost");
|
|
return;
|
|
}
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleControlsChanged(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
Controls::Action action = Controls::ACTION_NONE;
|
|
while (1)
|
|
{
|
|
action = (Controls::Action)(action + 1);
|
|
if (action == Controls::NUM_ACTIONS)
|
|
break;
|
|
const Controls::Input input = Controls::Get().Get(action);
|
|
SetConfigValue(CONFIG_CONTROLS_SECTION, Controls::Get().GetActionName(action), Controls::Get().GetInputName(input));
|
|
}
|
|
SetConfigValue(CONFIG_CONTROLS_SECTION, CONFIG_INVERT_MOUSE_Y, Controls::Get().InvertMouseY());
|
|
SetConfigValue(CONFIG_CONTROLS_SECTION, CONFIG_MOUSE_SENSITIVITY, Controls::Get().GetMouseSensitivity());
|
|
SetConfigValue(CONFIG_CONTROLS_SECTION, CONFIG_TURN_RATE, Controls::Get().GetTurnRate());
|
|
}
|
|
|
|
void CryptoCityUrho3D::LoadControls()
|
|
{
|
|
Controls default_controls;
|
|
Controls &controls = Controls::Get();
|
|
unsigned conflicts = 0;
|
|
std::stringstream ss;
|
|
Controls::Action action = Controls::ACTION_NONE;
|
|
controls.Flush();
|
|
while (1)
|
|
{
|
|
action = (Controls::Action)(action + 1);
|
|
if (action == Controls::NUM_ACTIONS)
|
|
break;
|
|
|
|
Controls::Input input = Controls::INPUT_NONE;
|
|
String s = GetConfigValue(CONFIG_CONTROLS_SECTION, controls.GetActionName(action), String(""));
|
|
if (!s.Empty())
|
|
input = controls.GetInputFromString(s.CString());
|
|
if (input == Controls::INPUT_NONE)
|
|
input = default_controls.Get(action);
|
|
|
|
if (input != Controls::INPUT_NONE)
|
|
{
|
|
const Controls::Action existing_action = controls.Get(input);
|
|
if (existing_action == Controls::ACTION_NONE)
|
|
{
|
|
controls.Set(action, input);
|
|
}
|
|
else
|
|
{
|
|
conflicts++;
|
|
ss << "\n" << controls.GetInputName(input) << " configured for " << controls.GetActionName(action)
|
|
<< " but is already used for " << controls.GetActionName(existing_action);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool invert_mouse_y = GetConfigValue(CONFIG_CONTROLS_SECTION, CONFIG_INVERT_MOUSE_Y, false);
|
|
controls.InvertMouseY(invert_mouse_y);
|
|
float mouse_sensitivity = GetConfigValue(CONFIG_CONTROLS_SECTION, CONFIG_MOUSE_SENSITIVITY, 0.0f);
|
|
if (mouse_sensitivity > 0.0f)
|
|
controls.SetMouseSensitivity(mouse_sensitivity);
|
|
float turn_rate = GetConfigValue(CONFIG_CONTROLS_SECTION, CONFIG_TURN_RATE, 0.0f);
|
|
if (turn_rate > 0.0f)
|
|
controls.SetTurnRate(turn_rate);
|
|
|
|
if (conflicts > 0)
|
|
{
|
|
if (conflicts == 1)
|
|
new MessageBox(context_, "There is a conflict in controls configuration:" + String(ss.str().c_str()));
|
|
else
|
|
new MessageBox(context_, "There are " + String(conflicts) + " conflicts in controls configuration: " + ss.str().c_str());
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleBuyItems(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to buy blocks");
|
|
return;
|
|
}
|
|
cryptonote::cc_command_buy_items_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
const std::vector<std::pair<uint32_t, uint32_t>> *v = (const std::vector<std::pair<uint32_t, uint32_t>>*)eventData[BuyItems::P_QUANTITIES].GetVoidPtr();
|
|
for (const auto &q: *v)
|
|
{
|
|
const uint32_t type = q.first;
|
|
const uint32_t amount = q.second;
|
|
if (amount == 0)
|
|
continue;
|
|
auto i = std::find_if(cmd.entries.begin(), cmd.entries.end(), [type](const cryptonote::cc_command_buy_items_t::entry_t &e){ return type == e.type; });
|
|
if (i != cmd.entries.end())
|
|
{
|
|
i->amount += amount;
|
|
continue;
|
|
}
|
|
cryptonote::cc_command_buy_items_t::entry_t e;
|
|
e.type = type;
|
|
e.amount = amount;
|
|
cmd.entries.push_back(e);
|
|
}
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleSell(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
cryptonote::cc_command_trade_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.bid = false;
|
|
cmd.type = eventData[Sell::P_TYPE].GetInt();
|
|
cmd.id = eventData[Sell::P_ID].GetInt();
|
|
cmd.amount = eventData[Sell::P_AMOUNT].GetInt();
|
|
cmd.price = eventData[Sell::P_PRICE].GetUInt64();
|
|
cmd.accrual_start_height = eventData[Sell::P_ACCRUAL_START_HEIGHT].GetUInt64();
|
|
cmd.accrual = eventData[Sell::P_ACCRUAL].GetInt64();
|
|
cmd.accrual_price_limit = eventData[Sell::P_ACCRUAL_PRICE_LIMIT].GetUInt64();
|
|
const std::vector<std::pair<uint64_t, uint32_t>> *matches = (const std::vector<std::pair<uint64_t, uint32_t>>*)eventData[Sell::P_MATCHES].GetVoidPtr();
|
|
if (matches)
|
|
for (const auto &e: *matches)
|
|
cmd.matches.push_back({e.first, e.second});
|
|
cmd.cost = eventData[Buy::P_COST].GetUInt64();
|
|
cmd.flag_construction_height = 0;
|
|
cmd.expiration = eventData[Buy::P_EXPIRATION].GetUInt64();
|
|
|
|
bool ok = SendCommand(cmd);
|
|
|
|
if (ok)
|
|
ui->AddToastNotification("Trade registered, will be final when mined");
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleBuy(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
cryptonote::cc_command_trade_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.bid = true;
|
|
cmd.type = eventData[Buy::P_TYPE].GetInt();
|
|
cmd.id = eventData[Buy::P_ID].GetInt();
|
|
cmd.amount = eventData[Buy::P_AMOUNT].GetInt();
|
|
cmd.price = eventData[Buy::P_PRICE].GetUInt64();
|
|
cmd.accrual_start_height = eventData[Buy::P_ACCRUAL_START_HEIGHT].GetUInt64();
|
|
cmd.accrual = eventData[Buy::P_ACCRUAL].GetInt64();
|
|
cmd.accrual_price_limit = eventData[Buy::P_ACCRUAL_PRICE_LIMIT].GetUInt64();
|
|
cmd.flag_construction_height = 0;
|
|
cmd.cost = eventData[Buy::P_COST].GetUInt64();
|
|
if (cmd.type == cryptonote::cc_command_trade_t::type_flag)
|
|
{
|
|
const std::shared_ptr<Flag> flag = gameState.map.get_flag(cmd.id);
|
|
if (!flag)
|
|
{
|
|
new MessageBox(context_, "Flag not found");
|
|
return;
|
|
}
|
|
cmd.flag_construction_height = flag->construction_height;
|
|
}
|
|
|
|
cryptonote::cc_command_buy_items_t cmd_last_resort;
|
|
cmd_last_resort.cc_account = cmd.cc_account;
|
|
|
|
const std::vector<std::pair<uint64_t, uint32_t>> *matches = (const std::vector<std::pair<uint64_t, uint32_t>>*)eventData[Sell::P_MATCHES].GetVoidPtr();
|
|
if (matches)
|
|
{
|
|
for (const auto &e: *matches)
|
|
{
|
|
if (e.first)
|
|
{
|
|
cmd.matches.push_back({e.first, e.second});
|
|
}
|
|
else
|
|
{
|
|
cmd_last_resort.entries.push_back({cmd.id, e.second});
|
|
cmd.amount -= e.second;
|
|
cmd.cost -= e.second * cc::get_last_resort_price(cmd.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ok = true;
|
|
cmd.expiration = eventData[Buy::P_EXPIRATION].GetUInt64();
|
|
if (cmd.amount > 0)
|
|
ok &= SendCommand(cmd);
|
|
|
|
if (ok && !cmd_last_resort.entries.empty())
|
|
ok &= SendCommand(cmd_last_resort);
|
|
|
|
if (ok)
|
|
ui->AddToastNotification("Trade registered, will be final when mined");
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleAssignItems(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to buy blocks");
|
|
return;
|
|
}
|
|
std::shared_ptr<Flag> flag = map.get_flag(selection.x0, selection.y0);
|
|
if (!flag)
|
|
{
|
|
new MessageBox(context_, "There is no flag there");
|
|
return;
|
|
}
|
|
cryptonote::cc_command_assign_items_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.flag = flag->id;
|
|
const std::vector<std::pair<uint32_t, uint32_t>> *v = (const std::vector<std::pair<uint32_t, uint32_t>>*)eventData[AssignItems::P_QUANTITIES].GetVoidPtr();
|
|
for (const auto &q: *v)
|
|
{
|
|
cryptonote::cc_command_assign_items_t::item_t e;
|
|
e.type = q.first;
|
|
e.amount = q.second;
|
|
cmd.items.push_back(e);
|
|
}
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleChopWood(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to chop wood");
|
|
return;
|
|
}
|
|
cryptonote::cc_command_chop_wood_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
const std::vector<std::pair<uint32_t, uint32_t>> *v = (const std::vector<std::pair<uint32_t, uint32_t>>*)eventData[ChopWood::P_QUANTITIES].GetVoidPtr();
|
|
for (const auto &q: *v)
|
|
{
|
|
cryptonote::cc_command_chop_wood_t::item_t e;
|
|
e.type = q.first;
|
|
e.amount = q.second;
|
|
cmd.items.push_back(e);
|
|
}
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleCarveRunestone(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to carve a runestone");
|
|
return;
|
|
}
|
|
|
|
cryptonote::cc_command_carve_runestone_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.flag = eventData[CarveRunestone::P_FLAG].GetUInt();
|
|
cmd.x = eventData[CarveRunestone::P_X].GetUInt();
|
|
cmd.y = eventData[CarveRunestone::P_Y].GetUInt();
|
|
cmd.h = eventData[CarveRunestone::P_H].GetUInt();
|
|
cmd.script_delta = eventData[CarveRunestone::P_SCRIPT].GetUInt();
|
|
const std::vector<std::tuple<uint8_t, uint32_t, std::string>> *overrides = (const std::vector<std::tuple<uint8_t, uint32_t, std::string>>*)eventData[CarveRunestone::P_OVERRIDES].GetVoidPtr();
|
|
for (const auto &e: *overrides)
|
|
cmd.overrides.push_back({std::get<0>(e), std::get<1>(e), std::get<2>(e)});
|
|
cmd.message = eventData[CarveRunestone::P_MESSAGE].GetString().CString();
|
|
|
|
cc::runestone_t runestone;
|
|
if (wallet->get_cc_runestone(cmd.flag, cmd.x, cmd.y, cmd.h, runestone))
|
|
{
|
|
cmd.script_delta -= runestone.script;
|
|
cmd.previous_message = std::move(runestone.message);
|
|
cmd.previous_overrides.reserve(runestone.overrides.size());
|
|
for (const auto &e: runestone.overrides)
|
|
cmd.previous_overrides.push_back({std::get<0>(e), std::get<1>(e), std::get<2>(e)});
|
|
}
|
|
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleNewAuctionFlag(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to auction something");
|
|
return;
|
|
}
|
|
|
|
cryptonote::cc_command_create_auction_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.type = cc::auction_t::type_flag;
|
|
const uint32_t flag = eventData[NewAuctionFlag::P_FLAG].GetUInt();
|
|
cmd.entries.push_back({flag, 1});
|
|
cmd.base_ticks = eventData[NewAuctionFlag::P_BASE_TICKS].GetUInt();
|
|
cmd.title = eventData[NewAuctionFlag::P_TITLE].GetString().CString();
|
|
cmd.description = eventData[NewAuctionFlag::P_DESCRIPTION].GetString().CString();
|
|
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleNewAuctionItem(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to auction something");
|
|
return;
|
|
}
|
|
|
|
cryptonote::cc_command_create_auction_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.type = cc::auction_t::type_item;
|
|
const std::vector<std::pair<uint32_t, uint32_t>> *v = (const std::vector<std::pair<uint32_t, uint32_t>>*)eventData[NewAuctionItem::P_ITEMS].GetVoidPtr();
|
|
for (const auto &q: *v)
|
|
cmd.entries.push_back({q.first, q.second});
|
|
cmd.base_ticks = eventData[NewAuctionItem::P_BASE_TICKS].GetUInt();
|
|
cmd.title = eventData[NewAuctionItem::P_TITLE].GetString().CString();
|
|
cmd.description = eventData[NewAuctionItem::P_DESCRIPTION].GetString().CString();
|
|
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleBidOnAuction(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to bid on an auction");
|
|
return;
|
|
}
|
|
|
|
cryptonote::cc_command_auction_bid_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.auction = eventData[BidOnAuction::P_AUCTION].GetUInt();
|
|
cmd.price = eventData[BidOnAuction::P_PRICE].GetUInt64();
|
|
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleAllowStyling(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to change allowed list");
|
|
return;
|
|
}
|
|
|
|
const std::vector<uint32_t> *v = (const std::vector<uint32_t>*)eventData[AllowStyling::P_ALLOW].GetVoidPtr();
|
|
const std::vector<uint32_t> &allowed = gameState.cityState.allow_styling;
|
|
|
|
// find removed ones
|
|
for (uint32_t a: allowed)
|
|
{
|
|
if (std::find(v->begin(), v->end(), a) != v->end())
|
|
continue;
|
|
|
|
cryptonote::cc_command_allow_styling_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.city = gameState.cityState.id;
|
|
cmd.account = a;
|
|
cmd.allow = false;
|
|
if (!SendCommand(cmd))
|
|
return;
|
|
}
|
|
|
|
// find added ones
|
|
for (uint32_t a: *v)
|
|
{
|
|
if (std::find(allowed.begin(), allowed.end(), a) != allowed.end())
|
|
continue;
|
|
|
|
cryptonote::cc_command_allow_styling_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.city = gameState.cityState.id;
|
|
cmd.account = a;
|
|
cmd.allow = true;
|
|
if (!SendCommand(cmd))
|
|
return;
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleAudioPlayingTrack(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
ui->OnAudioTrack(eventData[AudioPlayingTrack::P_TITLE].GetString());
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleDestroyItems(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to destroy items");
|
|
return;
|
|
}
|
|
const std::vector<std::pair<uint32_t, uint32_t>> *v = (const std::vector<std::pair<uint32_t, uint32_t>>*)eventData[DestroyItems::P_QUANTITIES].GetVoidPtr();
|
|
for (const auto &q: *v)
|
|
{
|
|
cryptonote::cc_command_destroy_items_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.item_id = q.first;
|
|
cmd.amount = q.second;
|
|
SendCommand(cmd);
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleDefineAttribute(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to define an attribute");
|
|
return;
|
|
}
|
|
cryptonote::cc_command_define_attribute_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.name = eventData[DefineAttribute::P_NAME].GetString().CString();
|
|
cmd.description = eventData[DefineAttribute::P_DESCRIPTION].GetString().CString();
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleIncreaseAttribute(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to increase attribute");
|
|
return;
|
|
}
|
|
const std::vector<std::pair<uint32_t, uint32_t>> *v = (const std::vector<std::pair<uint32_t, uint32_t>>*)eventData[IncreaseAttribute::P_ATTRIBUTES].GetVoidPtr();
|
|
for (const auto &q: *v)
|
|
{
|
|
cryptonote::cc_command_increase_attribute_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.attribute_id = q.first;
|
|
cmd.points = q.second;
|
|
SendCommand(cmd);
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleAddCitySpecialization(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to add a city specialization");
|
|
return;
|
|
}
|
|
const uint32_t specialization = eventData[AddCitySpecialization::P_SPECIALIZATION].GetInt();
|
|
cryptonote::cc_command_add_city_specialization_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.city = gameState.cityState.id;
|
|
cmd.specialization = specialization;
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleSavePlayerProfile(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to save your player profile");
|
|
return;
|
|
}
|
|
|
|
cryptonote::cc_command_edit_player_profile_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.previous_text = gameState.playerState.description;
|
|
cmd.new_text = eventData[SavePlayerProfile::P_PROFILE].GetString().CString();
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleUpdateItem(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to edit an item");
|
|
return;
|
|
}
|
|
|
|
cryptonote::cc_command_update_item_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.item = eventData[UpdateItem::P_ITEM].GetUInt();
|
|
cmd.prev_sdesc = eventData[UpdateItem::P_PREVIOUS_SECONDARY_DESCRIPTION].GetString().CString();
|
|
cmd.new_sdesc = eventData[UpdateItem::P_NEW_SECONDARY_DESCRIPTION].GetString().CString();
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleDiceRoll(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to roll a dice");
|
|
return;
|
|
}
|
|
cryptonote::cc_command_dice_roll_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.text = eventData[DiceRoll::P_TEXT].GetString().CString();
|
|
cmd.min_value = eventData[DiceRoll::P_MIN_VALUE].GetUInt();
|
|
cmd.max_value = eventData[DiceRoll::P_MAX_VALUE].GetUInt();
|
|
cmd.account = eventData[DiceRoll::P_ACCOUNT].GetUInt();
|
|
cmd.attribute = eventData[DiceRoll::P_ATTRIBUTE].GetUInt();
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleHunt(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to hunt");
|
|
return;
|
|
}
|
|
cryptonote::cc_command_hunt_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.target = eventData[Hunt::P_TARGET].GetUInt();
|
|
cmd.city = gameState.cityState.id;
|
|
std::string type;
|
|
switch (cmd.target)
|
|
{
|
|
case cryptonote::cc_command_hunt_t::hunt_target_moose: cmd.population = gameState.cityState.moose; type = "moose"; break;
|
|
case cryptonote::cc_command_hunt_t::hunt_target_bears: cmd.population = gameState.cityState.bears; type = "bear"; break;
|
|
default: new MessageBox(context_, "Invalid hunt target"); return;
|
|
}
|
|
if (SendCommand(cmd))
|
|
{
|
|
std::string msg = "A " + type + " hunting party has left " + gameState.get_city_name(gameState.cityState.id) + "\nResults will be known after the next block is mined";
|
|
ui->AddToastNotification(msg.c_str());
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleGetTextures(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
std::map<uint8_t, std::map<uint16_t, std::pair<Texture2D*, material_style_t>>> *textures = (std::map<uint8_t, std::map<uint16_t, std::pair<Texture2D*, material_style_t>>>*)eventData[GetTextures::P_TEXTURES].GetVoidPtr();
|
|
cityMesh->GetBlockTextures(*textures);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandlePalette(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to change palette");
|
|
return;
|
|
}
|
|
|
|
const std::vector<std::tuple<uint8_t, int16_t>> *v = (const std::vector<std::tuple<uint8_t, int16_t>>*)eventData[Palette::P_PALETTE].GetVoidPtr();
|
|
cryptonote::cc_command_palette_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.flag = eventData[Palette::P_FLAG].GetUInt();
|
|
for (const auto &e: *v)
|
|
{
|
|
const uint8_t index = std::get<0>(e);
|
|
const int16_t variant_delta = std::get<1>(e);
|
|
cmd.palette.push_back({index, variant_delta});
|
|
}
|
|
|
|
if (!cmd.palette.empty())
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleFightFire(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to fight fire");
|
|
return;
|
|
}
|
|
|
|
cryptonote::cc_command_fight_fire_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.burning_flag = eventData[FightFire::P_BURNING_FLAG].GetUInt();
|
|
cmd.fire_state = eventData[FightFire::P_FIRE_STATE].GetUInt();
|
|
cmd.firefighting_flag = eventData[FightFire::P_FIREFIGHTING_FLAG].GetUInt();
|
|
cmd.last_firefighting_height = eventData[FightFire::P_LAST_FIREFIGHTING_HEIGHT].GetUInt64();
|
|
|
|
const std::shared_ptr<Flag> burning_flag = map.get_flag(cmd.burning_flag);
|
|
if (!burning_flag)
|
|
{
|
|
new MessageBox(context_, "Failed to find burning flag");
|
|
return;
|
|
}
|
|
const std::shared_ptr<Flag> firefighting_flag = map.get_flag(cmd.firefighting_flag);
|
|
if (!firefighting_flag)
|
|
{
|
|
new MessageBox(context_, "Failed to find firefighting flag");
|
|
return;
|
|
}
|
|
cmd.cost = 0;
|
|
if (firefighting_flag->owner != gameState.playerState.id)
|
|
{
|
|
if (!cc::get_service_fee(firefighting_flag->service_price, burning_flag->x0, burning_flag->y0, burning_flag->x1, burning_flag->y1, cmd.cost))
|
|
{
|
|
new MessageBox(context_, "Failed to calculate fee");
|
|
return;
|
|
}
|
|
}
|
|
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleGenerateInvitation(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to generate invitations");
|
|
return;
|
|
}
|
|
uint64_t amount = eventData[GenerateInvitation::P_AMOUNT].GetUInt64();
|
|
uint64_t expiration = eventData[GenerateInvitation::P_EXPIRATION].GetUInt64();
|
|
std::string invitation;
|
|
if (wallet->generate_invitation(amount, expiration, crypto::null_pkey, invitation))
|
|
eventData[GenerateInvitation::P_INVITATION] = invitation.c_str();
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleRequestInvitationStatus(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
bool used = false, balance_ok = true;
|
|
if (wallet)
|
|
{
|
|
const String pkey = eventData[RequestInvitationStatus::P_PUBLIC_KEY].GetString();
|
|
wallet->get_invitation_status(pkey.CString(), used, balance_ok);
|
|
}
|
|
eventData[RequestInvitationStatus::P_USED] = used;
|
|
eventData[RequestInvitationStatus::P_BALANCE_OK] = balance_ok;
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleAcceptInvitation(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
const String invitation = eventData[AcceptInvitation::P_INVITATION].GetString();
|
|
const String name = eventData[AcceptInvitation::P_NAME].GetString();
|
|
if (!wallet || !wallet->wallet() || wallet->is_spectator())
|
|
{
|
|
const String filename = eventData[AcceptInvitation::P_FILENAME].GetString();
|
|
if (!wallet->generate_new_wallet(filename.CString()))
|
|
{
|
|
new MessageBox(context_, "Failed to create new wallet");
|
|
return;
|
|
}
|
|
pending_invitation = eventData;
|
|
return;
|
|
}
|
|
|
|
if (name.Empty())
|
|
{
|
|
UINewAccountDialog *mb = new UINewAccountDialog(context_, &gameState, true);
|
|
SubscribeToEvent(mb, E_NEW_ACCOUNT_OKAYED, [this, invitation](StringHash eventType, VariantMap& eventData) {
|
|
VariantMap pendingEventData = *pending_invitation;
|
|
pending_invitation = boost::none;
|
|
pendingEventData[AcceptInvitation::P_NAME] = eventData[NewAccountOkayed::P_NAME].GetString();
|
|
HandleAcceptInvitation(E_CRYPTOCITY_ACCEPT_INVITATION, pendingEventData);
|
|
});
|
|
pending_invitation = eventData;
|
|
return;
|
|
}
|
|
|
|
cryptonote::blobdata inner;
|
|
crypto::signature inner_signature;
|
|
uint32_t account;
|
|
uint64_t amount, expiration;
|
|
crypto::public_key pkey;
|
|
crypto::secret_key secret_key;
|
|
crypto::signature secret_key_signature;
|
|
crypto::public_key recipient;
|
|
if (!cc::parse_invitation(invitation.CString(), inner, inner_signature, account, amount, expiration, pkey, recipient, secret_key, secret_key_signature))
|
|
{
|
|
new MessageBox(context_, "Failed to parse invitation");
|
|
return;
|
|
}
|
|
|
|
cryptonote::cc_command_redeem_account_t cmd;
|
|
cmd.public_key = wallet->get_cc_pkey();
|
|
cmd.name = name.CString();
|
|
cmd.inviting_account = account;
|
|
cmd.invitation = inner;
|
|
memset(&cmd.invitation_signature, 0, sizeof(cmd.invitation_signature));
|
|
memset(&cmd.recipient_signature, 0, sizeof(cmd.recipient_signature));
|
|
cmd.cc_nonce = 0;
|
|
wallet->get_pm_keys(cmd.pmspk, cmd.pmvpk);
|
|
|
|
cryptonote::cc_command_t command = cmd;
|
|
cryptonote::blobdata blob = cryptonote::t_serializable_object_to_blob(command);
|
|
crypto::hash hash;
|
|
crypto::cn_fast_hash(blob.data(), blob.size(), hash);
|
|
crypto::generate_signature(hash, pkey, secret_key, cmd.invitation_signature);
|
|
if (recipient != crypto::null_pkey)
|
|
{
|
|
cryptonote::account_public_address address;
|
|
if (!wallet->get_main_wallet_address(address))
|
|
{
|
|
new MessageBox(context_, "Error getting wallet address");
|
|
return;
|
|
}
|
|
if (address.m_spend_public_key != recipient)
|
|
{
|
|
new MessageBox(context_, "This invitation may only be redeemed by a particular wallet");
|
|
return;
|
|
}
|
|
crypto::secret_key spend_key, view_key;
|
|
epee::wipeable_string seed;
|
|
if (!wallet->get_secret_data(spend_key, view_key, seed))
|
|
{
|
|
new MessageBox(context_, "Error getting keys to sign invitation redemption");
|
|
return;
|
|
}
|
|
crypto::generate_signature(hash, recipient, spend_key, cmd.recipient_signature);
|
|
}
|
|
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleCreateNewWallet(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
const String filename = eventData[CreateNewWallet::P_FILENAME].GetString();
|
|
if (filename.Empty())
|
|
{
|
|
auto *fileSelector = new UIFileSelector(context_);
|
|
fileSelector->SetTitle("Select filename to save new wallet");
|
|
fileSelector->SetButtonTexts("Create", "Cancel");
|
|
fileSelector->SetFilters(Vector<String>(1, "*.keys"), 0);
|
|
SubscribeToEvent(fileSelector, E_FILESELECTED, [this](StringHash eventType, VariantMap& eventData) {
|
|
const bool ok = eventData[FileSelected::P_OK].GetBool();
|
|
if (ok)
|
|
{
|
|
VariantMap newEventData;
|
|
newEventData[CreateNewWallet::P_FILENAME] = eventData[FileSelected::P_FILENAME].GetString();
|
|
HandleCreateNewWallet(E_CRYPTOCITY_CREATE_NEW_WALLET, newEventData);
|
|
|
|
ui->AddToastNotification("Creating new wallet...\nWhen done, you can then fund it and create a game account");
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (!wallet->generate_new_wallet(filename.CString()))
|
|
{
|
|
new MessageBox(context_, "Failed to create new wallet");
|
|
return;
|
|
}
|
|
|
|
showWalletInfo_ = true;
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleStartMining(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet)
|
|
return;
|
|
bool busy = false;
|
|
if (!wallet->start_mining(1, &busy))
|
|
{
|
|
if (busy)
|
|
new MessageBox(context_, "The Townforge daemon is syncing with the network, cannot mine now");
|
|
else
|
|
new MessageBox(context_, "Error starting mining");
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleStopMining(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet)
|
|
return;
|
|
wallet->stop_mining();
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleMainPanelGeometry(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (eventData.Contains(MainPanelGeometry::P_X))
|
|
{
|
|
const int x = eventData[MainPanelGeometry::P_X].GetInt();
|
|
const int y = eventData[MainPanelGeometry::P_Y].GetInt();
|
|
const int width = eventData[MainPanelGeometry::P_WIDTH].GetInt();
|
|
const int height = eventData[MainPanelGeometry::P_HEIGHT].GetInt();
|
|
const bool shaded = eventData[MainPanelGeometry::P_SHADED].GetBool();
|
|
char s[64];
|
|
snprintf(s, sizeof(s), "%dx%dx%dx%dx%d", x, y, width, height, shaded);
|
|
SetConfigValue(CONFIG_UI_SECTION, CONFIG_PANEL, String(s));
|
|
}
|
|
else
|
|
{
|
|
String geometry = GetConfigValue(CONFIG_UI_SECTION, CONFIG_PANEL, DEFAULT_PANEL);
|
|
int x = -1, y = -1, width = -1, height = -1, shaded = 0;
|
|
sscanf(geometry.CString(), "%dx%dx%dx%dx%d", &x, &y, &width, &height, &shaded);
|
|
eventData[MainPanelGeometry::P_X] = x;
|
|
eventData[MainPanelGeometry::P_Y] = y;
|
|
eventData[MainPanelGeometry::P_WIDTH] = width;
|
|
eventData[MainPanelGeometry::P_HEIGHT] = height;
|
|
eventData[MainPanelGeometry::P_SHADED] = (bool)shaded;
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleRestoreModelBackup(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
const bool restore = eventData[RestoreModelBackup::P_RESTORE].GetBool();
|
|
if (!restore)
|
|
{
|
|
RemoveBackup();
|
|
return;
|
|
}
|
|
std::string s;
|
|
if (!epee::file_io_utils::load_file_to_string(building_backup_filename.CString(), s))
|
|
{
|
|
new MessageBox(context_, "Failed to load file");
|
|
return;
|
|
}
|
|
if (!boost::starts_with(s, "Townforge backup\n"))
|
|
{
|
|
new MessageBox(context_, "Invalid data");
|
|
return;
|
|
}
|
|
uint32_t id = 0;
|
|
sscanf(s.c_str() + strlen("Townforge backup\n"), "Flag %u\n", &id);
|
|
if (id == 0)
|
|
{
|
|
new MessageBox(context_, "Invalid data");
|
|
return;
|
|
}
|
|
const char *ptr = strchr(s.c_str(), '\n');
|
|
if (!ptr)
|
|
{
|
|
new MessageBox(context_, "Invalid data");
|
|
return;
|
|
}
|
|
ptr = strchr(ptr + 1, '\n');
|
|
if (!ptr)
|
|
{
|
|
new MessageBox(context_, "Invalid data");
|
|
return;
|
|
}
|
|
s.assign(ptr + 1, s.size() - (ptr + 1 - s.data()));
|
|
|
|
const std::shared_ptr<Flag> flag = map.get_flag(id);
|
|
if (!flag)
|
|
{
|
|
new MessageBox(context_, "Flag not found");
|
|
return;
|
|
}
|
|
|
|
Flag::unpacker unpacker(flag);
|
|
|
|
std::map<uint32_t, uint32_t> existing_tiles;
|
|
const std::shared_ptr<TileData> tiles = flag->get_tiles();
|
|
if (tiles)
|
|
{
|
|
const uint32_t bonus = cc::get_build_labour_cost_for_height_bonus(gameState.has_discovery(DISCOVERY_IMPROVED_SCAFFOLDING), gameState.has_discovery(DISCOVERY_ADVANCED_SCAFFOLDING));
|
|
for (const auto &t: tiles->tiles)
|
|
{
|
|
uint32_t h = 0;
|
|
for (const auto &e: t)
|
|
{
|
|
if (e > 0 && e < flag->palette.size())
|
|
{
|
|
const uint16_t material = flag->palette[e];
|
|
const cc::block_variant_t variant = cc::get_block_variant(material);
|
|
++existing_tiles[variant.item];
|
|
existing_tiles[ITEM_LABOUR] += cc::get_build_labour_cost_for_height(h, bonus);
|
|
}
|
|
++h;
|
|
}
|
|
}
|
|
}
|
|
|
|
flagUnderConstruction = std::make_tuple(flag->id, flag->palette, tiles, std::shared_ptr<Flag::unpacker>(new Flag::unpacker(flag)));
|
|
undo.Push(tiles);
|
|
selection = Selection(flag->x0, flag->y0, flag->x1, flag->y1);
|
|
std::shared_ptr<TileData> td(new TileData);
|
|
td->tiles = std::vector<std::vector<uint8_t>>((flag->x1 - flag->x0 + 1) * (flag->y1 - flag->y0 + 1), std::vector<uint8_t>());
|
|
flag->set_tiles(std::move(td));
|
|
if (!LoadModelData(flag, s, existing_tiles))
|
|
{
|
|
new MessageBox(context_, "Failed to load backup");
|
|
return;
|
|
}
|
|
GoToFlag(flag);
|
|
}
|
|
|
|
void CryptoCityUrho3D::AddBlock(bool use_selection, bool flat)
|
|
{
|
|
UnsetFocus();
|
|
if (use_selection && !hasSelection_)
|
|
return;
|
|
if (!use_selection && (!mouse_x || !mouse_y))
|
|
return;
|
|
|
|
uint32_t selection_x0 = use_selection ? selection.x0 : top_x;
|
|
uint32_t selection_y0 = use_selection ? selection.y0 : top_y;
|
|
uint32_t selection_x1 = use_selection ? selection.x1 : top_x;
|
|
uint32_t selection_y1 = use_selection ? selection.y1 : top_y;
|
|
|
|
std::shared_ptr<Flag> flag = map.get_flag(selection_x0, selection_y0);
|
|
if (!flag)
|
|
{
|
|
if (use_selection)
|
|
new MessageBox(context_, "There is no flag there");
|
|
return;
|
|
}
|
|
if (flag->role == ROLE_EMPTY)
|
|
{
|
|
new MessageBox(context_, "Select building settings first");
|
|
return;
|
|
}
|
|
if (flagUnderConstruction && std::get<0>(*flagUnderConstruction) != flag->id)
|
|
{
|
|
new MessageBox(context_, "You can only build on a single lot at once: either commit or cancel the one you're buildng on now first");
|
|
return;
|
|
}
|
|
|
|
const auto i = flags_with_pending_build_commands.find(flag->id);
|
|
if (i != flags_with_pending_build_commands.end() && i->second > 0)
|
|
{
|
|
new MessageBox(context_, "There are pending build commands on this flag, modifying it now would create conflict");
|
|
return;
|
|
}
|
|
|
|
Flag::unpacker unpacker(flag);
|
|
|
|
EndModelPlacement();
|
|
if (!flagUnderConstruction)
|
|
{
|
|
auto tiles = flag->get_tiles();
|
|
flagUnderConstruction = std::make_tuple(flag->id, flag->palette, tiles, std::shared_ptr<Flag::unpacker>(new Flag::unpacker(flag)));
|
|
undo.Push({std::move(tiles)});
|
|
SaveBackup();
|
|
if (flag->owner != gameState.playerState.id)
|
|
new MessageBox(context_, "You do not own this flag:\nyou will not be able to submit any change to the blockchain", "Info");
|
|
ui->AddToastNotification("New build started. When done, either approve or abandon it\nPress/hold X to select block type to build with");
|
|
}
|
|
|
|
selection_x0 = std::max(selection_x0, flag->x0);
|
|
selection_y0 = std::max(selection_y0, flag->y0);
|
|
selection_x1 = std::min(selection_x1, flag->x1);
|
|
selection_y1 = std::min(selection_y1, flag->y1);
|
|
uint16_t local_top_h = top_h >= flag->base_height ? top_h - flag->base_height : 0;
|
|
|
|
Command cmd;
|
|
cmd.type = Command::cmd_push_area;
|
|
cmd.owner = gameState.playerState.id;
|
|
cmd.push_area.flag = flag->id;
|
|
cmd.push_area.dx = selection_x0 - flag->x0;
|
|
cmd.push_area.dy = selection_y0 - flag->y0;
|
|
cmd.push_area.wm1 = selection_x1 - selection_x0;
|
|
cmd.push_area.hm1 = selection_y1 - selection_y0;
|
|
if (use_selection)
|
|
{
|
|
cmd.push_area.height = 0;
|
|
if (flat)
|
|
{
|
|
for (uint32_t y = selection_y0; y <= selection_y1; ++y)
|
|
{
|
|
for (uint32_t x = selection_x0; x <= selection_x1; ++x)
|
|
{
|
|
const uint16_t terrain_height = cityMesh->GetTerrainHeight(x, y);
|
|
const uint16_t th = flag->get_tile_height(x, y);
|
|
const uint16_t actual_height = std::max<uint16_t>(std::max<uint16_t>(terrain_height, flag->base_height) - flag->base_height, th);
|
|
if (actual_height > cmd.push_area.height)
|
|
cmd.push_area.height = actual_height;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cmd.push_area.height = local_top_h;
|
|
}
|
|
|
|
SelectNextMaterial(false);
|
|
if (currentBlockVariant == cc::BLOCK_VARIANT_NONE)
|
|
return;
|
|
const cc::block_variant_t block_variant = cc::get_block_variant(currentBlockVariant);
|
|
|
|
uint8_t palette_index = 0;
|
|
for (size_t i = 0; i < flag->palette.size(); ++i)
|
|
{
|
|
if (flag->palette[i] == currentBlockVariant)
|
|
{
|
|
palette_index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (palette_index == 0)
|
|
{
|
|
palette_index = FindFreePaletteSlot(flag);
|
|
if (palette_index == 0)
|
|
{
|
|
new MessageBox(context_, "Not enough space in palette to add this new material");
|
|
return;
|
|
}
|
|
if (palette_index >= flag->palette.size())
|
|
flag->palette.resize((uint32_t)palette_index + 1, cc::BLOCK_VARIANT_NONE);
|
|
flag->palette[palette_index] = currentBlockVariant;
|
|
}
|
|
|
|
const uint32_t bonus = cc::get_build_labour_cost_for_height_bonus(gameState.has_discovery(DISCOVERY_IMPROVED_SCAFFOLDING), gameState.has_discovery(DISCOVERY_ADVANCED_SCAFFOLDING));
|
|
|
|
std::map<uint32_t, uint32_t> flag_budget = flag->budget;
|
|
uint8_t raw_types[65536];
|
|
unsigned int idx = 0;
|
|
const auto flag_tiles = flag->get_tiles();
|
|
for (uint32_t y = selection_y0; y <= selection_y1; ++y)
|
|
{
|
|
for (uint32_t x = selection_x0; x <= selection_x1; ++x)
|
|
{
|
|
raw_types[idx] = 0;
|
|
if (!use_selection || selection.is_selected(x, y))
|
|
{
|
|
if (flag_budget[block_variant.item] == 0)
|
|
{
|
|
new MessageBox(context_, String("Not enough ") + gameState.get_item_name(block_variant.item).c_str() + " blocks left in flag budget");
|
|
return;
|
|
}
|
|
if (!use_selection)
|
|
{
|
|
const uint16_t th = flag->get_tile_height(x, y);
|
|
if (th < local_top_h)
|
|
{
|
|
printf("Setting min height to %u\n", local_top_h);
|
|
cmd.push_area.height = local_top_h;
|
|
}
|
|
// check whether there is already a block at the mouse location
|
|
if (local_top_h < th && flag->get_tile_type(x, y, local_top_h))
|
|
{
|
|
printf("Not double adding\n");
|
|
return; // we can return since when not using selection, it's a single tile
|
|
}
|
|
}
|
|
|
|
raw_types[idx] = palette_index;
|
|
flag_budget[block_variant.item]--;
|
|
const unsigned int orgidx = (y - flag->y0) * (flag->x1 - flag->x0 + 1) + x - flag->x0;
|
|
const auto &original_tiles = std::get<2>(*flagUnderConstruction)->tiles;
|
|
const uint8_t previous_block = local_top_h < original_tiles[orgidx].size() ? original_tiles[orgidx][local_top_h] : 0;
|
|
const uint8_t new_block = currentBlockVariant;
|
|
if (previous_block && new_block == previous_block)
|
|
{
|
|
flag_budget[ITEM_LABOUR] += cc::get_removal_labour_cost_for_height(local_top_h);
|
|
}
|
|
else
|
|
{
|
|
const uint32_t labour_cost = cc::get_build_labour_cost_for_height(local_top_h, bonus);
|
|
if (flag_budget[ITEM_LABOUR] < labour_cost)
|
|
{
|
|
new MessageBox(context_, String("Not enough ") + gameState.get_item_name(ITEM_LABOUR).c_str() + " units left in flag budget");
|
|
return;
|
|
}
|
|
flag_budget[ITEM_LABOUR] -= labour_cost;
|
|
}
|
|
}
|
|
++idx;
|
|
}
|
|
}
|
|
uint8_t encoded_types[65536];
|
|
uint32_t encoded_types_len;
|
|
if (!cc::encode_blocks(raw_types, idx, encoded_types, &encoded_types_len))
|
|
{
|
|
printf("Failed to encode single type, should never happen\n");
|
|
return;
|
|
}
|
|
cmd.push_area.type_ptr = encoded_types;
|
|
cmd.push_area.type_len = encoded_types_len;
|
|
|
|
if (map.command(cmd, &gameState.playerState))
|
|
{
|
|
undo.Push({std::move(map.get_flag_tiles(flag->id))});
|
|
SaveBackup();
|
|
std::deque<std::shared_ptr<Flag>> flags;
|
|
flags.push_back(flag);
|
|
cityMesh->RebuildFlags(flags, GetEffectiveSelection(), false);
|
|
}
|
|
flag->budget = flag_budget;
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleAddBlock(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
const bool flat = eventData[AddBlock::P_FLAT].GetBool();
|
|
AddBlock(true, flat);
|
|
}
|
|
|
|
void CryptoCityUrho3D::SetNoFlagUnderConstruction()
|
|
{
|
|
undo.Clear();
|
|
RemoveBackup();
|
|
flagUnderConstruction = boost::none;
|
|
}
|
|
|
|
uint8_t CryptoCityUrho3D::get_block_from_palette_index(const std::shared_ptr<Flag> &flag, uint8_t idx) const
|
|
{
|
|
if (idx == 0)
|
|
return ITEM_NONE;
|
|
if (idx >= flag->palette.size())
|
|
{
|
|
new MessageBox(context_, "Invalid palette index");
|
|
return ITEM_NONE;
|
|
}
|
|
const uint16_t variant = flag->palette[idx];
|
|
if (variant == cc::BLOCK_VARIANT_NONE)
|
|
{
|
|
new MessageBox(context_, "Block variant is NONE");
|
|
return ITEM_NONE;
|
|
}
|
|
const cc::block_variant_t block_variant = cc::get_block_variant(variant);
|
|
const uint8_t block = block_variant.item;
|
|
return block;
|
|
}
|
|
|
|
void CryptoCityUrho3D::RemoveBlock(bool use_selection, bool top_level)
|
|
{
|
|
UnsetFocus();
|
|
if (use_selection && !hasSelection_)
|
|
return;
|
|
if (!use_selection && (!mouse_x || !mouse_y))
|
|
return;
|
|
|
|
uint32_t selection_x0 = use_selection ? selection.x0 : mouse_x;
|
|
uint32_t selection_y0 = use_selection ? selection.y0 : mouse_y;
|
|
uint32_t selection_x1 = use_selection ? selection.x1 : mouse_x;
|
|
uint32_t selection_y1 = use_selection ? selection.y1 : mouse_y;
|
|
|
|
std::shared_ptr<Flag> flag = map.get_flag(selection_x0, selection_y0);
|
|
if (!flag)
|
|
{
|
|
if (use_selection)
|
|
new MessageBox(context_, "There is no flag there");
|
|
return;
|
|
}
|
|
if (flag->role == ROLE_EMPTY)
|
|
{
|
|
new MessageBox(context_, "Select building settings first");
|
|
return;
|
|
}
|
|
if (flagUnderConstruction && std::get<0>(*flagUnderConstruction) != flag->id)
|
|
{
|
|
new MessageBox(context_, "You can only build on a single lot at once: either commit or cancel the one you're buildng on now first");
|
|
return;
|
|
}
|
|
|
|
selection_x0 = std::max(selection_x0, flag->x0);
|
|
selection_y0 = std::max(selection_y0, flag->y0);
|
|
selection_x1 = std::min(selection_x1, flag->x1);
|
|
selection_y1 = std::min(selection_y1, flag->y1);
|
|
|
|
Flag::unpacker unpacker(flag);
|
|
|
|
EndModelPlacement();
|
|
if (!flagUnderConstruction)
|
|
{
|
|
auto tiles = flag->get_tiles();
|
|
flagUnderConstruction = std::make_tuple(flag->id, flag->palette, tiles, std::shared_ptr<Flag::unpacker>(new Flag::unpacker(flag)));
|
|
undo.Push({std::move(tiles)});
|
|
SaveBackup();
|
|
if (flag->owner != gameState.playerState.id)
|
|
new MessageBox(context_, "You do not own this flag:\nyou will not be able to submit any change to the blockchain", "Info");
|
|
ui->AddToastNotification("New build started. When done, either approve or abandon it\nPress/hold X to select block type to build with");
|
|
}
|
|
|
|
Command cmd;
|
|
cmd.type = Command::cmd_pop_area;
|
|
cmd.owner = gameState.playerState.id;
|
|
cmd.pop_area.flag = flag->id;
|
|
cmd.pop_area.dx = selection_x0 - flag->x0;
|
|
cmd.pop_area.dy = selection_y0 - flag->y0;
|
|
cmd.pop_area.wm1 = selection_x1 - selection_x0;
|
|
cmd.pop_area.hm1 = selection_y1 - selection_y0;
|
|
cmd.pop_area.height = use_selection ? -1 : mouse_h - flag->base_height;
|
|
|
|
const auto flag_tile_data = flag->get_tiles();
|
|
const auto &flag_tiles = flag_tile_data->tiles;
|
|
if (use_selection && top_level)
|
|
{
|
|
cmd.pop_area.height = 0;
|
|
for (uint32_t y = selection_y0; y <= selection_y1; ++y)
|
|
for (uint32_t x = selection_x0; x <= selection_x1; ++x)
|
|
if (selection.is_selected(x, y))
|
|
cmd.pop_area.height = std::max<uint16_t>(cmd.pop_area.height, flag_tiles[(y - flag->y0) * (flag->x1 - flag->x0 + 1) + x - flag->x0].size());
|
|
--cmd.pop_area.height; // will get to -1 if nothing if empty
|
|
}
|
|
|
|
const uint32_t bonus = cc::get_build_labour_cost_for_height_bonus(gameState.has_discovery(DISCOVERY_IMPROVED_SCAFFOLDING), gameState.has_discovery(DISCOVERY_ADVANCED_SCAFFOLDING));
|
|
|
|
uint8_t raw_types[65536];
|
|
unsigned int idx = 0;
|
|
uint32_t blocks_recovered[256] = {0};
|
|
uint32_t labour_recovered = 0, labour_expended = 0;
|
|
for (uint32_t y = selection_y0; y <= selection_y1; ++y)
|
|
{
|
|
for (uint32_t x = selection_x0; x <= selection_x1; ++x)
|
|
{
|
|
const bool selected = !use_selection || selection.is_selected(x, y) ? 1 : 0;
|
|
if (selected)
|
|
{
|
|
const unsigned int orgidx = (y - flag->y0) * (flag->x1 - flag->x0 + 1) + x - flag->x0;
|
|
const uint16_t th = flag_tiles[orgidx].size();
|
|
if (flag_tiles[orgidx].empty())
|
|
{
|
|
raw_types[idx++] = 0;
|
|
continue;
|
|
}
|
|
const uint16_t removal_height = use_selection ? top_level ? cmd.pop_area.height : th - 1 : mouse_h - flag->base_height;
|
|
if (removal_height >= th || flag_tiles[orgidx][removal_height] == 0)
|
|
{
|
|
raw_types[idx++] = 0;
|
|
continue;
|
|
}
|
|
const auto &original_tiles = std::get<2>(*flagUnderConstruction)->tiles;
|
|
const uint8_t previous_block = get_block_from_palette_index(flag, removal_height < original_tiles[orgidx].size() ? original_tiles[orgidx][removal_height] : 0);
|
|
const uint8_t new_block = get_block_from_palette_index(flag, removal_height < flag_tiles[orgidx].size() ? flag_tiles[orgidx][removal_height] : 0);
|
|
if (new_block == ITEM_NONE)
|
|
return;
|
|
blocks_recovered[new_block]++;
|
|
if (previous_block && previous_block == new_block)
|
|
labour_expended += cc::get_removal_labour_cost_for_height(removal_height);
|
|
else
|
|
labour_recovered += cc::get_build_labour_cost_for_height(removal_height, bonus);
|
|
}
|
|
raw_types[idx++] = selected;
|
|
}
|
|
}
|
|
if (idx == 0)
|
|
return;
|
|
uint8_t encoded_types[65536];
|
|
uint32_t encoded_types_len;
|
|
if (!cc::encode_blocks(raw_types, idx, encoded_types, &encoded_types_len))
|
|
{
|
|
printf("Failed to encode single type, should never happen\n");
|
|
return;
|
|
}
|
|
cmd.push_area.type_ptr = encoded_types;
|
|
cmd.push_area.type_len = encoded_types_len;
|
|
|
|
for (size_t i = 0; i < 256; ++i)
|
|
{
|
|
flag->budget[i] += blocks_recovered[i];
|
|
}
|
|
flag->budget[ITEM_LABOUR] += labour_recovered;
|
|
if (labour_expended > flag->budget[ITEM_LABOUR])
|
|
{
|
|
new MessageBox(context_, "Not enough labour in budget");
|
|
return;
|
|
}
|
|
flag->budget[ITEM_LABOUR] -= labour_expended;
|
|
|
|
if (map.command(cmd, &gameState.playerState))
|
|
{
|
|
undo.Push({std::move(map.get_flag_tiles(flag->id))});
|
|
SaveBackup();
|
|
std::deque<std::shared_ptr<Flag>> flags;
|
|
flags.push_back(flag);
|
|
cityMesh->RebuildFlags(flags, GetEffectiveSelection(), false);
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::RemoveHiddenBlocks()
|
|
{
|
|
UnsetFocus();
|
|
if (!hasSelection_)
|
|
return;
|
|
|
|
std::shared_ptr<Flag> flag = map.get_flag(selection.x0, selection.y0);
|
|
if (!flag)
|
|
{
|
|
new MessageBox(context_, "There is no flag there");
|
|
return;
|
|
}
|
|
if (flag->role == ROLE_EMPTY)
|
|
{
|
|
new MessageBox(context_, "Select building settings first");
|
|
return;
|
|
}
|
|
if (flagUnderConstruction && std::get<0>(*flagUnderConstruction) != flag->id)
|
|
{
|
|
new MessageBox(context_, "You can only build on a single lot at once: either commit or cancel the one you're buildng on now first");
|
|
return;
|
|
}
|
|
|
|
Flag::unpacker unpacker(flag);
|
|
|
|
if (!flagUnderConstruction)
|
|
{
|
|
auto tiles = flag->get_tiles();
|
|
flagUnderConstruction = std::make_tuple(flag->id, flag->palette, tiles, std::shared_ptr<Flag::unpacker>(new Flag::unpacker(flag)));
|
|
undo.Push({std::move(tiles)});
|
|
SaveBackup();
|
|
if (flag->owner != gameState.playerState.id)
|
|
new MessageBox(context_, "You do not own this flag:\nyou will not be able to submit any change to the blockchain", "Info");
|
|
ui->AddToastNotification("New build started. When done, either approve or abandon it\nPress/hold X to select block type to build with");
|
|
}
|
|
|
|
const uint32_t bonus = cc::get_build_labour_cost_for_height_bonus(gameState.has_discovery(DISCOVERY_IMPROVED_SCAFFOLDING), gameState.has_discovery(DISCOVERY_ADVANCED_SCAFFOLDING));
|
|
|
|
const auto flag_tile_data = flag->get_tiles();
|
|
const auto &flag_tiles = flag_tile_data->tiles;
|
|
uint16_t flag_height = 0;
|
|
for (const auto &tile: flag_tiles)
|
|
flag_height = std::max<uint16_t>(flag_height, tile.size());
|
|
|
|
uint8_t raw_types[65536];
|
|
uint32_t blocks_recovered[256] = {0};
|
|
uint32_t labour_recovered = 0, labour_expended = 0;
|
|
for (uint16_t h = 0; h < flag_height; ++h)
|
|
{
|
|
uint32_t minx = std::numeric_limits<uint32_t>::max();
|
|
uint32_t maxx = 0;
|
|
uint32_t miny = std::numeric_limits<uint32_t>::max();
|
|
uint32_t maxy = 0;
|
|
memset(raw_types, 0, sizeof(raw_types));
|
|
|
|
uint32_t idx = 0;
|
|
for (uint32_t y = flag->y0; y <= flag->y1; ++y)
|
|
{
|
|
for (uint32_t x = flag->x0; x <= flag->x1; ++x, ++idx)
|
|
{
|
|
if (x == flag->x0 || x == flag->x1 || y == flag->y0 || y == flag->y1)
|
|
continue;
|
|
if ((size_t)h + 1 >= flag_tiles[idx].size())
|
|
continue;
|
|
if (flag_tiles[idx][h] == 0)
|
|
continue;
|
|
if (flag_tiles[idx][h + 1] == 0)
|
|
continue;
|
|
if (h > 0 && flag_tiles[idx][h-1] == 0)
|
|
continue;
|
|
const uint32_t idx_north = idx - (flag->x1 -flag->x0 + 1);
|
|
const uint32_t idx_west = idx - 1;
|
|
const uint32_t idx_south = idx + (flag->x1 -flag->x0 + 1);
|
|
const uint32_t idx_east = idx + 1;
|
|
if (flag_tiles[idx_north].size() <= h || flag_tiles[idx_north][h] == 0)
|
|
continue;
|
|
if (flag_tiles[idx_west].size() <= h || flag_tiles[idx_west][h] == 0)
|
|
continue;
|
|
if (flag_tiles[idx_south].size() <= h || flag_tiles[idx_south][h] == 0)
|
|
continue;
|
|
if (flag_tiles[idx_east].size() <= h || flag_tiles[idx_east][h] == 0)
|
|
continue;
|
|
|
|
const auto &original_tiles = std::get<2>(*flagUnderConstruction)->tiles;
|
|
const uint8_t previous_block = get_block_from_palette_index(flag, h < original_tiles[idx].size() ? original_tiles[idx][h] : 0);
|
|
const uint8_t new_block = get_block_from_palette_index(flag, h < flag_tiles[idx].size() ? flag_tiles[idx][h] : 0);
|
|
if (new_block == ITEM_NONE)
|
|
{
|
|
printf("Possible WTF\n");
|
|
return;
|
|
}
|
|
blocks_recovered[new_block]++;
|
|
if (previous_block && previous_block == new_block)
|
|
labour_expended += cc::get_removal_labour_cost_for_height(h);
|
|
else
|
|
labour_recovered += cc::get_build_labour_cost_for_height(h, bonus);
|
|
|
|
raw_types[idx] = flag_tiles[idx][h];
|
|
minx = std::min(minx, x);
|
|
maxx = std::max(maxx, x);
|
|
miny = std::min(miny, y);
|
|
maxy = std::max(maxy, y);
|
|
}
|
|
}
|
|
|
|
if (maxx < minx)
|
|
continue;
|
|
|
|
Command cmd;
|
|
cmd.type = Command::cmd_pop_area;
|
|
cmd.owner = gameState.playerState.id;
|
|
cmd.pop_area.flag = flag->id;
|
|
cmd.pop_area.dx = 0;
|
|
cmd.pop_area.dy = 0;
|
|
cmd.pop_area.wm1 = flag->x1 - flag->x0;
|
|
cmd.pop_area.hm1 = flag->y1 - flag->y0;
|
|
cmd.pop_area.height = h;
|
|
|
|
uint8_t encoded_types[65536];
|
|
uint32_t encoded_types_len;
|
|
if (!cc::encode_blocks(raw_types, idx, encoded_types, &encoded_types_len))
|
|
{
|
|
printf("Failed to encode single type, should never happen\n");
|
|
return;
|
|
}
|
|
cmd.push_area.type_ptr = encoded_types;
|
|
cmd.push_area.type_len = encoded_types_len;
|
|
|
|
if (!map.command(cmd, &gameState.playerState))
|
|
{
|
|
new MessageBox(context_, "Error removing hidden blocks");
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool changed = false;
|
|
for (size_t i = 0; i < 256; ++i)
|
|
if (blocks_recovered[i])
|
|
changed = true;
|
|
|
|
if (!changed)
|
|
{
|
|
ui->AddNotification("No hidden blocks to remove");
|
|
return;
|
|
}
|
|
|
|
undo.Push({std::move(map.get_flag_tiles(flag->id))});
|
|
SaveBackup();
|
|
std::deque<std::shared_ptr<Flag>> flags;
|
|
flags.push_back(flag);
|
|
cityMesh->RebuildFlags(flags, GetEffectiveSelection(), false);
|
|
|
|
for (size_t i = 0; i < 256; ++i)
|
|
{
|
|
flag->budget[i] += blocks_recovered[i];
|
|
if (blocks_recovered[i] > 0)
|
|
ui->AddNotification(String(blocks_recovered[i]) + " " + gameState.get_item_name(i).c_str() + " recovered");
|
|
}
|
|
flag->budget[ITEM_LABOUR] += labour_recovered;
|
|
if (labour_expended > flag->budget[ITEM_LABOUR])
|
|
{
|
|
new MessageBox(context_, "Not enough labour in budget");
|
|
return;
|
|
}
|
|
flag->budget[ITEM_LABOUR] -= labour_expended;
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleRemoveBlock(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
const bool top_level = eventData[RemoveBlock::P_TOP_LEVEL].GetBool();
|
|
RemoveBlock(true, top_level);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleRemoveHiddenBlocks(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
RemoveHiddenBlocks();
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleDemolish(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to demolish a building");
|
|
return;
|
|
}
|
|
std::shared_ptr<Flag> flag = map.get_flag(selection.x0, selection.y0);
|
|
if (!flag)
|
|
{
|
|
new MessageBox(context_, "There is no flag there");
|
|
return;
|
|
}
|
|
if (flag->owner != gameState.playerState.id)
|
|
{
|
|
new MessageBox(context_, "You do not own this flag");
|
|
return;
|
|
}
|
|
if (flag->role == ROLE_EMPTY)
|
|
{
|
|
new MessageBox(context_, "There is no building there");
|
|
return;
|
|
}
|
|
|
|
cryptonote::cc_command_demolish_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.flag = flag->id;
|
|
cmd.role = flag->role;
|
|
cmd.economic_power = flag->economic_power;
|
|
cmd.repair = flag->repair;
|
|
cmd.construction_height = flag->construction_height;
|
|
cmd.last_service_height = flag->last_service_height;
|
|
cmd.service_price = flag->service_price;
|
|
cmd.name = flag->name_hidden;
|
|
cmd.ignore = flag->ignore;
|
|
cmd.active = flag->active;
|
|
cmd.crop = flag->crop;
|
|
cmd.sow_height = flag->sow_height;
|
|
cmd.num_missed_ticks = flag->num_missed_ticks;
|
|
for (const auto &e: flag->budget)
|
|
{
|
|
if (e.second)
|
|
cmd.budget.push_back({e.first, e.second});
|
|
}
|
|
cmd.tiles = flag->get_packed_tiles();
|
|
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleDestroyFlag(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to destroy a flag");
|
|
return;
|
|
}
|
|
std::shared_ptr<Flag> flag = map.get_flag(selection.x0, selection.y0);
|
|
if (!flag)
|
|
{
|
|
new MessageBox(context_, "There is no flag there");
|
|
return;
|
|
}
|
|
if (flag->owner != gameState.playerState.id)
|
|
{
|
|
new MessageBox(context_, "You do not own this flag");
|
|
return;
|
|
}
|
|
if (flag->role != ROLE_EMPTY)
|
|
{
|
|
new MessageBox(context_, "There is a building there, destroy it first");
|
|
return;
|
|
}
|
|
|
|
cryptonote::cc_command_destroy_flag_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.id = flag->id;
|
|
cmd.city = gameState.cityState.id;
|
|
cmd.x0 = flag->x0;
|
|
cmd.y0 = flag->y0;
|
|
cmd.x1 = flag->x1;
|
|
cmd.y1 = flag->y1;
|
|
cmd.construction_height = flag->construction_height;
|
|
cmd.crop = flag->crop;
|
|
cmd.sow_height = flag->sow_height;
|
|
cmd.vegetables_nutrients = flag->vegetables_nutrients;
|
|
cmd.grain_nutrients = flag->grain_nutrients;
|
|
cmd.palette = flag->palette;
|
|
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleUndo(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
EndModelPlacement();
|
|
Undo();
|
|
}
|
|
|
|
void CryptoCityUrho3D::Undo()
|
|
{
|
|
std::shared_ptr<Flag> flag = map.get_flag(selection.x0, selection.y0);
|
|
if (!flag)
|
|
return;
|
|
if (flagUnderConstruction && std::get<0>(*flagUnderConstruction) != flag->id)
|
|
{
|
|
new MessageBox(context_, "You can only build on a single lot at once: either commit or cancel the one you're buildng on now first");
|
|
return;
|
|
}
|
|
if (!undo.CanUndo())
|
|
{
|
|
ui->AddNotification("Cannot undo");
|
|
return;
|
|
}
|
|
|
|
Flag::unpacker unpacker(flag);
|
|
|
|
const std::shared_ptr<TileData> modified_tiles = map.get_flag_tiles(flag->id);
|
|
auto state = undo.Undo();
|
|
const auto &original_tiles = state;
|
|
UpdateBudget(flag, original_tiles, modified_tiles, true);
|
|
map.set_flag_tiles(std::get<0>(*flagUnderConstruction), state);
|
|
std::deque<std::shared_ptr<Flag>> flags;
|
|
flags.push_back(flag);
|
|
cityMesh->RebuildFlags(flags, GetEffectiveSelection(), false);
|
|
SaveBackup();
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleRedo(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
EndModelPlacement();
|
|
Redo();
|
|
}
|
|
|
|
void CryptoCityUrho3D::Redo()
|
|
{
|
|
std::shared_ptr<Flag> flag = map.get_flag(selection.x0, selection.y0);
|
|
if (!flag)
|
|
return;
|
|
if (flagUnderConstruction && std::get<0>(*flagUnderConstruction) != flag->id)
|
|
{
|
|
new MessageBox(context_, "You can only build on a single lot at once: either commit or cancel the one you're buildng on now first");
|
|
return;
|
|
}
|
|
if (!undo.CanRedo())
|
|
{
|
|
ui->AddNotification("Cannot redo");
|
|
return;
|
|
}
|
|
|
|
Flag::unpacker unpacker(flag);
|
|
|
|
const std::shared_ptr<TileData> modified_tiles = map.get_flag_tiles(flag->id);
|
|
auto state = undo.Redo();
|
|
const std::shared_ptr<TileData> &new_tiles = state;
|
|
UpdateBudget(flag, modified_tiles, new_tiles, false);
|
|
map.set_flag_tiles(std::get<0>(*flagUnderConstruction), state);
|
|
std::deque<std::shared_ptr<Flag>> flags;
|
|
flags.push_back(flag);
|
|
cityMesh->RebuildFlags(flags, GetEffectiveSelection(), false);
|
|
SaveBackup();
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleLoadModel(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
if (!hasSelection_)
|
|
{
|
|
new MessageBox(context_, "No selection, cannot place the model");
|
|
return;
|
|
}
|
|
std::shared_ptr<Flag> flag = map.get_flag(selection.x0, selection.y0);
|
|
if (!flag)
|
|
{
|
|
new MessageBox(context_, "Select a flag first");
|
|
return;
|
|
}
|
|
if (flag->role == ROLE_EMPTY)
|
|
{
|
|
new MessageBox(context_, "Select building settings first");
|
|
return;
|
|
}
|
|
if (flagUnderConstruction && std::get<0>(*flagUnderConstruction) != flag->id)
|
|
{
|
|
new MessageBox(context_, "You can only build on a single lot at once: either commit or cancel the one you're buildng on now first");
|
|
return;
|
|
}
|
|
|
|
if (flag->owner != gameState.playerState.id)
|
|
new MessageBox(context_, "You do not own this flag:\nyou will not be able to submit any change to the blockchain", "Info");
|
|
|
|
EndModelPlacement();
|
|
String filename = eventData[LoadModel::P_FILENAME].GetString();
|
|
std::string s;
|
|
if (!epee::file_io_utils::load_file_to_string(filename.CString(), s))
|
|
{
|
|
new MessageBox(context_, "Failed to load file");
|
|
return;
|
|
}
|
|
if (LoadModelData(flag, s))
|
|
{
|
|
placing_model = true;
|
|
placing_model_data = std::move(s);
|
|
placing_model_flag = flag;
|
|
placing_model_dx = 0;
|
|
placing_model_dy = 0;
|
|
placing_model_dh = 0;
|
|
placing_model_dr = 0;
|
|
ui->OnRequestModelPlacement();
|
|
}
|
|
}
|
|
|
|
static void dec(uint32_t &secondary, uint32_t &primary, uint32_t n)
|
|
{
|
|
if (primary >= n)
|
|
{
|
|
primary -= n;
|
|
}
|
|
else
|
|
{
|
|
secondary -= n - primary;
|
|
primary = 0;
|
|
}
|
|
}
|
|
|
|
bool CryptoCityUrho3D::LoadModelData(const std::shared_ptr<Flag> &flag, const std::string &data, std::map<uint32_t, uint32_t> existing_tiles, bool silent)
|
|
{
|
|
const bool has_existing_tiles = !existing_tiles.empty();
|
|
std::list<MagicaModel> mm;
|
|
if (!load_magica_data(mm, data))
|
|
{
|
|
if (!silent)
|
|
new MessageBox(context_, "Failed to load model");
|
|
return false;
|
|
}
|
|
if (mm.size() > 1)
|
|
{
|
|
if (!silent)
|
|
ui->AddNotification("File contains more than one model, using first one");
|
|
}
|
|
|
|
auto get_model_width = [](const MagicaModel &m, int dr) -> uint32_t
|
|
{
|
|
return (dr & 1) ? m.depth : m.width;
|
|
};
|
|
auto get_model_depth = [](const MagicaModel &m, int dr) -> uint32_t
|
|
{
|
|
return (dr & 1) ? m.width : m.depth;
|
|
};
|
|
auto get_model_x = [](const MagicaModel &m, int x, int y, int dr) -> uint32_t
|
|
{
|
|
switch (dr & 3)
|
|
{
|
|
default:
|
|
case 0: return x;
|
|
case 1: return m.depth - 1 - y;
|
|
case 2: return m.width - 1 - x;
|
|
case 3: return y;
|
|
}
|
|
};
|
|
auto get_model_y = [](const MagicaModel &m, int x, int y, int dr) -> uint32_t
|
|
{
|
|
switch (dr & 3)
|
|
{
|
|
default:
|
|
case 0: return y;
|
|
case 1: return x;
|
|
case 2: return m.depth - 1 - y;
|
|
case 3: return m.width - 1 - x;
|
|
}
|
|
};
|
|
|
|
// if just one tile is selected, load to the entire flag
|
|
Selection sel = GetEffectiveSelection();
|
|
if (sel.bounding_box_width() == 1 && sel.bounding_box_height() == 1)
|
|
SelectWholeFlag();
|
|
|
|
sel = GetEffectiveSelection();
|
|
if (get_model_width(mm.front(), placing_model_dr) > sel.bounding_box_width() || get_model_depth(mm.front(), placing_model_dr) > sel.bounding_box_height())
|
|
{
|
|
if (!silent)
|
|
ui->AddNotification("Model is larger than selection, it will be clipped");
|
|
}
|
|
|
|
Flag::unpacker unpacker(flag);
|
|
|
|
const uint32_t bmx0 = std::max(flag->x0, sel.x0);
|
|
const uint32_t bmy0 = std::max(flag->y0, sel.y0);
|
|
const uint32_t bmx1 = std::min<uint32_t>(std::min(flag->x1, sel.x1), sel.x0 + std::min<uint32_t>(get_model_width(mm.front(), placing_model_dr), 256) - 1);
|
|
const uint32_t bmy1 = std::min<uint32_t>(std::min(flag->y1, sel.y1), sel.y0 + std::min<uint32_t>(get_model_depth(mm.front(), placing_model_dr), 256) - 1);
|
|
const uint32_t selx0 = sel.x0 + placing_model_dx;
|
|
const uint32_t sely0 = sel.y0 + placing_model_dy;
|
|
const uint32_t selx1 = sel.x1 + placing_model_dx;
|
|
const uint32_t sely1 = sel.y1 + placing_model_dy;
|
|
const uint32_t mx0 = std::max(flag->x0, selx0);
|
|
const uint32_t my0 = std::max(flag->y0, sely0);
|
|
const uint32_t mx1 = std::min<uint32_t>(std::min(flag->x1, selx1), selx0 + std::min<uint32_t>(get_model_width(mm.front(), placing_model_dr), 256) - 1);
|
|
const uint32_t my1 = std::min<uint32_t>(std::min(flag->y1, sely1), sely0 + std::min<uint32_t>(get_model_depth(mm.front(), placing_model_dr), 256) - 1);
|
|
|
|
const std::vector<uint16_t> original_palette = flag->palette;
|
|
|
|
// merge palettes
|
|
uint8_t palette_lut[256];
|
|
for (int i = 0; i < 256; ++i)
|
|
palette_lut[i] = 0;
|
|
if (!mm.front().palette.empty())
|
|
{
|
|
const auto &imported_palette = mm.front().palette;
|
|
const auto &flag_palette = flag->palette;
|
|
for (size_t i = 1; i < imported_palette.size(); ++i)
|
|
{
|
|
bool present = false;
|
|
for (size_t j = 1; j < flag_palette.size(); ++j)
|
|
{
|
|
if (imported_palette[i] == flag_palette[j])
|
|
{
|
|
palette_lut[i] = j;
|
|
present = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!present)
|
|
{
|
|
if (flag_palette.size() == 256)
|
|
{
|
|
if (!silent)
|
|
new MessageBox(context_, "No space in palette");
|
|
return false;
|
|
}
|
|
flag->palette.push_back(imported_palette[i]);
|
|
palette_lut[i] = flag->palette.size() - 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// work out the model height for each tile we're loading
|
|
uint32_t model_height[65536], *model_heightptr = model_height;
|
|
memset(model_height, 0, sizeof(model_height));
|
|
for (uint32_t y = bmy0; y <= bmy1; ++y)
|
|
{
|
|
for (uint32_t x = bmx0; x <= bmx1; ++x, ++model_heightptr)
|
|
{
|
|
const uint16_t tile_height = flag->get_tile_height(x, y);
|
|
if (tile_height > 0)
|
|
{
|
|
uint32_t h = 0;
|
|
while (h < tile_height && flag->get_tile_type(x, y, h) == 0)
|
|
++h;
|
|
*model_heightptr = h;
|
|
}
|
|
else
|
|
{
|
|
*model_heightptr = std::numeric_limits<uint16_t>::max();
|
|
}
|
|
}
|
|
}
|
|
|
|
// calculate a base height so that no model block appears below ground or existing blocks
|
|
model_heightptr = model_height;
|
|
uint16_t base_height = 0;
|
|
for (uint32_t y = bmy0; y <= bmy1; ++y)
|
|
{
|
|
for (uint32_t x = bmx0; x <= bmx1; ++x, ++model_heightptr)
|
|
{
|
|
const uint16_t terrain_height = cityMesh->GetTerrainHeight(x, y);
|
|
const uint32_t combined_height = terrain_height + flag->get_tile_height(x, y);
|
|
if (combined_height < flag->base_height)
|
|
{
|
|
if (!silent)
|
|
new MessageBox(context_, "Internal error placing model: combined height too low");
|
|
return false;
|
|
}
|
|
const uint16_t relative_height = combined_height - flag->base_height;
|
|
const uint16_t needed = relative_height < *model_heightptr ? 0 : relative_height - *model_heightptr;
|
|
if (needed > base_height)
|
|
base_height = needed;
|
|
}
|
|
}
|
|
|
|
const int32_t signed_base_height = (int32_t)base_height + placing_model_dh;
|
|
base_height = std::max<int32_t>((int32_t)base_height + placing_model_dh, 0);
|
|
const uint32_t cut = signed_base_height < 0 ? - signed_base_height : 0;
|
|
|
|
uint64_t n_blocks_total = 0;
|
|
for (uint16_t h = 0; h < mm.front().height; ++h )
|
|
{
|
|
const uint8_t *model_data = mm.front().data.data() + mm.front().width * mm.front().depth * h;
|
|
for (uint32_t y = 0; y < mm.front().depth; ++y)
|
|
for (uint32_t x = 0; x < mm.front().width; ++x)
|
|
if (model_data[y * mm.front().width + x])
|
|
++n_blocks_total;
|
|
}
|
|
|
|
std::map<uint32_t, uint32_t> flag_budget = flag->budget;
|
|
const uint32_t player_level = cc::get_badge_score(gameState.playerState.badges).second;
|
|
|
|
const uint32_t bonus = cc::get_build_labour_cost_for_height_bonus(gameState.has_discovery(DISCOVERY_IMPROVED_SCAFFOLDING), gameState.has_discovery(DISCOVERY_ADVANCED_SCAFFOLDING));
|
|
if (!silent)
|
|
{
|
|
std::map<uint32_t, uint32_t> materials_needed;
|
|
for (uint16_t h = cut; h < mm.front().height; ++h )
|
|
{
|
|
const uint8_t *model_data = mm.front().data.data() + mm.front().width * mm.front().depth * h;
|
|
const uint16_t height = base_height + h - cut;
|
|
const uint32_t labour_cost = cc::get_build_labour_cost_for_height(height, bonus);
|
|
for (uint32_t y = my0; y <= my1; ++y)
|
|
{
|
|
for (uint32_t x = mx0; x <= mx1; ++x)
|
|
{
|
|
const uint32_t mx = get_model_x(mm.front(), x - selx0, y - sely0, placing_model_dr);
|
|
const uint32_t my = get_model_y(mm.front(), x - selx0, y - sely0, placing_model_dr);
|
|
const uint8_t material = model_data[my * mm.front().width + mx];
|
|
const bool selected = selection.is_selected(x, y);
|
|
if (selected)
|
|
{
|
|
const uint8_t raw_type = palette_lut[material];
|
|
if (raw_type >= flag->palette.size())
|
|
{
|
|
if (!silent)
|
|
new MessageBox(context_, "Raw type not in range");
|
|
return false;
|
|
}
|
|
if (raw_type > 0)
|
|
{
|
|
const cc::block_variant_t block_variant = cc::get_block_variant(flag->palette[raw_type]);
|
|
if (block_variant.unlock_level > player_level)
|
|
{
|
|
if (!silent)
|
|
new MessageBox(context_, "Model uses at least one material which is not unlocked yet: " + std::string(block_variant.name));
|
|
return false;
|
|
}
|
|
materials_needed[block_variant.item]++;
|
|
materials_needed[ITEM_LABOUR] += labour_cost;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string error;
|
|
for (const auto &e: materials_needed)
|
|
{
|
|
const auto i = flag_budget.find(e.first);
|
|
const auto j = existing_tiles.find(e.first);
|
|
const uint32_t available = (i == flag_budget.end() ? 0 : i->second) + (j == existing_tiles.end() ? 0 : j->second);
|
|
if (e.second > available)
|
|
{
|
|
error += std::to_string(e.second) + " " + gameState.get_item_name(e.first) + " needed, but only " + std::to_string(available) + " in budget (" + std::to_string(e.second - available) + " shortfall)\n";
|
|
}
|
|
}
|
|
if (!error.empty())
|
|
new MessageBox(context_, "Not enough resources:\n" + error);
|
|
}
|
|
|
|
bool ok = true;
|
|
uint64_t n_blocks_used = 0;
|
|
for (uint16_t h = cut; h < mm.front().height; ++h )
|
|
{
|
|
const uint8_t *model_data = mm.front().data.data() + mm.front().width * mm.front().depth * h;
|
|
|
|
Command cmd;
|
|
cmd.type = Command::cmd_push_area;
|
|
cmd.owner = gameState.playerState.id;
|
|
cmd.push_area.flag = flag->id;
|
|
cmd.push_area.dx = mx0 - flag->x0;
|
|
cmd.push_area.dy = my0 - flag->y0;
|
|
cmd.push_area.wm1 = mx1 - mx0;
|
|
cmd.push_area.hm1 = my1 - my0;
|
|
cmd.push_area.height = base_height + h - cut;
|
|
|
|
uint8_t raw_types[65536];
|
|
unsigned int idx = 0;
|
|
for (uint32_t y = my0; y <= my1; ++y)
|
|
{
|
|
for (uint32_t x = mx0; x <= mx1; ++x)
|
|
{
|
|
const uint32_t mx = get_model_x(mm.front(), x - selx0, y - sely0, placing_model_dr);
|
|
const uint32_t my = get_model_y(mm.front(), x - selx0, y - sely0, placing_model_dr);
|
|
const uint8_t material = model_data[my * mm.front().width + mx];
|
|
const bool selected = selection.is_selected(x, y);
|
|
if (selected)
|
|
{
|
|
const uint8_t raw_type = palette_lut[material];
|
|
if (raw_type >= flag->palette.size())
|
|
{
|
|
if (!silent)
|
|
new MessageBox(context_, "Raw type not in range");
|
|
return false;
|
|
}
|
|
if (raw_type > 0)
|
|
{
|
|
const cc::block_variant_t block_variant = cc::get_block_variant(flag->palette[raw_type]);
|
|
if (block_variant.unlock_level > player_level)
|
|
{
|
|
if (!silent)
|
|
new MessageBox(context_, "Model uses at least one material which is not unlocked yet: " + std::string(block_variant.name)
|
|
+ ", unlocked at level " + std::to_string(block_variant.unlock_level) + ", you are level " + std::to_string(player_level));
|
|
return false;
|
|
}
|
|
if (flag_budget[block_variant.item] == 0 && existing_tiles[block_variant.item] == 0)
|
|
{
|
|
if (!silent)
|
|
new MessageBox(context_, String("Not enough ") + gameState.get_item_name(block_variant.item).c_str() + " blocks left in flag budget");
|
|
return false;
|
|
}
|
|
const uint32_t labour_cost = cc::get_build_labour_cost_for_height(cmd.push_area.height, bonus);
|
|
if (flag_budget[ITEM_LABOUR] + existing_tiles[ITEM_LABOUR] < labour_cost)
|
|
{
|
|
if (!silent)
|
|
new MessageBox(context_, String("Not enough ") + gameState.get_item_name(ITEM_LABOUR).c_str() + " units left in flag budget");
|
|
return false;
|
|
}
|
|
dec(flag_budget[block_variant.item], existing_tiles[block_variant.item], 1);
|
|
dec(flag_budget[ITEM_LABOUR], existing_tiles[ITEM_LABOUR], labour_cost);
|
|
++n_blocks_used;
|
|
}
|
|
|
|
raw_types[idx++] = raw_type;
|
|
}
|
|
else
|
|
raw_types[idx++] = 0;
|
|
}
|
|
}
|
|
uint8_t encoded_types[65536];
|
|
uint32_t encoded_types_len;
|
|
if (!cc::encode_blocks(raw_types, idx, encoded_types, &encoded_types_len))
|
|
{
|
|
printf("Failed to encode single type, should never happen\n");
|
|
return false;
|
|
}
|
|
cmd.push_area.type_ptr = encoded_types;
|
|
cmd.push_area.type_len = encoded_types_len;
|
|
|
|
if (!flagUnderConstruction && !has_existing_tiles)
|
|
{
|
|
auto tiles = flag->get_tiles();
|
|
flagUnderConstruction = std::make_tuple(flag->id, original_palette, tiles, std::shared_ptr<Flag::unpacker>(new Flag::unpacker(flag)));
|
|
undo.Push({std::move(tiles)});
|
|
SaveBackup();
|
|
if (!silent)
|
|
ui->AddToastNotification("New build started. When done, either approve or abandon it\nPress/hold X to select block type to build with");
|
|
}
|
|
|
|
ok &= map.command(cmd, &gameState.playerState);
|
|
if (!ok)
|
|
break;
|
|
}
|
|
|
|
flag->budget = flag_budget;
|
|
|
|
const bool partial = n_blocks_used < n_blocks_total;
|
|
if (partial && !silent)
|
|
new MessageBox(context_, "Part of the model was not loaded due being outside the selection", "Info");
|
|
|
|
if (!has_existing_tiles)
|
|
undo.Push({std::move(map.get_flag_tiles(flag->id))});
|
|
SaveBackup();
|
|
gameState.add_dirty_flag(flag, false);
|
|
|
|
return true;
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleSaveModel(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
if (!hasSelection_)
|
|
{
|
|
new MessageBox(context_, "No selection, no model to save");
|
|
return;
|
|
}
|
|
std::shared_ptr<Flag> flag = map.get_flag(selection.x0, selection.y0);
|
|
if (!flag)
|
|
return;
|
|
|
|
std::string data;
|
|
const bool selection = eventData[SaveModel::P_SELECTION].GetBool();
|
|
if (!SaveModelData(flag, data, selection))
|
|
{
|
|
new MessageBox(context_, "Failed to save model");
|
|
return;
|
|
}
|
|
const String filename = eventData[SaveModel::P_FILENAME].GetString();
|
|
if (!epee::file_io_utils::save_string_to_file(filename.CString(), data))
|
|
{
|
|
new MessageBox(context_, "Failed to save model file");
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool CryptoCityUrho3D::SaveModelData(const std::shared_ptr<Flag> &flag, std::string &data, bool selection_only, bool silent)
|
|
{
|
|
MagicaModel mm;
|
|
|
|
const Selection sel = GetEffectiveSelection();
|
|
const uint32_t mx0 = selection_only ? std::max(flag->x0, sel.x0) : flag->x0;
|
|
const uint32_t my0 = selection_only ? std::max(flag->y0, sel.y0) : flag->y0;
|
|
const uint32_t mx1 = selection_only ? std::min(flag->x1, sel.x1) : flag->x1;
|
|
const uint32_t my1 = selection_only ? std::min(flag->y1, sel.y1) : flag->y1;
|
|
|
|
mm.width = mx1 - mx0 + 1;
|
|
mm.depth = my1 - my0 + 1;
|
|
mm.height = 0;
|
|
|
|
Flag::unpacker unpacker(flag);
|
|
|
|
for (uint16_t h = 0; ; ++h )
|
|
{
|
|
uint32_t n_blocks = 0;
|
|
mm.data.resize(mm.width * mm.depth * (h + 1));
|
|
uint8_t *model_data = mm.data.data() + mm.width * mm.depth * h;
|
|
const auto *tile = flag->get_raw_tiles() + mx0 - flag->x0 + (my0 - flag->y0) * (flag->x1 - flag->x0 + 1);
|
|
|
|
unsigned int idx = 0;
|
|
for (uint32_t y = my0; y <= my1; ++y, tile += flag->x1 - flag->x0 + 1 - mm.width)
|
|
{
|
|
for (uint32_t x = mx0; x <= mx1; ++x, ++tile)
|
|
{
|
|
uint8_t type = 0;
|
|
if (h < tile->get_height())
|
|
{
|
|
type = tile->get_type(h);
|
|
++n_blocks;
|
|
}
|
|
model_data[idx++] = type;
|
|
}
|
|
}
|
|
|
|
if (!n_blocks)
|
|
break;
|
|
++mm.height;
|
|
if (mm.height == 255)
|
|
{
|
|
if (!silent)
|
|
new MessageBox(context_, "Stopping at 255 blocks high (Magica Voxel model format restriction)");
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (mm.height == 0)
|
|
{
|
|
if (!silent)
|
|
new MessageBox(context_, "Nothing to save");
|
|
return true;
|
|
}
|
|
|
|
mm.palette = flag->palette;
|
|
|
|
if (!save_magica_data(std::list<MagicaModel>(1, mm), data))
|
|
{
|
|
if (!silent)
|
|
new MessageBox(context_, "Failed to save model");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleBuildingStats(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
if (!hasSelection_)
|
|
{
|
|
new MessageBox(context_, "No selection, select part of the building to check");
|
|
return;
|
|
}
|
|
std::shared_ptr<Flag> flag = map.get_flag(selection.x0, selection.y0);
|
|
if (!flag)
|
|
return;
|
|
|
|
Flag::unpacker unpacker(flag);
|
|
|
|
std::map<uint8_t, uint32_t> loaded_budget;
|
|
const std::shared_ptr<TileData> tiles = flag->get_tiles();
|
|
for (const auto &e: tiles->tiles)
|
|
{
|
|
for (const uint8_t block: e)
|
|
{
|
|
if (block == 0)
|
|
continue;
|
|
if (block >= flag->palette.size())
|
|
{
|
|
new MessageBox(context_, "Block is not in flag palette");
|
|
return;
|
|
}
|
|
const uint16_t variant = flag->palette[block];
|
|
const cc::block_variant_t block_variant = cc::get_block_variant(variant);
|
|
loaded_budget[block_variant.item] += 1;
|
|
}
|
|
}
|
|
|
|
std::string msg = "Blocks used in this building:\n";
|
|
for (const auto &e: flag->budget)
|
|
{
|
|
if (e.second == 0)
|
|
continue;
|
|
if (e.first >= ITEM_FIRST_BLOCK && e.first <= ITEM_LAST_BLOCK)
|
|
msg += std::to_string(loaded_budget[e.first]) + "/" + std::to_string(e.second) + " " + gameState.get_item_name(e.first) + "\n";
|
|
}
|
|
new MessageBox(context_, msg.c_str(), "Info");
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleChat(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!console->IsVisible())
|
|
ToggleConsole();
|
|
}
|
|
|
|
template<typename T> static bool GetJSONValue(const JSONValue &field, T &value);
|
|
template<> bool GetJSONValue(const JSONValue &field, bool &value) { if (!field.IsBool()) return false; value = field.GetBool(); return true; }
|
|
template<> bool GetJSONValue(const JSONValue &field, int &value) { if (!field.IsNumber()) return false; value = field.GetInt(); return true; }
|
|
template<> bool GetJSONValue(const JSONValue &field, unsigned &value) { if (!field.IsNumber()) return false; value = field.GetUInt(); return true; }
|
|
template<> bool GetJSONValue(const JSONValue &field, float &value) { if (!field.IsNumber()) return false; value = field.GetFloat(); return true; }
|
|
template<> bool GetJSONValue(const JSONValue &field, String &value) { if (!field.IsString()) return false; value = field.GetString(); return true; }
|
|
template<> bool GetJSONValue(const JSONValue &field, unsigned long &value)
|
|
{
|
|
if (!field.IsString()) return false;
|
|
const String s = field.GetString();
|
|
char *endptr = NULL;
|
|
errno = 0;
|
|
unsigned long ul = strtoul(s.CString(), &endptr, 10);
|
|
if (errno || (endptr && *endptr)) return false;
|
|
value = ul;
|
|
return true;
|
|
}
|
|
template<> bool GetJSONValue(const JSONValue &field, unsigned long long &value)
|
|
{
|
|
if (!field.IsString()) return false;
|
|
const String s = field.GetString();
|
|
char *endptr = NULL;
|
|
errno = 0;
|
|
unsigned long long ull = strtoull(s.CString(), &endptr, 10);
|
|
if (errno || (endptr && *endptr)) return false;
|
|
value = ull;
|
|
return true;
|
|
}
|
|
|
|
template<typename T>
|
|
T CryptoCityUrho3D::GetConfigValue(const String &filename, SharedPtr<JSONFile> &config, const char *section, const char *key, const T &default_value)
|
|
{
|
|
if (filename.Empty())
|
|
return default_value;
|
|
if (!config)
|
|
{
|
|
SharedPtr<JSONFile> tmp(DynamicCast<JSONFile>(context_->CreateObject(JSONFile::GetTypeStatic())));
|
|
if (!tmp)
|
|
return default_value;
|
|
File file(context_, filename, FILE_READ);
|
|
if (!tmp->Load(file))
|
|
return default_value;
|
|
config = tmp;
|
|
}
|
|
const JSONValue *root = &config->GetRoot();
|
|
if (!root || !root->IsObject())
|
|
return default_value;
|
|
if (section)
|
|
{
|
|
if (!root->Contains(section))
|
|
return default_value;
|
|
root = &root->Get(section);
|
|
if (!root->IsObject())
|
|
return default_value;
|
|
}
|
|
if (!root->Contains(key))
|
|
return default_value;
|
|
const JSONValue &field = root->Get(key);
|
|
T v;
|
|
if (!GetJSONValue(field, v))
|
|
return default_value;
|
|
return v;
|
|
}
|
|
|
|
template<typename T>
|
|
T CryptoCityUrho3D::GetConfigValue(const char *section, const char *key, const T &default_value)
|
|
{
|
|
String s;
|
|
while (config_watcher.GetNextChange(s))
|
|
{
|
|
if (s == "config.json")
|
|
config.Reset();
|
|
}
|
|
return GetConfigValue(config_filename, config, section, key, default_value);
|
|
}
|
|
|
|
template<typename T, typename R> static R to_json_value(const T &t) { return t; }
|
|
template<> String to_json_value<TBID, String>(const TBID &id) { return String((uint64_t)id); }
|
|
|
|
template<typename T>
|
|
struct to_json_value_type
|
|
{
|
|
typedef T type;
|
|
};
|
|
|
|
template<>
|
|
struct to_json_value_type<TBID>
|
|
{
|
|
typedef String type;
|
|
};
|
|
|
|
template<typename T>
|
|
T CryptoCityUrho3D::SetConfigValue(const char *section, const char *key, const T &value)
|
|
{
|
|
if (!config)
|
|
config = DynamicCast<JSONFile>(context_->CreateObject(JSONFile::GetTypeStatic()));
|
|
|
|
JSONValue *root = &config->GetRoot();
|
|
if (!root || !root->IsObject())
|
|
*root = JSONObject();
|
|
if (section)
|
|
{
|
|
if (!root->Contains(section))
|
|
(*root)[section] = JSONObject();
|
|
root = &(*root)[section];
|
|
if (!root->IsObject())
|
|
{
|
|
(*root)[section] = JSONObject();
|
|
root = &(*root)[section];
|
|
}
|
|
}
|
|
(*root)[key] = to_json_value<T, typename to_json_value_type<T>::type>(value);
|
|
|
|
File f(context_, config_filename, FILE_WRITE);
|
|
if (!config->Save(f))
|
|
fprintf(stderr, "Error saving config file");
|
|
return value;
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleEnableShadows(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (eventData.Contains(EnableShadows::P_METHOD))
|
|
EnableShadows(SetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_SHADOWS, TBID(eventData[EnableShadows::P_METHOD].GetUInt64())));
|
|
else
|
|
eventData[EnableShadows::P_METHOD] = (unsigned long long)GetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_SHADOWS, DEFAULT_SHADOWS);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleSetShadowMapSize(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (eventData.Contains(SetShadowMapSize::P_MAP_SIZE))
|
|
SetShadowMapSize(SetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_SHADOW_MAP_SIZE, TBID(eventData[SetShadowMapSize::P_MAP_SIZE].GetUInt64())));
|
|
else
|
|
eventData[SetShadowMapSize::P_MAP_SIZE] = (unsigned long long)GetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_SHADOW_MAP_SIZE, DEFAULT_SHADOW_MAP_SIZE);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleEnableClouds(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (eventData.Contains(EnableClouds::P_ENABLE))
|
|
EnableClouds(SetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_CLOUDS, eventData[EnableClouds::P_ENABLE].GetBool()));
|
|
else
|
|
eventData[EnableClouds::P_ENABLE] = GetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_CLOUDS, DEFAULT_CLOUDS);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleSetMaxActiveFPS(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (eventData.Contains(SetMaxActiveFPS::P_FPS))
|
|
SetMaxActiveFPS(SetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_MAX_ACTIVE_FPS, eventData[SetMaxActiveFPS::P_FPS].GetUInt()));
|
|
else
|
|
{
|
|
unsigned fps = GetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_MAX_ACTIVE_FPS, DEFAULT_MAX_ACTIVE_FPS);
|
|
if (fps == 0)
|
|
fps = engine_->GetMaxFps();
|
|
eventData[SetMaxActiveFPS::P_FPS] = fps;
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleSetMaxInactiveFPS(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (eventData.Contains(SetMaxInactiveFPS::P_FPS))
|
|
SetMaxInactiveFPS(SetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_MAX_INACTIVE_FPS, eventData[SetMaxInactiveFPS::P_FPS].GetUInt()));
|
|
else
|
|
{
|
|
unsigned fps = GetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_MAX_INACTIVE_FPS, DEFAULT_MAX_INACTIVE_FPS);
|
|
if (fps == 0)
|
|
fps = engine_->GetMaxInactiveFps();
|
|
eventData[SetMaxInactiveFPS::P_FPS] = fps;
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleSetViewDistance(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (eventData.Contains(SetViewDistance::P_DISTANCE))
|
|
SetViewDistance(SetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_VIEW_DISTANCE, eventData[SetViewDistance::P_DISTANCE].GetUInt()));
|
|
else
|
|
eventData[SetViewDistance::P_DISTANCE] = GetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_VIEW_DISTANCE, DEFAULT_VIEW_DISTANCE);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleSetFontSize(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (eventData.Contains(SetFontSize::P_SIZE))
|
|
{
|
|
SetFontSize(SetConfigValue(CONFIG_UI_SECTION, CONFIG_FONT_SIZE, eventData[SetFontSize::P_SIZE].GetUInt()));
|
|
}
|
|
else
|
|
{
|
|
unsigned size = GetConfigValue(CONFIG_UI_SECTION, CONFIG_FONT_SIZE, DEFAULT_FONT_SIZE);
|
|
eventData[SetFontSize::P_SIZE] = size;
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleEnableHorizon(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (eventData.Contains(EnableHorizon::P_ENABLE))
|
|
EnableHorizon(SetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_HORIZON, eventData[EnableHorizon::P_ENABLE].GetBool()));
|
|
else
|
|
eventData[EnableHorizon::P_ENABLE] = GetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_HORIZON, DEFAULT_HORIZON);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleEnableDynamicSky(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (eventData.Contains(EnableDynamicSky::P_ENABLE))
|
|
EnableDynamicSky(SetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_DYNAMIC_SKY, eventData[EnableDynamicSky::P_ENABLE].GetBool()));
|
|
else
|
|
eventData[EnableDynamicSky::P_ENABLE] = GetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_DYNAMIC_SKY, DEFAULT_DYNAMIC_SKY);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleTimeOfDay(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (eventData.Contains(TimeOfDay::P_TIME_OF_DAY))
|
|
SetTimeOfDay(SetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_TIME_OF_DAY, eventData[TimeOfDay::P_TIME_OF_DAY].GetFloat()));
|
|
else
|
|
eventData[TimeOfDay::P_TIME_OF_DAY] = GetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_TIME_OF_DAY, DEFAULT_TIME_OF_DAY);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleShowFlags(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (eventData.Contains(ShowFlags::P_SHOW))
|
|
ShowFlags(SetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_SHOW_FLAGS, eventData[ShowFlags::P_SHOW].GetBool()));
|
|
else
|
|
eventData[ShowFlags::P_SHOW] = GetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_SHOW_FLAGS, DEFAULT_SHOW_FLAGS);
|
|
}
|
|
|
|
static VariantMap make_music_track_variant(const JSONValue &e, const String &track_prefix)
|
|
{
|
|
VariantMap v;
|
|
v["title"] = e.Get("title").GetString();
|
|
v["filename"] = track_prefix + e.Get("filename").GetString();
|
|
v["chiptune"] = e.Get("chiptune").GetBool();
|
|
return v;
|
|
}
|
|
|
|
VariantVector CryptoCityUrho3D::GetMusicTracks()
|
|
{
|
|
VariantVector Tracks;
|
|
auto* cache = context_->GetSubsystem<ResourceCache>();
|
|
|
|
static const char *track_prefixes[] = { "Audio/Music/", "Audio/ExtraMusic/" };
|
|
|
|
for (const auto &track_prefix: track_prefixes)
|
|
{
|
|
const String track_list = String(track_prefix) + "tracks.json";
|
|
SharedPtr<JSONFile> tracks(cache->GetResource<JSONFile>(track_list, false));
|
|
if (!tracks)
|
|
continue;
|
|
const JSONValue &root = tracks->GetRoot();
|
|
if (!root.IsArray())
|
|
{
|
|
printf("%s: root is not an array\n", track_list.CString());
|
|
continue;
|
|
}
|
|
const JSONArray &array = root.GetArray();
|
|
for (unsigned i = 0; i < array.Size(); ++i)
|
|
{
|
|
if (!array[i].Get("filename").IsString() || !array[i].Get("title").IsString() || !array[i].Get("chiptune").IsBool())
|
|
{
|
|
printf("%s: type error at index %u\n", track_list.CString(), i);
|
|
continue;
|
|
}
|
|
Tracks.Push(make_music_track_variant(array[i], track_prefix));
|
|
}
|
|
}
|
|
return Tracks;
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleGetMusicTracks(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
eventData[GetMusicTracks::P_TRACKS] = GetMusicTracks();
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleGraphicsMode(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (eventData.Contains(GraphicsMode::P_MONITOR))
|
|
{
|
|
char s[256];
|
|
const int monitor = eventData[GraphicsMode::P_MONITOR].GetInt();
|
|
const int width = eventData[GraphicsMode::P_WIDTH].GetInt();
|
|
const int height = eventData[GraphicsMode::P_HEIGHT].GetInt();
|
|
const int refresh_rate = eventData[GraphicsMode::P_REFRESH_RATE].GetInt();
|
|
eventData[GraphicsMode::P_SUCCESS] = false;
|
|
if (SetGraphicsMode(monitor, width, height, refresh_rate))
|
|
{
|
|
snprintf(s, sizeof(s), "%d:%dx%d@%d", monitor, width, height, refresh_rate);
|
|
SetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_GRAPHICS_MODE, String(s));
|
|
eventData[GraphicsMode::P_SUCCESS] = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
String mode = GetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_GRAPHICS_MODE, DEFAULT_GRAPHICS_MODE);
|
|
int monitor = -1, width = -1, height = -1, refresh_rate = -1;
|
|
sscanf(mode.CString(), "%d:%dx%d@%d", &monitor, &width, &height, &refresh_rate);
|
|
eventData[GraphicsMode::P_MONITOR] = monitor;
|
|
eventData[GraphicsMode::P_WIDTH] = width;
|
|
eventData[GraphicsMode::P_HEIGHT] = height;
|
|
eventData[GraphicsMode::P_REFRESH_RATE] = refresh_rate;
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleMusicTrack(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (eventData.Contains(MusicTrack::P_TRACK))
|
|
SetMusicTrack(SetConfigValue(CONFIG_AUDIO_SECTION, CONFIG_MUSIC_TRACK, eventData[MusicTrack::P_TRACK].GetString()));
|
|
else
|
|
eventData[MusicTrack::P_TRACK] = GetConfigValue(CONFIG_AUDIO_SECTION, CONFIG_MUSIC_TRACK, DEFAULT_MUSIC_TRACK);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleMusicVolume(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (eventData.Contains(MusicVolume::P_VOLUME))
|
|
SetMusicVolume(SetConfigValue(CONFIG_AUDIO_SECTION, CONFIG_MUSIC_VOLUME, eventData[MusicVolume::P_VOLUME].GetFloat()));
|
|
else
|
|
eventData[MusicVolume::P_VOLUME] = GetConfigValue(CONFIG_AUDIO_SECTION, CONFIG_MUSIC_VOLUME, DEFAULT_MUSIC_VOLUME);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleSFXVolume(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (eventData.Contains(SFXVolume::P_VOLUME))
|
|
SetSFXVolume(SetConfigValue(CONFIG_AUDIO_SECTION, CONFIG_SFX_VOLUME, eventData[SFXVolume::P_VOLUME].GetFloat()));
|
|
else
|
|
eventData[SFXVolume::P_VOLUME] = GetConfigValue(CONFIG_AUDIO_SECTION, CONFIG_SFX_VOLUME, DEFAULT_SFX_VOLUME);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandlePlaySFXOnNewBlock(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (eventData.Contains(PlaySFXOnNewBlock::P_PLAY))
|
|
SetPlaySFXOnNewBlock(SetConfigValue(CONFIG_AUDIO_SECTION, CONFIG_PLAY_SFX_ON_NEW_BLOCK, eventData[PlaySFXOnNewBlock::P_PLAY].GetBool()));
|
|
else
|
|
eventData[PlaySFXOnNewBlock::P_PLAY] = GetConfigValue(CONFIG_AUDIO_SECTION, CONFIG_PLAY_SFX_ON_NEW_BLOCK, DEFAULT_PLAY_SFX_ON_NEW_BLOCK);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleControlDaemon(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (eventData.Contains(ControlDaemon::P_CONTROL))
|
|
SetControlDaemon(SetConfigValue(CONFIG_NODE_SECTION, CONFIG_CONTROL_DAEMON, eventData[ControlDaemon::P_CONTROL].GetBool()));
|
|
else
|
|
eventData[ControlDaemon::P_CONTROL] = GetConfigValue(CONFIG_NODE_SECTION, CONFIG_CONTROL_DAEMON, DEFAULT_CONTROL_DAEMON);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleRestartDaemon(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
const std::string data_dir = eventData[RestartDaemon::P_DATA_DIR].GetString().CString();
|
|
SetConfigValue(CONFIG_NODE_SECTION, CONFIG_DAEMON_DATA_DIR, data_dir.c_str());
|
|
const std::string log = eventData[RestartDaemon::P_LOG].GetString().CString();
|
|
SetConfigValue(CONFIG_NODE_SECTION, CONFIG_DAEMON_LOG, log.c_str());
|
|
daemon_controller->Configure(data_dir, log);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleEnableTutorial(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (eventData.Contains(EnableTutorial::P_ENABLE))
|
|
SetEnableTutorial(SetConfigValue(CONFIG_TUTORIAL_SECTION, CONFIG_ENABLE_TUTORIAL, eventData[EnableTutorial::P_ENABLE].GetBool()));
|
|
else
|
|
eventData[EnableTutorial::P_ENABLE] = GetConfigValue(CONFIG_ENABLE_TUTORIAL, CONFIG_ENABLE_TUTORIAL, DEFAULT_ENABLE_TUTORIAL);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleResetTutorial(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!config)
|
|
config = DynamicCast<JSONFile>(context_->CreateObject(JSONFile::GetTypeStatic()));
|
|
|
|
JSONValue *root = &config->GetRoot();
|
|
if (!root || !root->IsObject())
|
|
*root = JSONObject();
|
|
(*root)[CONFIG_TUTORIAL_STATE_SECTION] = JSONObject();
|
|
|
|
File f(context_, config_filename, FILE_WRITE);
|
|
if (!config->Save(f))
|
|
fprintf(stderr, "Error saving config file");
|
|
}
|
|
|
|
static Variant make_tutorial_variant(const std::pair<TBStr, TBStr> &e)
|
|
{
|
|
VariantMap v;
|
|
v["trigger"] = e.first.CStr();
|
|
v["title"] = e.second.CStr();
|
|
return v;
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleGetTutorials(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
std::map<TBStr, TBStr> tutorials = get_tutorial_list();
|
|
VariantVector Tutorials;
|
|
for (const auto &e: tutorials)
|
|
Tutorials.Push(make_tutorial_variant(e));
|
|
eventData[GetTutorials::P_TUTORIALS] = Tutorials;
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleTutorialTrigger(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
const String tag = eventData[TutorialTrigger::P_TAG].GetString();
|
|
const bool force = eventData[TutorialTrigger::P_FORCE].GetBool();
|
|
if (!force)
|
|
{
|
|
const String field = "seen:" + tag;
|
|
if (GetConfigValue(CONFIG_TUTORIAL_STATE_SECTION, field.CString(), false))
|
|
return;
|
|
SetConfigValue(CONFIG_TUTORIAL_STATE_SECTION, field.CString(), true);
|
|
}
|
|
|
|
UITBWindow *tutorial = display_tutorial(context_, &gameState, tag.CString());
|
|
if (tutorial)
|
|
{
|
|
SubscribeToEvent(tutorial, E_CRYPTOCITY_ENABLE_TUTORIAL, URHO3D_HANDLER(CryptoCityUrho3D, HandleEnableTutorial));
|
|
}
|
|
else if (gameState.debug)
|
|
printf("Tutorial not found: %s\n", tag.CString());
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleMusicSkipTrack(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
audio.PlayNext();
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleIncludeUnmatchedTradesInQueuedComands(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (eventData.Contains(IncludeUnmatchedTradesInQueuedCommands::P_INCLUDE))
|
|
SetConfigValue(CONFIG_UI_SECTION, CONFIG_INCLUDE_UNMATCHED_TRADES_IN_QUEUED_COMMANDS, eventData[IncludeUnmatchedTradesInQueuedCommands::P_INCLUDE].GetBool());
|
|
else
|
|
eventData[IncludeUnmatchedTradesInQueuedCommands::P_INCLUDE] = GetConfigValue(CONFIG_UI_SECTION, CONFIG_INCLUDE_UNMATCHED_TRADES_IN_QUEUED_COMMANDS, DEFAULT_INCLUDE_UNMATCHED_TRADES_IN_QUEUED_COMMANDS);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleCompass(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (eventData.Contains(Compass::P_COMPASS))
|
|
SetCompass(SetConfigValue(CONFIG_UI_SECTION, CONFIG_COMPASS, TBID(eventData[Compass::P_COMPASS].GetUInt64())));
|
|
else
|
|
eventData[Compass::P_COMPASS] = (unsigned long long)TBID(GetConfigValue(CONFIG_UI_SECTION, CONFIG_COMPASS, DEFAULT_COMPASS));
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleCalendar(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (eventData.Contains(Calendar::P_CALENDAR))
|
|
SetCalendar(SetConfigValue(CONFIG_UI_SECTION, CONFIG_CALENDAR, TBID(eventData[Calendar::P_CALENDAR].GetUInt64())));
|
|
else
|
|
eventData[Calendar::P_CALENDAR] = (unsigned long long)TBID(GetConfigValue(CONFIG_UI_SECTION, CONFIG_CALENDAR, DEFAULT_CALENDAR));
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleThermometer(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (eventData.Contains(Thermometer::P_THERMOMETER))
|
|
SetThermometer(SetConfigValue(CONFIG_UI_SECTION, CONFIG_THERMOMETER, TBID(eventData[Thermometer::P_THERMOMETER].GetUInt64())));
|
|
else
|
|
eventData[Thermometer::P_THERMOMETER] = (unsigned long long)TBID(GetConfigValue(CONFIG_UI_SECTION, CONFIG_THERMOMETER, DEFAULT_THERMOMETER));
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleNotificationLifetime(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (eventData.Contains(NotificationLifetime::P_LIFETIME))
|
|
SetNotificationLifetime(SetConfigValue(CONFIG_UI_SECTION, CONFIG_NOTIFICATION_LIFTIME, eventData[NotificationLifetime::P_LIFETIME].GetUInt()));
|
|
else
|
|
{
|
|
unsigned lifetime = GetConfigValue(CONFIG_UI_SECTION, CONFIG_NOTIFICATION_LIFTIME, DEFAULT_NOTIFICATION_LIFTIME);
|
|
eventData[NotificationLifetime::P_LIFETIME] = lifetime;
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleResearch(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
cryptonote::cc_command_research_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.discovery = eventData[Research::P_DISCOVERY].GetUInt();
|
|
cmd.amount = eventData[Research::P_AMOUNT].GetUInt64();
|
|
SendCommand(cmd);
|
|
ui->AddToastNotification((cryptonote::print_money(cmd.amount) + " paid for research\nResults will be known after next block is mined").c_str());
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleApproveBuild(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
if (!flagUnderConstruction)
|
|
{
|
|
new MessageBox(context_, "There is no current build to approve");
|
|
return;
|
|
}
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to make changes to your buildings");
|
|
return;
|
|
}
|
|
|
|
std::shared_ptr<Flag> flag = map.get_flag(std::get<0>(*flagUnderConstruction));
|
|
if (!flag)
|
|
{
|
|
new MessageBox(context_, "Failed to find abandoned flag - it might have been destroyed");
|
|
return;
|
|
}
|
|
|
|
Flag::unpacker unpacker(flag);
|
|
|
|
const auto &original_tiles = std::get<2>(*flagUnderConstruction)->tiles;
|
|
if (original_tiles.size() != (flag->x1 - flag->x0 + 1) * (flag->y1 - flag->y0 + 1))
|
|
{
|
|
new MessageBox(context_, "Unexpected size of tile and saved data");
|
|
return;
|
|
}
|
|
|
|
ui->AddNotification("Creating transaction(s)...");
|
|
|
|
// one tx for each layer
|
|
uint8_t remove_tiles[65536], add_tiles[65536];
|
|
uint64_t parent = 0;
|
|
bool palette_updated = false;
|
|
const uint32_t flag_x0 = flag->x0;
|
|
const uint32_t flag_y0 = flag->y0;
|
|
const uint32_t flag_x1 = flag->x1;
|
|
const uint32_t flag_y1 = flag->y1;
|
|
const std::shared_ptr<TileData> flag_tile_data = flag->get_tiles();
|
|
const auto &flag_tiles = flag_tile_data->tiles;
|
|
|
|
uint16_t max_height = flag->get_max_height();
|
|
for (const auto &t: original_tiles)
|
|
max_height = std::max(max_height, (uint16_t)t.size());
|
|
|
|
bool is_runestone[256] = {false};
|
|
for (size_t i = 0; i < flag->palette.size(); ++i)
|
|
{
|
|
if (flag->palette[i] == 0)
|
|
continue;
|
|
const cc::block_variant_t block_variant = cc::get_block_variant(flag->palette[i]);
|
|
if (block_variant.item == ITEM_RUNESTONE)
|
|
is_runestone[i] = true;
|
|
}
|
|
|
|
wallet->unlock();
|
|
epee::misc_utils::auto_scope_leave_caller locker = epee::misc_utils::create_scope_leave_handler([&](){ wallet->lock(); });
|
|
|
|
for (uint16_t h = 0; h < max_height; ++h)
|
|
{
|
|
uint32_t bx0r = flag_x1;
|
|
uint32_t by0r = flag_y1;
|
|
uint32_t bx1r = flag_x0;
|
|
uint32_t by1r = flag_y0;
|
|
uint32_t bx0a = flag_x1;
|
|
uint32_t by0a = flag_y1;
|
|
uint32_t bx1a = flag_x0;
|
|
uint32_t by1a = flag_y0;
|
|
bool removed = false, added = false;
|
|
for (uint32_t y = flag_y0; y <= flag_y1; ++y)
|
|
{
|
|
for (uint32_t x = flag_x0; x <= flag_x1; ++x)
|
|
{
|
|
const unsigned int orgidx = (y - flag_y0) * (flag_x1 - flag_x0 + 1) + x - flag_x0;
|
|
|
|
const uint8_t previous_block = h < original_tiles[orgidx].size() ? original_tiles[orgidx][h] : 0;
|
|
const uint8_t new_block = h < flag_tiles[orgidx].size() ? flag_tiles[orgidx][h] : 0;
|
|
if (previous_block && new_block != previous_block)
|
|
{
|
|
bx0r = std::min(bx0r, x);
|
|
by0r = std::min(by0r, y);
|
|
bx1r = std::max(bx1r, x);
|
|
by1r = std::max(by1r, y);
|
|
removed = true;
|
|
}
|
|
if (new_block && new_block != previous_block)
|
|
{
|
|
bx0a = std::min(bx0a, x);
|
|
by0a = std::min(by0a, y);
|
|
bx1a = std::max(bx1a, x);
|
|
by1a = std::max(by1a, y);
|
|
added = true;
|
|
}
|
|
}
|
|
}
|
|
if (!removed && !added)
|
|
continue;
|
|
|
|
cryptonote::cc_command_build_t cmd_remove, cmd_add;
|
|
cmd_remove.cc_account = cmd_add.cc_account = wallet->get_cc_account();
|
|
cmd_remove.flag = cmd_add.flag = std::get<0>(*flagUnderConstruction);
|
|
cmd_remove.dx = bx0r - flag_x0;
|
|
cmd_remove.dy = by0r - flag_y0;
|
|
cmd_remove.wm1 = bx1r - bx0r;
|
|
cmd_remove.hm1 = by1r - by0r;
|
|
cmd_add.dx = bx0a - flag_x0;
|
|
cmd_add.dy = by0a - flag_y0;
|
|
cmd_add.wm1 = bx1a - bx0a;
|
|
cmd_add.hm1 = by1a - by0a;
|
|
cmd_remove.remove = true;
|
|
cmd_add.remove = false;
|
|
cmd_remove.height = cmd_add.height = h;
|
|
|
|
for (int pass = 0; pass < 2; ++pass)
|
|
{
|
|
if (pass == 0 && !removed)
|
|
continue;
|
|
if (pass == 1 && !added)
|
|
continue;
|
|
bool changed = false;
|
|
unsigned int idx = 0;
|
|
const uint32_t bx0 = pass == 0 ? bx0r : bx0a;
|
|
const uint32_t by0 = pass == 0 ? by0r : by0a;
|
|
const uint32_t bx1 = pass == 0 ? bx1r : bx1a;
|
|
const uint32_t by1 = pass == 0 ? by1r : by1a;
|
|
uint8_t *tiles = pass == 0 ? remove_tiles : add_tiles;
|
|
for (uint32_t y = by0; y <= by1; ++y)
|
|
{
|
|
for (uint32_t x = bx0; x <= bx1; ++x)
|
|
{
|
|
unsigned int orgidx = (y - flag_y0) * (flag_x1 - flag_x0 + 1) + x - flag_x0;
|
|
const uint8_t previous_block = h < original_tiles[orgidx].size() ? original_tiles[orgidx][h] : 0;
|
|
const uint8_t new_block = h < flag_tiles[orgidx].size() ? flag_tiles[orgidx][h] : 0;
|
|
if (pass == 0)
|
|
{
|
|
if (previous_block && new_block != previous_block)
|
|
{
|
|
tiles[idx] = previous_block;
|
|
if (is_runestone[previous_block])
|
|
{
|
|
cc::runestone_t runestone;
|
|
if (wallet->get_cc_runestone(std::get<0>(*flagUnderConstruction), x - flag_x0, y - flag_y0, h, runestone))
|
|
{
|
|
cryptonote::cc_command_carve_runestone_t cmd_carve;
|
|
cmd_carve.cc_account = cmd_add.cc_account = wallet->get_cc_account();
|
|
cmd_carve.flag = std::get<0>(*flagUnderConstruction);
|
|
cmd_carve.x = x - flag_x0;
|
|
cmd_carve.y = y - flag_y0;
|
|
cmd_carve.h = h;
|
|
cmd_carve.script_delta = -runestone.script;
|
|
cmd_carve.previous_message = std::move(runestone.message);
|
|
cmd_carve.message = "";
|
|
cmd_carve.previous_overrides.reserve(runestone.overrides.size());
|
|
for (auto &e: runestone.overrides)
|
|
cmd_carve.previous_overrides.push_back({std::get<0>(e), std::get<1>(e), std::move(std::get<2>(e))});
|
|
cmd_carve.overrides = {};
|
|
|
|
cmd_carve.cc_parent = parent;
|
|
if (!SendCommand(cmd_carve, &parent))
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
tiles[idx] = 0;
|
|
}
|
|
else
|
|
{
|
|
if (new_block && new_block != previous_block)
|
|
tiles[idx] = new_block;
|
|
else
|
|
tiles[idx] = 0;
|
|
}
|
|
if (tiles[idx++])
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
if (!changed)
|
|
{
|
|
MERROR("No change on second pass");
|
|
continue;
|
|
}
|
|
|
|
uint8_t encoded[65536];
|
|
uint32_t encoded_len = 0;
|
|
if (!cc::encode_blocks(tiles, idx, encoded, &encoded_len))
|
|
{
|
|
new MessageBox(context_, "Failed to create build command");
|
|
return;
|
|
}
|
|
cryptonote::cc_command_build_t &cmd = pass == 0 ? cmd_remove : cmd_add;
|
|
cmd.block_data = std::vector<uint8_t>(encoded, encoded + encoded_len);
|
|
cmd.cc_parent = parent;
|
|
|
|
if (!cmd.remove && !palette_updated)
|
|
{
|
|
const std::vector<uint16_t> &original_palette = std::get<1>(*flagUnderConstruction);
|
|
const std::vector<uint16_t> &palette = flag->palette;
|
|
for (uint32_t i = 1; i < 256; ++i)
|
|
{
|
|
const uint16_t old_variant = i < original_palette.size() ? original_palette[i] : (uint16_t)cc::BLOCK_VARIANT_NONE;
|
|
const uint16_t new_variant = i < palette.size() ? palette[i] : (uint16_t)cc::BLOCK_VARIANT_NONE;
|
|
if (old_variant != new_variant)
|
|
cmd.palette.push_back({(uint8_t)i, (int16_t)(new_variant - old_variant)});
|
|
}
|
|
palette_updated = true;
|
|
}
|
|
|
|
if (!SendCommand(cmd, &parent))
|
|
return;
|
|
}
|
|
}
|
|
|
|
RestoreFlagUnderConstruction();
|
|
ui->AddNotification("Transaction(s) sent, the building will appear as the blockchain progresses");
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleAbandonBuild(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
if (!flagUnderConstruction)
|
|
{
|
|
new MessageBox(context_, "There is no current build to abandon");
|
|
return;
|
|
}
|
|
|
|
RestoreFlagUnderConstruction();
|
|
}
|
|
|
|
void CryptoCityUrho3D::RestoreFlagUnderConstruction()
|
|
{
|
|
const std::shared_ptr<Flag> flag = map.get_flag(std::get<0>(*flagUnderConstruction));
|
|
if (!flag)
|
|
{
|
|
SetNoFlagUnderConstruction();
|
|
new MessageBox(context_, "Failed to find flag - it might have been destroyed");
|
|
return;
|
|
}
|
|
|
|
Flag::unpacker unpacker(flag);
|
|
|
|
const std::shared_ptr<TileData> modified_tiles = map.get_flag_tiles(std::get<0>(*flagUnderConstruction));
|
|
const auto &original_tiles = std::get<2>(*flagUnderConstruction);
|
|
if (modified_tiles->tiles.size() != original_tiles->tiles.size())
|
|
{
|
|
SetNoFlagUnderConstruction();
|
|
new MessageBox(context_, "Unexpected tiles size");
|
|
return;
|
|
}
|
|
UpdateBudget(flag, original_tiles, modified_tiles, true);
|
|
flag->palette = std::get<1>(*flagUnderConstruction);
|
|
map.set_flag_tiles(std::get<0>(*flagUnderConstruction), original_tiles);
|
|
SetNoFlagUnderConstruction();
|
|
gameState.add_dirty_flag(flag, false);
|
|
EndModelPlacement();
|
|
}
|
|
|
|
void CryptoCityUrho3D::UpdateBudget(const std::shared_ptr<Flag> &flag, const std::vector<std::vector<uint8_t>> &from_tiles, const std::vector<std::vector<uint8_t>> &to_tiles, bool undo)
|
|
{
|
|
if (from_tiles.size() != to_tiles.size())
|
|
{
|
|
new MessageBox(context_, "Unexpected tiles size");
|
|
return;
|
|
}
|
|
const uint32_t bonus = cc::get_build_labour_cost_for_height_bonus(gameState.has_discovery(DISCOVERY_IMPROVED_SCAFFOLDING), gameState.has_discovery(DISCOVERY_ADVANCED_SCAFFOLDING));
|
|
const uint32_t flag_x0 = flag->x0;
|
|
const uint32_t flag_y0 = flag->y0;
|
|
const uint32_t flag_x1 = flag->x1;
|
|
const uint32_t flag_y1 = flag->y1;
|
|
for (uint32_t y = flag_y0; y <= flag_y1; ++y)
|
|
{
|
|
for (uint32_t x = flag_x0; x <= flag_x1; ++x)
|
|
{
|
|
const unsigned int orgidx = (y - flag_y0) * (flag_x1 - flag_x0 + 1) + x - flag_x0;
|
|
|
|
const size_t max_height = std::max(from_tiles[orgidx].size(), to_tiles[orgidx].size());
|
|
for (size_t h = 0; h < max_height; ++h)
|
|
{
|
|
const uint8_t previous_block = get_block_from_palette_index(flag, h < from_tiles[orgidx].size() ? from_tiles[orgidx][h] : 0);
|
|
const uint8_t new_block = get_block_from_palette_index(flag, h < to_tiles[orgidx].size() ? to_tiles[orgidx][h] : 0);
|
|
if (previous_block && new_block != previous_block)
|
|
{
|
|
if (undo)
|
|
{
|
|
flag->budget[ITEM_LABOUR] += cc::get_removal_labour_cost_for_height(h);
|
|
flag->budget[previous_block]--;
|
|
}
|
|
else
|
|
{
|
|
flag->budget[ITEM_LABOUR] -= cc::get_removal_labour_cost_for_height(h);
|
|
flag->budget[previous_block]++;
|
|
}
|
|
}
|
|
if (new_block && new_block != previous_block)
|
|
{
|
|
if (undo)
|
|
{
|
|
flag->budget[ITEM_LABOUR] += cc::get_build_labour_cost_for_height(h, bonus);
|
|
flag->budget[new_block]++;
|
|
}
|
|
else
|
|
{
|
|
flag->budget[ITEM_LABOUR] -= cc::get_build_labour_cost_for_height(h, bonus);
|
|
flag->budget[new_block]--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::UpdateBudget(const std::shared_ptr<Flag> &flag, const std::shared_ptr<TileData> &from_tiles, const std::shared_ptr<TileData> &to_tiles, bool undo)
|
|
{
|
|
UpdateBudget(flag, from_tiles->tiles, to_tiles->tiles, undo);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleExitConfirmation(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
TBWidgetEvent *ev = (TBWidgetEvent*)eventData[TBWidgetEventNamespace::P_WIDGET_EVENT].GetVoidPtr();
|
|
if (ev->ref_id == TBIDC("TBMessageWindow.yes"))
|
|
{
|
|
engine_->Exit();
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleExitRequest(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
ui->OnExitRequest();
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleExit(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
engine_->Exit();
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleMaterialSelected(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
|
|
const std::shared_ptr<Flag> flag = hasSelection_ ? map.get_flag(selection.x0, selection.y0) : nullptr;
|
|
if (!flag)
|
|
return;
|
|
|
|
int material = eventData[MaterialSelected::P_MATERIAL].GetInt();
|
|
if (material >= ITEM_FIRST_BLOCK && material <= ITEM_LAST_BLOCK)
|
|
{
|
|
const uint16_t variant = cc::get_default_block_variant(material);
|
|
if (variant == cc::BLOCK_VARIANT_NONE)
|
|
return;
|
|
const cc::block_variant_t block_variant = cc::get_block_variant(variant);
|
|
if (flag->budget.find(block_variant.item) == flag->budget.end())
|
|
{
|
|
ui->AddNotification("No such block available in flag budget");
|
|
return;
|
|
}
|
|
if (std::find(flag->palette.begin(), flag->palette.end(), variant) == flag->palette.end())
|
|
{
|
|
uint8_t palette_index = FindFreePaletteSlot(flag);
|
|
if (palette_index == 0)
|
|
{
|
|
new MessageBox(context_, "Not enough space in palette to add this new material");
|
|
return;
|
|
}
|
|
}
|
|
|
|
currentBlockVariant = variant;
|
|
std::string msg = std::string("Building material selected: ") + cc::get_block_variant(currentBlockVariant).name;
|
|
ui->AddNotification(msg.c_str());
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleVariantSelected(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
|
|
uint16_t variant = eventData[VariantSelected::P_VARIANT].GetUInt();
|
|
if (variant == cc::BLOCK_VARIANT_NONE)
|
|
return;
|
|
|
|
const std::shared_ptr<Flag> flag = hasSelection_ ? map.get_flag(selection.x0, selection.y0) : nullptr;
|
|
if (!flag)
|
|
return;
|
|
|
|
const cc::block_variant_t block_variant = cc::get_block_variant(variant);
|
|
if (flag->budget.find(block_variant.item) == flag->budget.end())
|
|
{
|
|
ui->AddNotification("No such block available in flag budget");
|
|
return;
|
|
}
|
|
if (std::find(flag->palette.begin(), flag->palette.end(), variant) == flag->palette.end())
|
|
{
|
|
uint8_t palette_index = FindFreePaletteSlot(flag);
|
|
if (palette_index == 0)
|
|
{
|
|
new MessageBox(context_, "Not enough space in palette to add this new material");
|
|
return;
|
|
}
|
|
}
|
|
|
|
currentBlockVariant = variant;
|
|
std::string msg = std::string("Building material selected: ") + cc::get_block_variant(currentBlockVariant).name;
|
|
ui->AddNotification(msg.c_str());
|
|
}
|
|
|
|
void CryptoCityUrho3D::SelectNextMaterial(bool next)
|
|
{
|
|
std::shared_ptr<Flag> flag = hasSelection_ ? map.get_flag(selection.x0, selection.y0) : nullptr;
|
|
if (!flag || flag->owner != gameState.playerState.id)
|
|
return;
|
|
|
|
const std::map<uint32_t, uint32_t> &items = flag->budget;
|
|
uint16_t variant = currentBlockVariant;
|
|
if (next || variant == cc::BLOCK_VARIANT_NONE)
|
|
{
|
|
++variant;
|
|
if (variant == cc::NUM_BLOCK_VARIANTS)
|
|
variant = 1;
|
|
}
|
|
|
|
while (1)
|
|
{
|
|
{
|
|
const cc::block_variant_t block_variant = cc::get_block_variant(variant);
|
|
const uint8_t item = block_variant.item;
|
|
const auto it = items.find(item);
|
|
if (it != items.end())
|
|
{
|
|
if (variant != currentBlockVariant)
|
|
{
|
|
currentBlockVariant = variant;
|
|
std::string msg = std::string("Building material selected: ") + block_variant.name + " (" + gameState.get_item_name(item) + ")";
|
|
ui->AddNotification(msg.c_str());
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
variant += 1;
|
|
if (variant == cc::NUM_BLOCK_VARIANTS)
|
|
variant = 1;
|
|
if (variant == (currentBlockVariant ? currentBlockVariant : 1))
|
|
{
|
|
ui->AddNotification("No material in the palette with backing blocks");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::PickMaterial()
|
|
{
|
|
Drawable *d = NULL;
|
|
uint32_t tile_x, tile_y, tile_h;
|
|
uint32_t top_x, top_y, top_h;
|
|
Vector3 point;
|
|
if (Pick(d, tile_x, tile_y, tile_h, top_x, top_y, top_h, &point))
|
|
{
|
|
const std::shared_ptr<Flag> flag = map.get_flag(tile_x, tile_y);
|
|
if (flag)
|
|
{
|
|
Flag::unpacker unpacker(flag);
|
|
const uint16_t tile_height = flag->get_tile_height(tile_x, tile_y);
|
|
if (tile_height > 0 && tile_h >= flag->base_height && tile_h - flag->base_height < tile_height)
|
|
{
|
|
const uint8_t tile = flag->get_tile_type(tile_x, tile_y, tile_h - flag->base_height);
|
|
if (tile && tile < flag->palette.size())
|
|
{
|
|
const uint16_t variant = flag->palette[tile];
|
|
if (variant != cc::BLOCK_VARIANT_NONE)
|
|
{
|
|
currentBlockVariant = variant;
|
|
std::string msg = std::string("Building material selected: ") + cc::get_block_variant(currentBlockVariant).name;
|
|
ui->AddNotification(msg.c_str());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::OnRunestoneTriggered(const std::shared_ptr<Flag> &flag, uint8_t x, uint8_t y, uint16_t h)
|
|
{
|
|
const bool is_owner = flag->owner == gameState.playerState.id;
|
|
|
|
if (is_owner)
|
|
{
|
|
ui->OnCarveRunestone(flag, x, y, h);
|
|
}
|
|
else
|
|
{
|
|
cc::runestone_t runestone;
|
|
bool blank = gameState.ignore_flag(flag->id) || gameState.ignore_player(flag->owner);
|
|
if (!blank)
|
|
{
|
|
if (!wallet->get_cc_runestone(flag->id, x, y, h, runestone))
|
|
blank = true;
|
|
else if (runestone.message.empty() && runestone.script == 0)
|
|
blank = true;
|
|
}
|
|
const std::string title = gameState.get_flag_name(flag->id);
|
|
if (blank)
|
|
new MessageBox(context_, "This runestone is blank", title.c_str());
|
|
else
|
|
ui->OnTriggerRunestone(flag, runestone);
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::TriggerIfRunestone()
|
|
{
|
|
Drawable *d = NULL;
|
|
uint32_t tile_x, tile_y, tile_h;
|
|
uint32_t top_x, top_y, top_h;
|
|
Vector3 point;
|
|
if (Pick(d, tile_x, tile_y, tile_h, top_x, top_y, top_h, &point))
|
|
{
|
|
const std::shared_ptr<Flag> flag = map.get_flag(tile_x, tile_y);
|
|
if (flag && tile_h >= flag->base_height)
|
|
{
|
|
Flag::unpacker unpacker(flag);
|
|
if (flag->get_tile_height(tile_x, tile_y))
|
|
{
|
|
const uint8_t tile = flag->get_tile_type(tile_x, tile_y, tile_h - flag->base_height);
|
|
if (tile && tile < flag->palette.size())
|
|
{
|
|
const uint16_t variant = flag->palette[tile];
|
|
if (variant != cc::BLOCK_VARIANT_NONE)
|
|
{
|
|
const cc::block_variant_t block_variant = cc::get_block_variant(variant);
|
|
if (block_variant.item == ITEM_RUNESTONE)
|
|
{
|
|
OnRunestoneTriggered(flag, tile_x - flag->x0, tile_y - flag->y0, tile_h - flag->base_height);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleWalletFileSelected(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
const bool ok = eventData[FileSelected::P_OK].GetBool();
|
|
const String filename = eventData[FileSelected::P_FILENAME].GetString();
|
|
|
|
if (ok)
|
|
{
|
|
wallet->load(filename.CString());
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleLoadingWallet(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleNewWallet(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
std::shared_ptr<tools::wallet2> w = wallet->wallet();
|
|
if (w)
|
|
{
|
|
MINFO("New wallet, rebuilding player state");
|
|
//ui->AddNotification("Wallet loaded, scanning...");
|
|
}
|
|
if (flagUnderConstruction)
|
|
RestoreFlagUnderConstruction();
|
|
cryptonote::cc_snapshot *snapshot = (cryptonote::cc_snapshot*)eventData[NewSnapshot::P_SNAPSHOT].GetVoidPtr();
|
|
const bool new_city = snapshot && gameState.cityState.ox == 0;
|
|
gameState.reset(snapshot);
|
|
gameState.update(wallet, snapshot ? snapshot->height : 0, snapshot ? snapshot->top_hash : "");
|
|
RebuildMap();
|
|
|
|
ui->ClearQueuedCommands();
|
|
gameState.on_new_wallet(wallet);
|
|
|
|
std::string s;
|
|
bool is_real_wallet = wallet && !wallet->is_spectator() && gameState.playerState.has_wallet && gameState.playerState.id != 0;
|
|
console->SetInputEnabled(is_real_wallet);
|
|
console->ClearHistory();
|
|
if (is_real_wallet && wallet->get_attribute("CC/ChatColor", s))
|
|
{
|
|
const uint32_t color = atoi(s.c_str()); // defaults to 0, great
|
|
console->SetColor(color);
|
|
}
|
|
else
|
|
{
|
|
console->SetColor(0);
|
|
}
|
|
mentioned_in_chat = console->HasUnreadPrefix(gameState.get_player_name(wallet->get_cc_account()).c_str());
|
|
needSkyUpdate_ = true;
|
|
|
|
if (pending_invitation)
|
|
{
|
|
VariantMap pendingEventData = *pending_invitation;
|
|
pending_invitation = boost::none;
|
|
HandleAcceptInvitation(E_CRYPTOCITY_ACCEPT_INVITATION, pendingEventData);
|
|
}
|
|
|
|
CheckModelBackup();
|
|
UpdateConsoleColorMenu();
|
|
|
|
if (new_city)
|
|
newCity_ = true;
|
|
|
|
if (showWalletInfo_)
|
|
{
|
|
showWalletInfo_ = false;
|
|
new UIWalletInfoDialog(context_, wallet, "Welcome to your new Townforge wallet\n\n<color #f04040>KEEP THIS WALLET SAFE. The information below is the only way to recover your gold if you lose your wallet file.</color>");
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleNewSnapshot(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
cryptonote::cc_snapshot *snapshot = (cryptonote::cc_snapshot*)eventData[NewSnapshot::P_SNAPSHOT].GetVoidPtr();
|
|
if (!snapshot)
|
|
{
|
|
new MessageBox(context_, "Error retrieving state, we will be out of sync with the blockchain");
|
|
return;
|
|
}
|
|
std::shared_ptr<TileData> tiles;
|
|
std::vector<uint16_t> palette;
|
|
if (flagUnderConstruction)
|
|
{
|
|
const uint32_t flag_id = std::get<0>(*flagUnderConstruction);
|
|
const std::shared_ptr<Flag> flag = map.get_flag(flag_id);
|
|
Flag::unpacker unpacker(flag);
|
|
tiles = flag->get_tiles();
|
|
palette = flag->get_palette();
|
|
}
|
|
const bool new_city = snapshot && gameState.cityState.ox == 0;
|
|
gameState.reset(snapshot);
|
|
if (wallet)
|
|
{
|
|
auto w = wallet->wallet();
|
|
if (w)
|
|
gameState.update(wallet, snapshot ? snapshot->height : 0, snapshot ? snapshot->top_hash : "");
|
|
}
|
|
RebuildMap();
|
|
if (flagUnderConstruction)
|
|
{
|
|
const uint32_t flag_id = std::get<0>(*flagUnderConstruction);
|
|
const std::shared_ptr<Flag> flag = map.get_flag(flag_id);
|
|
if (flag)
|
|
{
|
|
Flag::unpacker unpacker(flag);
|
|
std::shared_ptr<TileData> new_tiles = flag->get_tiles();
|
|
if (*new_tiles == *std::get<2>(*flagUnderConstruction)) // if the new state is the same as the original state
|
|
{
|
|
flag->set_tiles(tiles); // reset to under construction state
|
|
flag->set_palette(palette);
|
|
gameState.add_dirty_flag(flag, false);
|
|
}
|
|
else
|
|
{
|
|
new MessageBox(context_, "The building you were currently building was changed on the blockchain. Building automatically abandoned.");
|
|
RestoreFlagUnderConstruction();
|
|
}
|
|
}
|
|
}
|
|
needSkyUpdate_ = true;
|
|
|
|
walletRefresher_.RequestSave();
|
|
|
|
if (new_city)
|
|
newCity_ = true;
|
|
}
|
|
|
|
void CryptoCityUrho3D::Welcome()
|
|
{
|
|
ui->AddNotification("Welcome to " + String(gameState.get_city_name(gameState.cityState.id).c_str()), 1.6f);
|
|
if (gameState.cityState.special_event != cc::SPECIAL_EVENT_NONE)
|
|
ui->AddNotification("Currently: " + String(cc::get_special_event_name(gameState.cityState.special_event)), 1.2f);
|
|
CheckModelBackup();
|
|
}
|
|
|
|
uint8_t CryptoCityUrho3D::FindFreePaletteSlot(const std::shared_ptr<Flag> &flag) const
|
|
{
|
|
const std::vector<uint16_t> &palette = flag->palette;
|
|
if (palette.size() > 256)
|
|
{
|
|
fprintf(stderr, "Palette is larger than 256\n");
|
|
return 0;
|
|
}
|
|
for (uint32_t i = 1; i <= 255; ++i)
|
|
{
|
|
if (i >= palette.size() || palette[i] == cc::BLOCK_VARIANT_NONE)
|
|
return i;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleNewBlock(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
std::shared_ptr<tools::wallet2> w = wallet->wallet();
|
|
cryptonote::block *b = (cryptonote::block*)eventData[NewBlock::P_BLOCK].GetVoidPtr();
|
|
if (!b)
|
|
{
|
|
new MessageBox(context_, "No block in new block event - game will be out of sync with the blockchain - requesting new snapshot");
|
|
if (wallet)
|
|
wallet->requestSnapshot(gameState.cityState.id);
|
|
return;
|
|
}
|
|
if (b->miner_tx.cc_cmd.type() == typeid(cryptonote::cc_command_game_update_t))
|
|
wallet->requestSnapshot(gameState.cityState.id);
|
|
mark_new_block = true;
|
|
const uint64_t block_height = cryptonote::get_block_height(*b);
|
|
gameState.update(wallet, block_height, epee::string_tools::pod_to_hex(cryptonote::get_block_hash(*b)));
|
|
std::vector<cryptonote::transaction> *txs = (std::vector<cryptonote::transaction>*)eventData[NewBlock::P_TXS].GetVoidPtr();
|
|
std::set<uint64_t> processed;
|
|
for (auto &tx: *txs)
|
|
{
|
|
if (tx.version == 2 && tx.minor_version > 0)
|
|
{
|
|
notification_sent_by_command_processing = false;
|
|
if (!gameState.process_command(tx.cc_cmd, wallet))
|
|
{
|
|
MERROR("Error processing command at height " << cryptonote::get_block_height(*b) << ": " << cryptonote::obj_to_json_str(tx.cc_cmd));
|
|
new MessageBox(context_, "Error processing command - game will be out of sync with the blockchain - requesting new snapshot");
|
|
if (wallet)
|
|
wallet->requestSnapshot(gameState.cityState.id);
|
|
return;
|
|
}
|
|
if (notification_sent_by_command_processing)
|
|
{
|
|
const cryptonote::cc_command_basenonce_t *basenonce = cryptonote::get_cc_command_base(tx.cc_cmd);
|
|
if (basenonce)
|
|
processed.insert(basenonce->cc_nonce);
|
|
}
|
|
}
|
|
}
|
|
|
|
// pick important events from game update
|
|
if (cryptonote::is_game_update_block(block_height))
|
|
NotifyGameUpdate(b);
|
|
|
|
if (gameState.playerState.id)
|
|
{
|
|
cc::game_events_t events;
|
|
if (wallet->get_game_events(block_height, block_height, gameState.playerState.id, 0, 0, 0, events))
|
|
{
|
|
NotifyEvents(events, processed);
|
|
if (events.size() > processed.size())
|
|
{
|
|
std::string s;
|
|
unsigned added = 0;
|
|
uint64_t land_tax = 0;
|
|
unsigned deactivated = 0;
|
|
int64_t balance = 0;
|
|
bool is_game_update = false;
|
|
const char *ptr;
|
|
for (const auto &event: events)
|
|
{
|
|
balance += event.balance;
|
|
|
|
if (processed.find(event.nonce) != processed.end())
|
|
continue;
|
|
|
|
// the game update is huge, trim and collapse
|
|
if (event.cmd == get_cc_tag<uint8_t>(cryptonote::cc_command_game_update_t()))
|
|
{
|
|
is_game_update = true;
|
|
|
|
static const char * const patterns[] = {
|
|
"prominence bonus", " to tick", "Consumed", "nutrients recovered", "On fire",
|
|
"Repair level changed", "payout for", "mortgage payment", "Won the auction", "via auction",
|
|
};
|
|
bool skip = false;
|
|
for (size_t i = 0; i < sizeof(patterns) / sizeof(patterns[0]); ++i)
|
|
{
|
|
if (strstr(event.event.c_str(), patterns[i]))
|
|
{
|
|
skip = true;
|
|
break;
|
|
}
|
|
}
|
|
if (skip)
|
|
continue;
|
|
|
|
// collapse some common events
|
|
if (strstr(event.event.c_str(), "Paid land tax ") || strstr(event.event.c_str(), "Paid long term empty extra tax "))
|
|
{
|
|
land_tax += -event.balance;
|
|
continue;
|
|
}
|
|
if ((ptr = strstr(event.event.c_str(), "eactivated ")) && ptr > event.event.c_str() && (ptr[-1] == 'd' || ptr[-1] == 'D'))
|
|
{
|
|
++deactivated;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (added > 0)
|
|
s += "\n";
|
|
if (event.flags.size() == 1)
|
|
s += game_util::get_building_name(&gameState, event.flags[0]) + ": ";
|
|
s += event.event;
|
|
++added;
|
|
}
|
|
if (is_game_update)
|
|
{
|
|
if (land_tax > 0)
|
|
s += "\nTotal land tax: " + game_util::print_money(land_tax);
|
|
if (deactivated > 0)
|
|
s += "\n" + std::to_string(deactivated) + " inactive buildings";
|
|
s += "\nBalance change: " + game_util::print_money(balance, true);
|
|
}
|
|
if (added > 0)
|
|
{
|
|
s = (added == 1 ? "Game event: " : "Game events:\n") + s;
|
|
ui->AddToastNotification(s.c_str());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cryptonote::is_game_update_block(block_height))
|
|
{
|
|
VariantMap eventData;
|
|
eventData[TutorialTrigger::P_TAG] = "game-update";
|
|
HandleTutorialTrigger(E_CRYPTOCITY_TUTORIAL_TRIGGER, eventData);
|
|
}
|
|
|
|
OnNewBlock();
|
|
needSkyUpdate_ = true;
|
|
}
|
|
|
|
void CryptoCityUrho3D::NotifyGameUpdate(const cryptonote::block *b)
|
|
{
|
|
if (b->miner_tx.cc_cmd.type() != typeid(cryptonote::cc_command_game_update_t))
|
|
return;
|
|
const cryptonote::cc_command_game_update_t &cg = boost::get<cryptonote::cc_command_game_update_t>(b->miner_tx.cc_cmd);
|
|
|
|
auto badges = gameState.playerState.badges;
|
|
const uint32_t new_level = cc::get_badge_score(badges).second;
|
|
for (const auto &e: cg.badges)
|
|
{
|
|
if (e.account == gameState.playerState.id)
|
|
{
|
|
if (badges[e.badge].first == e.delta_level)
|
|
{
|
|
badges.erase(e.badge);
|
|
}
|
|
else
|
|
{
|
|
badges[e.badge].first -= e.delta_level;
|
|
badges[e.badge].second -= e.delta_timestamp;
|
|
}
|
|
}
|
|
}
|
|
const uint32_t previous_level = cc::get_badge_score(badges).second;
|
|
if (/*(previous_level > 0 || new_level == 1) &&*/ new_level > previous_level)
|
|
{
|
|
UpdateConsoleColorMenu();
|
|
|
|
std::string msg = "You are now level " + std::to_string(new_level);
|
|
for (uint32_t level = previous_level + 1; level <= new_level; ++level)
|
|
{
|
|
std::string this_level_msg;
|
|
for (int variant = 1; variant < cc::NUM_BLOCK_VARIANTS; ++variant)
|
|
{
|
|
const cc::block_variant_t block_variant = cc::get_block_variant(variant);
|
|
if (block_variant.unlock_level == level)
|
|
this_level_msg += std::string("\n Material unlocked: ") + block_variant.name;
|
|
}
|
|
for (uint32_t c = 0; c < NUM_CHAT_COLORS && c < chat_colors.size(); ++c)
|
|
{
|
|
const uint32_t unlock_level = cc::get_chat_colour_unlock_level(c);
|
|
if (unlock_level == level)
|
|
this_level_msg += "\n Chat color unlocked: " + std::get<0>(chat_colors[c]);
|
|
}
|
|
if (!this_level_msg.empty())
|
|
{
|
|
msg += "\n Level " + std::to_string(level) + this_level_msg;
|
|
}
|
|
}
|
|
ui->AddToastNotification(msg.c_str(), "images/star_gold.png");
|
|
}
|
|
|
|
size_t city_idx = 0;
|
|
for (; city_idx < cg.cities.size(); ++city_idx)
|
|
if (cg.cities[city_idx].city_id == gameState.cityState.id)
|
|
break;
|
|
if (city_idx < cg.cities.size())
|
|
{
|
|
const auto &city = cg.cities[city_idx];
|
|
for (const auto &e: city.fire)
|
|
{
|
|
const std::shared_ptr<Flag> f = map.get_flag(e.flag);
|
|
if (!f)
|
|
continue;
|
|
std::string msg;
|
|
const uint8_t old_fire_state = f->fire_state;
|
|
const uint8_t new_fire_state = f->fire_state + e.delta_state;
|
|
const bool old_on_fire = old_fire_state > 0 && old_fire_state < 128;
|
|
const bool new_on_fire = new_fire_state > 0 && new_fire_state < 128;
|
|
if (!old_on_fire && new_on_fire)
|
|
msg = game_util::get_building_name(&gameState, e.flag) + " is on fire!";
|
|
else if (old_on_fire && !new_on_fire)
|
|
{
|
|
const uint32_t id = e.flag;
|
|
if (std::find_if(city.derelict.begin(), city.derelict.end(), [id](const cryptonote::cc_command_game_update_t::flag_t &e){ return e.id == id; }) == city.derelict.end())
|
|
msg = game_util::get_building_name(&gameState, e.flag) + " is not burning anymore";
|
|
else
|
|
msg = game_util::get_building_name(&gameState, e.flag) + " was destroyed by fire!";
|
|
}
|
|
if (!msg.empty())
|
|
ui->AddToastNotification(msg.c_str(), "images/house-on-fire.png");
|
|
}
|
|
}
|
|
|
|
if (!cg.auction_ends.empty() && wallet && wallet->wallet())
|
|
{
|
|
std::vector<cc::auction_t> auctions;
|
|
if (!wallet->get_cc_auctions(cg.auction_ends, auctions))
|
|
auctions.clear();
|
|
for (const auto &e: auctions)
|
|
{
|
|
std::string msg;
|
|
if (e.bids.empty())
|
|
{
|
|
msg = "The " + e.title + " auction ended without a buyer";
|
|
}
|
|
else
|
|
{
|
|
uint32_t player_id = std::get<0>(e.bids.back());
|
|
std::string price = cryptonote::print_money(std::get<1>(e.bids.back()));
|
|
game_util::reduce_amount_zeroes(price);
|
|
msg = gameState.get_player_name(player_id) + " won the " + e.title + " auction for " + price;
|
|
}
|
|
if (!msg.empty())
|
|
ui->AddToastNotification(msg.c_str(), "images/auction-hammer.png");
|
|
}
|
|
}
|
|
}
|
|
|
|
static void add_event_to_group(const cc::game_event_t &e, std::string &msg, std::set<uint64_t> &processed)
|
|
{
|
|
if (!msg.empty())
|
|
msg += "\n";
|
|
msg += e.event;
|
|
processed.insert(e.nonce);
|
|
}
|
|
|
|
void CryptoCityUrho3D::NotifyEvents(const cc::game_events_t &events, std::set<uint64_t> &processed)
|
|
{
|
|
const uint32_t me = gameState.playerState.id;
|
|
std::string trade_msg, building_msg, demolish_msg, fire_msg, coin_msg, auction_msg;
|
|
std::string sow_msg, harvest_vegetables_msg, harvest_grain_msg;
|
|
std::string chop_wood_msg, meat_msg, land_msg;
|
|
for (const auto &e: events)
|
|
{
|
|
if (processed.find(e.nonce) != processed.end())
|
|
continue;
|
|
if (e.account != me && std::find(e.counterparties.begin(), e.counterparties.end(), me) == e.counterparties.end())
|
|
continue;
|
|
if (e.cmd == get_cc_tag<uint8_t>(cryptonote::cc_command_trade_t()) || e.cmd == get_cc_tag<uint8_t>(cryptonote::cc_command_match_t()) || e.cmd == get_cc_tag<uint8_t>(cryptonote::cc_command_buy_items_t()) || e.cmd == get_cc_tag<uint8_t>(cryptonote::cc_command_dividend_t()) || e.cmd == get_cc_tag<uint8_t>(cryptonote::cc_command_create_mortgage_t()))
|
|
add_event_to_group(e, trade_msg, processed);
|
|
else if (e.cmd == get_cc_tag<uint8_t>(cryptonote::cc_command_sow_t()))
|
|
add_event_to_group(e, sow_msg, processed);
|
|
else if (e.cmd == get_cc_tag<uint8_t>(cryptonote::cc_command_harvest_t()))
|
|
add_event_to_group(e, strstr(e.event.c_str(), "egetables") ? harvest_vegetables_msg : harvest_grain_msg, processed);
|
|
else if (e.cmd == get_cc_tag<uint8_t>(cryptonote::cc_command_building_settings_t()) || e.cmd == get_cc_tag<uint8_t>(cryptonote::cc_command_repair_t()))
|
|
add_event_to_group(e, building_msg, processed);
|
|
else if (e.cmd == get_cc_tag<uint8_t>(cryptonote::cc_command_demolish_t()) || e.cmd == get_cc_tag<uint8_t>(cryptonote::cc_command_destroy_flag_t()))
|
|
add_event_to_group(e, demolish_msg, processed);
|
|
else if (e.cmd == get_cc_tag<uint8_t>(cryptonote::cc_command_fight_fire_t()))
|
|
add_event_to_group(e, fire_msg, processed);
|
|
else if (e.cmd == get_cc_tag<uint8_t>(cryptonote::cc_command_mint_t()) || e.cmd == get_cc_tag<uint8_t>(cryptonote::cc_command_smelt_t()))
|
|
add_event_to_group(e, coin_msg, processed);
|
|
else if (e.cmd == get_cc_tag<uint8_t>(cryptonote::cc_command_auction_bid_t()))
|
|
add_event_to_group(e, auction_msg, processed);
|
|
else if (e.cmd == get_cc_tag<uint8_t>(cryptonote::cc_command_chop_wood_t()))
|
|
add_event_to_group(e, chop_wood_msg, processed);
|
|
else if (e.cmd == get_cc_tag<uint8_t>(cryptonote::cc_command_hunt_t()))
|
|
add_event_to_group(e, meat_msg, processed);
|
|
else if (e.cmd == get_cc_tag<uint8_t>(cryptonote::cc_command_buy_land_t()))
|
|
add_event_to_group(e, land_msg, processed);
|
|
|
|
// hacky gemstone finder
|
|
static const struct {
|
|
const char *pattern, *icon;
|
|
} gemstone_patterns[] = {
|
|
"Found a diamond", "images/diamond.png",
|
|
"Found a ruby", "images/ruby.png",
|
|
"Found a emerald", "images/emerald.png",
|
|
"Found a amethyst", "images/amethyst.png",
|
|
"Found a sapphire", "images/sapphire.png",
|
|
};
|
|
for (const auto &f: gemstone_patterns)
|
|
{
|
|
if (strstr(e.event.c_str(), f.pattern))
|
|
{
|
|
ui->AddToastNotification(e.event.c_str(), f.icon);
|
|
processed.insert(e.nonce);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!trade_msg.empty())
|
|
ui->AddToastNotification(trade_msg.c_str(), "images/gold-coins.png");
|
|
if (!building_msg.empty())
|
|
ui->AddToastNotification(building_msg.c_str(), "images/building.png");
|
|
if (!sow_msg.empty())
|
|
ui->AddToastNotification(sow_msg.c_str(), "images/seeds.png");
|
|
if (!harvest_vegetables_msg.empty())
|
|
ui->AddToastNotification(harvest_vegetables_msg.c_str(), "images/vegetables.png");
|
|
if (!harvest_grain_msg.empty())
|
|
ui->AddToastNotification(harvest_grain_msg.c_str(), "images/wheat.png");
|
|
if (!demolish_msg.empty())
|
|
ui->AddToastNotification(demolish_msg.c_str(), "images/demolish.png");
|
|
if (!fire_msg.empty())
|
|
ui->AddToastNotification(fire_msg.c_str(), "images/house-on-fire.png");
|
|
if (!coin_msg.empty())
|
|
ui->AddToastNotification(coin_msg.c_str(), "images/blank-coin-256.png");
|
|
if (!auction_msg.empty())
|
|
ui->AddToastNotification(auction_msg.c_str(), "images/auction-hammer.png");
|
|
if (!chop_wood_msg.empty())
|
|
ui->AddToastNotification(chop_wood_msg.c_str(), "images/chop-wood.png");
|
|
if (!meat_msg.empty())
|
|
ui->AddToastNotification(meat_msg.c_str(), "images/meat.png");
|
|
if (!land_msg.empty())
|
|
ui->AddToastNotification(land_msg.c_str(), "images/land.png");
|
|
}
|
|
|
|
void CryptoCityUrho3D::OnNewBlock()
|
|
{
|
|
if (play_sfx_on_new_block)
|
|
audio.PlaySFX("Audio/SFX/501212__andrewharn__notification-synth.ogg");
|
|
|
|
ui->OnNewBlock();
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleNewChatLine(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet)
|
|
return;
|
|
|
|
std::shared_ptr<tools::wallet2> w = wallet->wallet();
|
|
uint32_t account_id = eventData[NewChatLine::P_ACCOUNT].GetUInt();
|
|
String line = eventData[NewChatLine::P_LINE].GetString();
|
|
unsigned color = eventData[NewChatLine::P_COLOR].GetUInt();
|
|
bool me = eventData[NewChatLine::P_ME].GetBool();
|
|
uint64_t timestamp = eventData[NewChatLine::P_TIMESTAMP].GetUInt64();
|
|
if (line.Empty() || gameState.ignore_player(account_id))
|
|
return;
|
|
|
|
const String stimestamp = tools::get_human_readable_timestamp(timestamp).c_str();
|
|
const String prefix = "(" + stimestamp + ")";
|
|
std::pair<String, uint32_t> source;
|
|
if (me)
|
|
{
|
|
source = std::make_pair("", 0);
|
|
line = String(gameState.get_player_name(account_id).c_str()) + " " + line;
|
|
}
|
|
else
|
|
{
|
|
source = std::make_pair(gameState.get_player_name(account_id).c_str(), account_id);
|
|
}
|
|
console->AddLine(prefix, source, line, me ? "ChatEmoteStyle" : "ChatDefaultStyle", color);
|
|
|
|
if (boost::starts_with(line.CString(), gameState.get_player_name(wallet->get_cc_account())))
|
|
mentioned_in_chat = true;
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleNewTradeCommand(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet)
|
|
return;
|
|
ui->NewTradeCommand();
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleWalletSynced(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet)
|
|
return;
|
|
const uint64_t top_height = eventData[Refreshed::P_TOP_HEIGHT].GetUInt64();
|
|
const std::string top_hash = eventData[Refreshed::P_TOP_HASH].GetString().CString();
|
|
gameState.update(wallet, top_height, top_hash);
|
|
ui->AddToastNotification("Game synced");
|
|
walletRefresher_.RequestSave();
|
|
}
|
|
|
|
bool CryptoCityUrho3D::IsCommandOurs(const cryptonote::cc_command_t *cmd) const
|
|
{
|
|
if (cmd->type() == typeid(cryptonote::cc_command_none_t))
|
|
return false;
|
|
const cryptonote::cc_command_base_t *base = cryptonote::get_cc_command_base(*cmd);
|
|
if (base && base->cc_account != 0 && base->cc_account == gameState.playerState.id)
|
|
return true;
|
|
if (cmd->type() == typeid(cryptonote::cc_command_create_account_t))
|
|
{
|
|
const cryptonote::cc_command_create_account_t &create_account = boost::get<cryptonote::cc_command_create_account_t>(*cmd);
|
|
if (create_account.public_key == wallet->get_cc_pkey())
|
|
return true;
|
|
}
|
|
if (cmd->type() == typeid(cryptonote::cc_command_redeem_account_t))
|
|
{
|
|
const cryptonote::cc_command_redeem_account_t &redeem_account = boost::get<cryptonote::cc_command_redeem_account_t>(*cmd);
|
|
if (redeem_account.public_key == wallet->get_cc_pkey())
|
|
return true;
|
|
}
|
|
if (cmd->type() == typeid(cryptonote::cc_command_transfer_t))
|
|
{
|
|
const cryptonote::cc_command_transfer_t &transfer = boost::get<cryptonote::cc_command_transfer_t>(*cmd);
|
|
if (transfer.cc_account == 0 && transfer.public_key == wallet->get_cc_pkey())
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleNewPoolCommand(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
const String txid = eventData[NewPoolCommand::P_TXID].GetString();
|
|
const cryptonote::cc_command_t *cmd = (const cryptonote::cc_command_t*)eventData[NewPoolCommand::P_COMMAND].GetVoidPtr();
|
|
if (!cmd)
|
|
return;
|
|
|
|
if (IsCommandOurs(cmd))
|
|
{
|
|
ui->AddQueuedCommand(txid.CString(), *cmd);
|
|
if (cmd->type() == typeid(cryptonote::cc_command_build_t))
|
|
{
|
|
const cryptonote::cc_command_build_t &build = boost::get<cryptonote::cc_command_build_t>(*cmd);
|
|
++flags_with_pending_build_commands[build.flag];
|
|
flags_with_pending_build_commands_commands[txid] = build.flag;
|
|
}
|
|
}
|
|
|
|
if (cmd->type() == typeid(cryptonote::cc_command_trade_t))
|
|
{
|
|
const cryptonote::cc_command_trade_t &trade = boost::get<cryptonote::cc_command_trade_t>(*cmd);
|
|
if (trade.type == cryptonote::cc_command_trade_t::type_flag && !trade.bid && trade.matches.empty())
|
|
{
|
|
flags_for_sale[trade.id] = trade.price;
|
|
flag_for_sale_commands[txid] = trade.id;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandlePoolCommandGone(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
const String txid = eventData[PoolCommandGone::P_TXID].GetString();
|
|
ui->RemoveQueuedCommand(txid.CString());
|
|
|
|
auto j = flags_with_pending_build_commands_commands.find(txid);
|
|
if (j != flags_with_pending_build_commands_commands.end())
|
|
{
|
|
const uint32_t flag = j->second;
|
|
--flags_with_pending_build_commands[flag];
|
|
flags_with_pending_build_commands_commands.erase(j);
|
|
}
|
|
|
|
auto i = flag_for_sale_commands.find(txid);
|
|
if (i != flag_for_sale_commands.end())
|
|
{
|
|
const uint32_t flag = i->second;
|
|
flags_for_sale.erase(flag);
|
|
flag_for_sale_commands.erase(i);
|
|
}
|
|
|
|
walletRefresher_.RequestRefresh();
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleGameNotification(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
String text = eventData[GameNotification::P_TEXT].GetString();
|
|
bool important = eventData[GameNotification::P_IMPORTANT].GetBool();
|
|
String icon = eventData[GameNotification::P_ICON].GetString();
|
|
console->AddLine("", std::make_pair("", 0), text, "ChatNotificationStyle", 0);
|
|
if (important)
|
|
{
|
|
ui->AddToastNotification(text, icon);
|
|
notification_sent_by_command_processing = true;
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::SetWindowTitleAndIcon()
|
|
{
|
|
ResourceCache* cache = GetSubsystem<ResourceCache>();
|
|
Graphics* graphics = GetSubsystem<Graphics>();
|
|
Image* icon = cache->GetResource<Image>("Textures/UrhoIcon.png");
|
|
graphics->BeginFrame();
|
|
graphics->Clear(CLEAR_COLOR | CLEAR_DEPTH | CLEAR_STENCIL, Color(0.0f, 0.0f, 0.0f, 1.0f));
|
|
graphics->EndFrame();
|
|
graphics->SetWindowIcon(icon);
|
|
graphics->SetWindowTitle("Townforge");
|
|
}
|
|
|
|
void CryptoCityUrho3D::ShowSplashScreen()
|
|
{
|
|
ResourceCache* cache = GetSubsystem<ResourceCache>();
|
|
Graphics* graphics = GetSubsystem<Graphics>();
|
|
|
|
float w = 1.0f;
|
|
float h = 1.0f;
|
|
|
|
SharedPtr<Texture2D> texture(cache->GetResource<Texture2D>("Textures/title.jpeg"));
|
|
ShaderVariation* vs = graphics->GetShader(VS, "Basic", "DIFFMAP VERTEXCOLOR");
|
|
ShaderVariation* ps = graphics->GetShader(PS, "Basic", "DIFFMAP VERTEXCOLOR");
|
|
|
|
const float wratio = texture->GetWidth() / (float)graphics->GetWidth();
|
|
const float hratio = texture->GetHeight() / (float)graphics->GetHeight();
|
|
if (wratio > hratio)
|
|
{
|
|
h = hratio / wratio;
|
|
}
|
|
else
|
|
{
|
|
w = wratio / hratio;
|
|
}
|
|
|
|
SharedPtr<VertexBuffer> vertexBuffer(new VertexBuffer(context_));
|
|
vertexBuffer->SetSize(6, MASK_POSITION | MASK_COLOR | MASK_TEXCOORD1, true);
|
|
float *dest = (float*)vertexBuffer->Lock(0, 6, true);
|
|
|
|
*dest++ = -w;
|
|
*dest++ = h;
|
|
*dest++ = 0;
|
|
((unsigned&)*dest++) = 0xffffffff;
|
|
*dest++ = 0;
|
|
*dest++ = 0;
|
|
|
|
*dest++ = -w;
|
|
*dest++ = -h;
|
|
*dest++ = 0;
|
|
((unsigned&)*dest++) = 0xffffffff;
|
|
*dest++ = 0;
|
|
*dest++ = 1;
|
|
|
|
*dest++ = w;
|
|
*dest++ = -h;
|
|
*dest++ = 0;
|
|
((unsigned&)*dest++) = 0xffffffff;
|
|
*dest++ = 1;
|
|
*dest++ = 1;
|
|
|
|
*dest++ = -w;
|
|
*dest++ = h;
|
|
*dest++ = 0;
|
|
((unsigned&)*dest++) = 0xffffffff;
|
|
*dest++ = 0;
|
|
*dest++ = 0;
|
|
|
|
*dest++ = w;
|
|
*dest++ = h;
|
|
*dest++ = 0;
|
|
((unsigned&)*dest++) = 0xffffffff;
|
|
*dest++ = 1;
|
|
*dest++ = 0;
|
|
|
|
*dest++ = w;
|
|
*dest++ = -h;
|
|
*dest++ = 0;
|
|
((unsigned&)*dest++) = 0xffffffff;
|
|
*dest++ = 1;
|
|
*dest++ = 1;
|
|
|
|
vertexBuffer->Unlock();
|
|
|
|
Matrix4 view = Matrix4::IDENTITY;
|
|
Matrix4 gpuProjection = Matrix4::IDENTITY;
|
|
|
|
graphics->BeginFrame();
|
|
|
|
graphics->ClearParameterSources();
|
|
graphics->SetBlendMode(BLEND_ALPHA);
|
|
graphics->SetColorWrite(true);
|
|
graphics->SetCullMode(CULL_NONE);
|
|
graphics->SetDepthWrite(false);
|
|
graphics->SetDepthTest(CMP_ALWAYS);
|
|
graphics->SetLineAntiAlias(true);
|
|
graphics->SetScissorTest(false);
|
|
graphics->SetStencilTest(false);
|
|
graphics->SetFillMode(FILL_SOLID);
|
|
graphics->SetShaders(vs, ps);
|
|
graphics->SetShaderParameter(VSP_MODEL, Matrix3x4::IDENTITY);
|
|
graphics->SetShaderParameter(VSP_VIEW, view);
|
|
graphics->SetShaderParameter(VSP_VIEWINV, view.Inverse());
|
|
graphics->SetShaderParameter(VSP_VIEWPROJ, gpuProjection * view);
|
|
graphics->SetShaderParameter(PSP_MATDIFFCOLOR, Color(1.0f, 1.0f, 1.0f, 1.0f));
|
|
|
|
graphics->SetTexture(0, texture);
|
|
graphics->SetVertexBuffer(vertexBuffer);
|
|
graphics->Draw(TRIANGLE_LIST, 0, 6);
|
|
graphics->EndFrame();
|
|
}
|
|
|
|
void CryptoCityUrho3D::CreateDebugHud()
|
|
{
|
|
// Get default style
|
|
ResourceCache* cache = GetSubsystem<ResourceCache>();
|
|
XMLFile* xmlFile = cache->GetResource<XMLFile>("UI/DefaultStyle.xml");
|
|
|
|
// Create debug HUD.
|
|
DebugHud* debugHud = engine_->CreateDebugHud();
|
|
debugHud->SetDefaultStyle(xmlFile);
|
|
}
|
|
|
|
|
|
void CryptoCityUrho3D::HandleKeyUp(StringHash /*eventType*/, VariantMap& eventData)
|
|
{
|
|
using namespace KeyUp;
|
|
|
|
int key = eventData[P_KEY].GetInt();
|
|
|
|
if (GetSubsystem<Urho3D::UI>()->GetFocusElement() || TBWidget::focused_widget)
|
|
return;
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleKeyDown(StringHash /*eventType*/, VariantMap& eventData)
|
|
{
|
|
using namespace KeyDown;
|
|
|
|
const Urho3D::Key key = (Urho3D::Key)eventData[P_KEY].GetInt();
|
|
const Controls::Action action = Controls::Get().GetActionFromKey(key);
|
|
const bool debug = GetConfigValue(NULL, CONFIG_DEBUG, false);
|
|
|
|
// Toggle debug HUD with F2
|
|
if (debug && key == KEY_F2)
|
|
GetSubsystem<DebugHud>()->ToggleAll();
|
|
else if (debug && key == KEY_F6)
|
|
RebuildMap(true);
|
|
|
|
// Common rendering quality controls, only when UI has no focused element
|
|
else if (!GetSubsystem<Urho3D::UI>()->GetFocusElement() && !TBWidget::focused_widget && !UITBWindow::HasModalDialog() && !ui->IsSelectMaterialOpen())
|
|
{
|
|
Renderer* renderer = GetSubsystem<Renderer>();
|
|
Input* input = GetSubsystem<Input>();
|
|
|
|
// Preferences / Pause
|
|
if (key == KEY_SELECT && touchEnabled_)
|
|
{
|
|
paused_ = !paused_;
|
|
|
|
if (screenJoystickSettingsIndex_ == M_MAX_UNSIGNED)
|
|
{
|
|
// Lazy initialization
|
|
ResourceCache* cache = GetSubsystem<ResourceCache>();
|
|
screenJoystickSettingsIndex_ = (unsigned)input->AddScreenJoystick(cache->GetResource<XMLFile>("UI/ScreenJoystickSettings_CryptoCityUrho3D.xml"), cache->GetResource<XMLFile>("UI/DefaultStyle.xml"));
|
|
}
|
|
else
|
|
input->SetScreenJoystickVisible(screenJoystickSettingsIndex_, paused_);
|
|
}
|
|
|
|
// Texture quality
|
|
else if (debug && key == '1')
|
|
{
|
|
auto quality = (unsigned)renderer->GetTextureQuality();
|
|
++quality;
|
|
if (quality > QUALITY_HIGH)
|
|
quality = QUALITY_LOW;
|
|
renderer->SetTextureQuality((MaterialQuality)quality);
|
|
}
|
|
|
|
// Material quality
|
|
else if (debug && key == '2')
|
|
{
|
|
auto quality = (unsigned)renderer->GetMaterialQuality();
|
|
++quality;
|
|
if (quality > QUALITY_HIGH)
|
|
quality = QUALITY_LOW;
|
|
renderer->SetMaterialQuality((MaterialQuality)quality);
|
|
}
|
|
|
|
// Specular lighting
|
|
else if (debug && key == '3')
|
|
renderer->SetSpecularLighting(!renderer->GetSpecularLighting());
|
|
|
|
// Shadow rendering
|
|
else if (debug && key == '4')
|
|
renderer->SetDrawShadows(!renderer->GetDrawShadows());
|
|
|
|
// Shadow map resolution
|
|
else if (debug && key == '5')
|
|
{
|
|
int shadowMapSize = renderer->GetShadowMapSize();
|
|
shadowMapSize *= 2;
|
|
if (shadowMapSize > 2048)
|
|
shadowMapSize = 512;
|
|
renderer->SetShadowMapSize(shadowMapSize);
|
|
}
|
|
|
|
// Shadow depth and filtering quality
|
|
else if (debug && key == '6')
|
|
{
|
|
ShadowQuality quality = renderer->GetShadowQuality();
|
|
quality = (ShadowQuality)(quality + 1);
|
|
if (quality > SHADOWQUALITY_BLUR_VSM)
|
|
quality = SHADOWQUALITY_SIMPLE_16BIT;
|
|
renderer->SetShadowQuality(quality);
|
|
}
|
|
|
|
// Occlusion culling
|
|
else if (debug && key == '7')
|
|
{
|
|
bool occlusion = renderer->GetMaxOccluderTriangles() > 0;
|
|
occlusion = !occlusion;
|
|
renderer->SetMaxOccluderTriangles(occlusion ? 5000 : 0);
|
|
}
|
|
|
|
// Instancing
|
|
else if (debug && key == '8')
|
|
renderer->SetDynamicInstancing(!renderer->GetDynamicInstancing());
|
|
|
|
// Take screenshot
|
|
else if (debug && key == '9')
|
|
{
|
|
Graphics* graphics = GetSubsystem<Graphics>();
|
|
Image screenshot(context_);
|
|
graphics->TakeScreenShot(screenshot);
|
|
// Here we save in the Data folder with date and time appended
|
|
const String base_filename = GetSubsystem<FileSystem>()->GetProgramDir() + "Screenshot_" +
|
|
Time::GetTimeStamp().Replaced(':', '_').Replaced('.', '_').Replaced(' ', '_');
|
|
screenshot.SavePNG(base_filename + ".png");
|
|
|
|
// save lower quality jpeg too
|
|
screenshot.Resize(screenshot.GetWidth() / 2, screenshot.GetHeight() / 2);
|
|
screenshot.SaveJPG(base_filename + ".jpeg", 0);
|
|
}
|
|
|
|
else if (debug && key == 'r')
|
|
{
|
|
cityMesh->setRenderMode((RenderMode)((cityMesh->getRenderMode() + 1) % NumRenderModes));
|
|
}
|
|
else if (debug && key == KEY_F12)
|
|
{
|
|
drawDebugGeometry_ = !drawDebugGeometry_;
|
|
}
|
|
|
|
// mouse-based building
|
|
else if (action == Controls::ACTION_ADD_BLOCK)
|
|
{
|
|
queued_commands.push_back(std::shared_ptr<AddBlockCommand>(new AddBlockCommand(false, true)));
|
|
}
|
|
else if (action == Controls::ACTION_REMOVE_BLOCK)
|
|
{
|
|
queued_commands.push_back(std::shared_ptr<RemoveBlockCommand>(new RemoveBlockCommand(false, false)));
|
|
}
|
|
else if (action == Controls::ACTION_ADD_BLOCKS)
|
|
{
|
|
queued_commands.push_back(std::shared_ptr<AddBlockCommand>(new AddBlockCommand(true, false)));
|
|
}
|
|
else if (action == Controls::ACTION_ADD_BLOCKS_FLAT)
|
|
{
|
|
queued_commands.push_back(std::shared_ptr<AddBlockCommand>(new AddBlockCommand(true, true)));
|
|
}
|
|
else if (action == Controls::ACTION_REMOVE_BLOCKS)
|
|
{
|
|
queued_commands.push_back(std::shared_ptr<RemoveBlockCommand>(new RemoveBlockCommand(true, false)));
|
|
}
|
|
else if (action == Controls::ACTION_UNDO)
|
|
{
|
|
queued_commands.push_back(std::shared_ptr<UndoCommand>(new UndoCommand()));
|
|
}
|
|
else if (action == Controls::ACTION_REDO)
|
|
{
|
|
queued_commands.push_back(std::shared_ptr<RedoCommand>(new RedoCommand()));
|
|
}
|
|
else if (action == Controls::ACTION_REMOVE_TOP_LEVEL)
|
|
{
|
|
queued_commands.push_back(std::shared_ptr<RemoveBlockCommand>(new RemoveBlockCommand(true, true)));
|
|
}
|
|
else if (action == Controls::ACTION_CYCLE_MATERIALS)
|
|
{
|
|
SelectNextMaterial(true);
|
|
}
|
|
else if (action == Controls::ACTION_TOGGLE_BUILD_MODE)
|
|
{
|
|
buildMode_ = !buildMode_;
|
|
crosshair_->SetVisible(buildMode_);
|
|
GetSubsystem<Input>()->SetMouseMode(buildMode_ ? MM_WRAP : MM_ABSOLUTE);
|
|
}
|
|
else if (action == Controls::ACTION_PICK_MATERIAL)
|
|
{
|
|
PickMaterial();
|
|
}
|
|
else if (action == Controls::ACTION_SELECT_MATERIAL)
|
|
{
|
|
ui->OpenSelectMaterial(currentBlockVariant);
|
|
}
|
|
else if (action == Controls::ACTION_JUMP_TO_BUILDING)
|
|
{
|
|
const std::shared_ptr<Flag> flag = GetSelectedFlag();
|
|
if (flag)
|
|
GoToFlag(flag);
|
|
}
|
|
else if (action == Controls::ACTION_PREV_FLAG)
|
|
{
|
|
const bool same_role = input->GetQualifierDown(QUAL_SHIFT);
|
|
GoToNextFlag(false, same_role);
|
|
}
|
|
else if (action == Controls::ACTION_NEXT_FLAG)
|
|
{
|
|
const bool same_role = input->GetQualifierDown(QUAL_SHIFT);
|
|
GoToNextFlag(true, same_role);
|
|
}
|
|
else if (action == Controls::ACTION_TOGGLE_LIGHT)
|
|
{
|
|
enableUserLight_ = !enableUserLight_;
|
|
Light* cameraLight = cameraNode_->GetComponent<Light>();
|
|
if (cameraLight)
|
|
cameraLight->Remove();
|
|
}
|
|
|
|
// console
|
|
else if (action == Controls::ACTION_TOGGLE_CONSOLE)
|
|
{
|
|
ToggleConsole();
|
|
}
|
|
|
|
// and screens
|
|
else if (action == Controls::ACTION_PLAYER_SCREEN)
|
|
{
|
|
if (gameState.playerState.id)
|
|
ui->ShowPlayerInfo(gameState.playerState.id);
|
|
}
|
|
else if (action == Controls::ACTION_SEARCH_FLAG)
|
|
{
|
|
ui->OnSearchFlagByName();
|
|
}
|
|
else if (action == Controls::ACTION_TRADE_SCREEN)
|
|
{
|
|
ui->ShowTradeScreen();
|
|
}
|
|
|
|
// camera modes
|
|
else if (action == Controls::ACTION_FREE_CAMERA)
|
|
{
|
|
if (dynamic_cast<FreeCamera*>(camera_.Get()))
|
|
{
|
|
camera_ = new WalkerCamera(cameraNode_);
|
|
ui->SetCameraType(TBIDC("walker"));
|
|
}
|
|
else
|
|
{
|
|
camera_ = new FreeCamera(cameraNode_);
|
|
ui->SetCameraType(TBIDC("free"));
|
|
}
|
|
camera_->set_collidable_root(cityMesh->GetSelectableNode());
|
|
}
|
|
else if (action == Controls::ACTION_ORBIT_CAMERA)
|
|
{
|
|
if (dynamic_cast<FreeCamera*>(camera_.Get()))
|
|
{
|
|
camera_ = new WalkerCamera(cameraNode_);
|
|
ui->SetCameraType(TBIDC("walker"));
|
|
}
|
|
else
|
|
{
|
|
camera_ = new OrbitCamera(cameraNode_);
|
|
ui->SetCameraType(TBIDC("orbit"));
|
|
}
|
|
camera_->set_collidable_root(cityMesh->GetSelectableNode());
|
|
}
|
|
|
|
else if (action == Controls::ACTION_SKIP_TRACK)
|
|
{
|
|
audio.PlayNext();
|
|
}
|
|
|
|
// options
|
|
else if (key == KEY_ESCAPE)
|
|
{
|
|
if (console->IsVisible())
|
|
console->RequestClose();
|
|
else
|
|
ui->ShowOptions();
|
|
}
|
|
}
|
|
else if (key == KEY_ESCAPE)
|
|
{
|
|
// Close console (if open) or exit when ESC is pressed
|
|
UnsetFocus();
|
|
if (!UITBWindow::CloseModal())
|
|
{
|
|
if (console->IsVisible())
|
|
{
|
|
console->RequestClose();
|
|
}
|
|
else if (GetPlatform() == "Web")
|
|
{
|
|
GetSubsystem<Input>()->SetMouseVisible(true);
|
|
if (useMouseMode_ != MM_ABSOLUTE)
|
|
GetSubsystem<Input>()->SetMouseMode(MM_FREE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::UpdateConsoleColorMenu()
|
|
{
|
|
auto* cache = GetSubsystem<ResourceCache>();
|
|
SharedPtr<JSONFile> colors_desc(cache->GetResource<JSONFile>("UI/chat/colors.json"));
|
|
if (colors_desc)
|
|
{
|
|
chat_colors.clear();
|
|
const uint32_t player_level = cc::get_badge_score(gameState.playerState.badges).second;
|
|
const JSONValue &root = colors_desc->GetRoot();
|
|
if (root.IsObject() && root.Get("colors").IsArray())
|
|
{
|
|
const JSONArray &array = root.Get("colors").GetArray();
|
|
for (unsigned i = 0; i < array.Size(); ++i)
|
|
{
|
|
const uint32_t unlock_level = cc::get_chat_colour_unlock_level(i);
|
|
const bool unlocked = player_level >= unlock_level;
|
|
if (array[i].Get("name").IsString() && array[i].Get("red").IsNumber() && array[i].Get("green").IsNumber() && array[i].Get("blue").IsNumber())
|
|
{
|
|
const Color color(array[i].Get("red").GetUInt() / 255.0f, array[i].Get("green").GetUInt() / 255.0f, array[i].Get("blue").GetUInt() / 255.0f);
|
|
std::string str = array[i].Get("name").GetString().CString();
|
|
if (!unlocked)
|
|
str = " " + str + " - lv " + std::to_string(unlock_level);
|
|
chat_colors.push_back(std::make_tuple(str, color, unlocked));
|
|
}
|
|
}
|
|
console->AddColors(chat_colors, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::ToggleConsole()
|
|
{
|
|
UpdateConsoleColorMenu();
|
|
console->Toggle();
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleEditModeChanged(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
editMode = (EditMode)eventData[EditModeChanged::P_EDIT_MODE].GetInt();
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleExtendSelection(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
|
|
const int32_t dx0 = eventData[SelectExtend::P_DX0].GetInt();
|
|
const int32_t dy0 = eventData[SelectExtend::P_DY0].GetInt();
|
|
const int32_t dx1 = eventData[SelectExtend::P_DX1].GetInt();
|
|
const int32_t dy1 = eventData[SelectExtend::P_DY1].GetInt();
|
|
|
|
selection.extend(dx0, dy0, dx1, dy1);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleExtendSelectionMaximally(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
|
|
const int32_t dx0 = eventData[SelectExtendMaximally::P_DX0].GetInt();
|
|
const int32_t dy0 = eventData[SelectExtendMaximally::P_DY0].GetInt();
|
|
const int32_t dx1 = eventData[SelectExtendMaximally::P_DX1].GetInt();
|
|
const int32_t dy1 = eventData[SelectExtendMaximally::P_DY1].GetInt();
|
|
const int32_t allow[4] = { dx0, dy0, dx1, dy1 };
|
|
|
|
bool unchanged = false;
|
|
while (!unchanged)
|
|
{
|
|
unchanged = true;
|
|
|
|
static const int32_t deltas[4][4] = {
|
|
{1, 0, 0, 0},
|
|
{0, 1, 0, 0},
|
|
{0, 0, 1, 0},
|
|
{0, 0, 0, 1},
|
|
};
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
if (!allow[i])
|
|
continue;
|
|
if (deltas[i][0] != 0 || deltas[i][2] != 0)
|
|
if (selection.bounding_box_width() >= 256)
|
|
continue;
|
|
if (deltas[i][1] != 0 || deltas[i][3] != 0)
|
|
if (selection.bounding_box_height() >= 256)
|
|
continue;
|
|
Selection s = selection;
|
|
s.extend(deltas[i][0], deltas[i][1], deltas[i][2], deltas[i][3]);
|
|
std::set<std::shared_ptr<Flag>> flags;
|
|
map.get_flags(s.x0, s.y0, s.x1, s.y1, flags);
|
|
if (flags.empty())
|
|
{
|
|
selection = std::move(s);
|
|
unchanged = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleShrinkSelection(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
|
|
const int32_t dx0 = eventData[SelectShrink::P_DX0].GetInt();
|
|
const int32_t dy0 = eventData[SelectShrink::P_DY0].GetInt();
|
|
const int32_t dx1 = eventData[SelectShrink::P_DX1].GetInt();
|
|
const int32_t dy1 = eventData[SelectShrink::P_DY1].GetInt();
|
|
|
|
selection.shrink(!!dx0, !!dy0, !!dx1, !!dy1);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleMoveSelection(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
|
|
const int32_t dx = eventData[SelectMove::P_DX].GetInt();
|
|
const int32_t dy = eventData[SelectMove::P_DY].GetInt();
|
|
|
|
selection.move(dx, dy);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleGrowSelection(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
selection.grow();
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleSelectWholeFlag(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
SelectWholeFlag();
|
|
}
|
|
|
|
void CryptoCityUrho3D::SelectWholeFlag()
|
|
{
|
|
std::shared_ptr<Flag> flag;
|
|
|
|
if (flagUnderConstruction)
|
|
{
|
|
flag = map.get_flag(std::get<0>(*flagUnderConstruction));
|
|
if (flag)
|
|
{
|
|
selection = Selection(flag->x0, flag->y0, flag->x1, flag->y1);
|
|
hasSelection_ = true;
|
|
}
|
|
}
|
|
if (!flag)
|
|
{
|
|
auto eff = GetEffectiveSelection();
|
|
flag = map.get_flag(eff.x0, eff.y0);
|
|
if (flag)
|
|
{
|
|
selection = Selection(flag->x0, flag->y0, flag->x1, flag->y1);
|
|
hasSelection_ = true;
|
|
}
|
|
}
|
|
|
|
if (!flag)
|
|
{
|
|
new MessageBox(context_, "First select part of the desired flag");
|
|
return;
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleSelectEdge(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
selection.make_edge();
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleGoToFlag(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
const uint32_t flag_id = eventData[GoToFlag::P_FLAG].GetUInt();
|
|
|
|
const std::shared_ptr<Flag> flag = map.get_flag(flag_id);
|
|
if (!flag)
|
|
{
|
|
new MessageBox(context_, "Flag not found");
|
|
return;
|
|
}
|
|
|
|
GoToFlag(flag);
|
|
}
|
|
|
|
void CryptoCityUrho3D::GoToFlag(const std::shared_ptr<Flag> &flag)
|
|
{
|
|
const uint32_t x = flag->x0 + (flag->x1 - flag->x0) / 2;
|
|
const uint32_t y = flag->y0 + (flag->y1 - flag->y0) / 2;
|
|
const uint16_t terrain_height = cityMesh->GetTerrainHeight(x, y);
|
|
Vector3 pos((int32_t)(x - gameState.cityState.ox), 30.f + terrain_height, (int32_t)(y - gameState.cityState.oy));
|
|
|
|
camera_ = new FreeCamera(cameraNode_);
|
|
ui->SetCameraType(TBIDC("free"));
|
|
camera_->set_target(pos);
|
|
|
|
cameraNode_->SetPosition(pos + Vector3(30.0f, 90.0f, -100.0f));
|
|
Vector3 lookat(pos);
|
|
cameraNode_->LookAt(lookat);
|
|
|
|
UITBWindow::CloseModal();
|
|
|
|
selection.clear();
|
|
hasSelection_ = false;
|
|
last_selected_flag = flag;
|
|
}
|
|
|
|
void CryptoCityUrho3D::GoToNextFlag(bool next, bool same_role)
|
|
{
|
|
const auto &flags = gameState.playerState.flags;
|
|
|
|
if (flags.empty())
|
|
return;
|
|
|
|
uint32_t flag_id = last_selected_flag ? last_selected_flag->id : 0;
|
|
std::vector<uint32_t>::const_iterator i = std::find(flags.begin(), flags.end(), flag_id);
|
|
|
|
// if no flag or the flag does not belong to us
|
|
if (i == flags.end())
|
|
i = flags.begin();
|
|
if (i == flags.end())
|
|
return;
|
|
|
|
const uint8_t role = map.get_flag(*i)->role;
|
|
do
|
|
{
|
|
if (next)
|
|
{
|
|
++i;
|
|
if (i == flags.end()) i = flags.begin();
|
|
}
|
|
else
|
|
{
|
|
if (i == flags.begin()) i = flags.end();
|
|
--i;
|
|
}
|
|
} while(same_role && role != map.get_flag(*i)->role);
|
|
|
|
GoToFlag(map.get_flag(*i));
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleGoToVista(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
const uint32_t city = eventData[GoToVista::P_CITY].GetUInt();
|
|
const Urho3D::Vector3 position = eventData[GoToVista::P_POSITION].GetVector3();
|
|
const Urho3D::Vector3 orientation = eventData[GoToVista::P_ORIENTATION].GetVector3();
|
|
|
|
if (city != gameState.cityState.id)
|
|
GoToCity(city);
|
|
|
|
camera_ = new FreeCamera(cameraNode_);
|
|
ui->SetCameraType(TBIDC("free"));
|
|
startPosition_ = position;
|
|
startRotation_ = Quaternion(orientation.x_, orientation.y_, orientation.z_);
|
|
startPositionHugTerrain_ = false;
|
|
MoveCameraToStartPosition(false); // goes now, the start pos/rot above will teleport if we had to change city
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleGetVista(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
eventData[GetVista::P_CITY] = (uint32_t)gameState.cityState.id;
|
|
eventData[GetVista::P_POSITION] = cameraNode_->GetPosition();
|
|
eventData[GetVista::P_ORIENTATION] = cameraNode_->GetRotation().EulerAngles();
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleGetGameUpdateEvents(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (wallet)
|
|
{
|
|
uint64_t height = eventData[GetGameUpdateEvents::P_HEIGHT].GetUInt64(), timestamp;
|
|
cc::game_events_t *events = new cc::game_events_t();
|
|
wallet->get_last_update_events(height, timestamp, *events);
|
|
eventData[GetGameUpdateEvents::P_EVENTS] = (void*)events;
|
|
eventData[GetGameUpdateEvents::P_HEIGHT] = (unsigned long long)height;
|
|
eventData[GetGameUpdateEvents::P_TIMESTAMP] = (unsigned long long)timestamp;
|
|
}
|
|
}
|
|
|
|
static Variant make_item_balance_variant(const std::pair<uint32_t, uint32_t> &e)
|
|
{
|
|
VariantMap v;
|
|
v["type"] = e.first;
|
|
v["amount"] = e.second;
|
|
return v;
|
|
}
|
|
|
|
static Variant make_flag_variant(const uint32_t id)
|
|
{
|
|
VariantMap v;
|
|
v["id"] = id;
|
|
return v;
|
|
}
|
|
|
|
static Variant make_badge_variant(const std::pair<uint32_t, std::pair<uint8_t, uint64_t>> &e)
|
|
{
|
|
VariantMap v;
|
|
v["id"] = e.first;
|
|
v["level"] = e.second.first;
|
|
v["timestamp"] = (unsigned long long)e.second.second;
|
|
return v;
|
|
}
|
|
|
|
static Variant make_attribute_variant(const std::pair<uint32_t, uint32_t> &e)
|
|
{
|
|
VariantMap v;
|
|
v["id"] = e.first;
|
|
v["points"] = e.second;
|
|
return v;
|
|
}
|
|
|
|
static Variant make_reserve_variant(const std::pair<uint32_t, std::pair<uint64_t, std::map<uint32_t, uint32_t>>> &e)
|
|
{
|
|
VariantMap v;
|
|
v["counterparty"] = e.first;
|
|
v["balance"] = (unsigned long long)e.second.first;
|
|
VariantVector items;
|
|
for (const auto &i: e.second.second)
|
|
{
|
|
VariantMap v2;
|
|
v2["type"] = i.first;
|
|
v2["amount"] = i.second;
|
|
items.Push(std::move(v2));
|
|
}
|
|
v["items"] = std::move(items);
|
|
return v;
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleRequestPlayerData(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (wallet)
|
|
{
|
|
const String pkey = eventData[RequestPlayerData::P_PUBLIC_KEY].GetString();
|
|
uint32_t id = eventData[RequestPlayerData::P_ACCOUNT].GetUInt();
|
|
if (!pkey.Empty())
|
|
{
|
|
id = wallet->lookup_account(pkey.CString());
|
|
}
|
|
|
|
std::string name, description;
|
|
std::string public_key, pmspk, pmvpk;
|
|
uint64_t balance;
|
|
std::map<uint32_t, uint32_t> item_balances;
|
|
std::vector<uint32_t> flags;
|
|
std::map<uint32_t, std::pair<uint8_t, uint64_t>> badges;
|
|
std::map<uint32_t, uint32_t> attributes;
|
|
bool ignore;
|
|
uint32_t inviting_account;
|
|
uint32_t num_invited;
|
|
uint32_t first_flag;
|
|
uint32_t script, script_state, script_city, script_owner;
|
|
std::map<uint32_t, std::pair<uint64_t, std::map<uint32_t, uint32_t>>> reserve;
|
|
std::map<std::string, uint64_t> script_local_variables;
|
|
uint32_t prestige;
|
|
if (wallet->get_cc_account_data(id, public_key, balance, item_balances, flags, badges, attributes, name, description, ignore, inviting_account, num_invited, first_flag, script, script_state, script_city, script_owner, reserve, script_local_variables, prestige, pmspk, pmvpk))
|
|
{
|
|
gameState.set_player_name(id, name, ignore);
|
|
|
|
eventData[RequestPlayerData::P_ACCOUNT] = id;
|
|
eventData[RequestPlayerData::P_PUBLIC_KEY] = public_key.c_str();
|
|
eventData[RequestPlayerData::P_NAME] = gameState.get_player_name(id).c_str();
|
|
eventData[RequestPlayerData::P_DESCRIPTION] = description.c_str();
|
|
eventData[RequestPlayerData::P_BALANCE] = (unsigned long long)balance;
|
|
eventData[RequestPlayerData::P_PRESTIGE] = prestige;
|
|
VariantVector ItemBalances; for (const auto &e: item_balances) ItemBalances.Push(make_item_balance_variant(e));
|
|
eventData[RequestPlayerData::P_ITEM_BALANCES] = ItemBalances;
|
|
VariantVector Flags; for (uint32_t id: flags) Flags.Push(make_flag_variant(id));
|
|
eventData[RequestPlayerData::P_FLAGS] = Flags;
|
|
VariantVector Badges; for (const auto &e: badges) Badges.Push(make_badge_variant(e));
|
|
eventData[RequestPlayerData::P_BADGES] = Badges;
|
|
VariantVector Attributes; for (const auto &e: attributes) Attributes.Push(make_attribute_variant(e));
|
|
eventData[RequestPlayerData::P_ATTRIBUTES] = Attributes;
|
|
VariantVector Reserve; for (const auto &e: reserve) Reserve.Push(make_reserve_variant(e));
|
|
eventData[RequestPlayerData::P_RESERVE] = Reserve;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleRequestItemData(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (wallet)
|
|
{
|
|
const uint32_t id = eventData[RequestItemData::P_ID].GetUInt();
|
|
uint32_t creator, group;
|
|
uint64_t amount, creation_height, gold;
|
|
std::string name;
|
|
bool ignore, is_group, is_public;
|
|
std::string primary_description, secondary_description;
|
|
std::vector<uint64_t> user_data;
|
|
crypto::hash hash;
|
|
if (wallet->get_cc_custom_item_data(id, creation_height, creator, amount, name, is_group, is_public, group, primary_description, secondary_description, ignore, gold, user_data, hash))
|
|
{
|
|
gameState.set_item_data(id, name, ignore, gold);
|
|
|
|
eventData[RequestItemData::P_AMOUNT] = (unsigned long long)amount;
|
|
eventData[RequestItemData::P_CREATOR] = creator;
|
|
eventData[RequestItemData::P_NAME] = gameState.get_item_name(id).c_str();
|
|
eventData[RequestItemData::P_PRIMARY_DESCRIPTION] = primary_description.c_str();
|
|
eventData[RequestItemData::P_SECONDARY_DESCRIPTION] = secondary_description.c_str();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleRequestScriptData(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (wallet)
|
|
{
|
|
const uint32_t id = eventData[RequestScriptData::P_ID].GetUInt();
|
|
std::string name, icon, desc, blob;
|
|
bool enabled;
|
|
if (wallet->get_cc_script(id, name, desc, icon, blob, enabled))
|
|
{
|
|
eventData[RequestScriptData::P_NAME] = name.c_str();
|
|
eventData[RequestScriptData::P_ICON] = icon.c_str();
|
|
eventData[RequestScriptData::P_DESC] = desc.c_str();
|
|
eventData[RequestScriptData::P_ENABLED] = enabled;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleRequestBadgeData(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (wallet)
|
|
{
|
|
const uint32_t id = eventData[RequestBadgeData::P_ID].GetUInt();
|
|
std::vector<cc::cc_badge_data_t> data;
|
|
std::vector<uint32_t> ids;
|
|
if (id != 0)
|
|
ids.push_back(id);
|
|
if (wallet->get_cc_badge_data(ids, data))
|
|
{
|
|
gameState.set_badge_name_and_desc(id, data[0].name, data[0].desc, data[0].icon);
|
|
|
|
eventData[RequestBadgeData::P_ID] = id;
|
|
eventData[RequestBadgeData::P_NAME] = data[0].name.c_str();
|
|
eventData[RequestBadgeData::P_DESCRIPTION] = data[0].desc.c_str();
|
|
eventData[RequestBadgeData::P_ICON] = data[0].icon.c_str();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleConsoleCommand(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
std::shared_ptr<tools::wallet2> w = wallet->wallet();
|
|
if (!w || wallet->is_spectator())
|
|
return;
|
|
|
|
String id = eventData[UIConsoleCommand::P_ID].GetString();
|
|
unsigned color = eventData[UIConsoleCommand::P_COLOR].GetUInt();
|
|
String line = eventData[UIConsoleCommand::P_COMMAND].GetString();
|
|
bool me = id == "Emote";
|
|
if (line.StartsWith("/me "))
|
|
{
|
|
line = line.SubstringUTF8(4);
|
|
me = true;
|
|
}
|
|
|
|
cryptonote::cc_command_chat_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.line = line.CString();
|
|
cmd.me = me;
|
|
cmd.color = color;
|
|
cmd.timestamp = time(NULL);
|
|
SendCommand(cmd);
|
|
|
|
wallet->set_attribute("CC/ChatColor", std::to_string(color));
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleConsoleSource(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
const uint32_t id = eventData[UIConsoleSource::P_SOURCE].GetUInt();
|
|
ui->ShowPlayerInfo(id);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleGetConsoleFilter(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
std::shared_ptr<tools::wallet2> w = wallet->wallet();
|
|
if (!w || wallet->is_spectator())
|
|
{
|
|
eventData[UIConsoleGetFilter::P_FILTER] = 0xffffffff;
|
|
return;
|
|
}
|
|
std::string s;
|
|
uint32_t filter;
|
|
|
|
if (wallet->get_attribute("CC/ConsoleFilter", s))
|
|
filter = atoi(s.c_str());
|
|
else
|
|
filter = 0xffffffff;
|
|
eventData[UIConsoleGetFilter::P_FILTER] = filter;
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleSetConsoleFilter(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
std::shared_ptr<tools::wallet2> w = wallet->wallet();
|
|
if (!w || wallet->is_spectator())
|
|
return;
|
|
const uint32_t filter = eventData[UIConsoleGetFilter::P_FILTER].GetUInt();
|
|
wallet->set_attribute("CC/ConsoleFilter", std::to_string(filter));
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleSetNodeConfig(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
const String address = eventData[SetNodeConfig::P_ADDRESS].GetString();
|
|
const uint16_t port = eventData[SetNodeConfig::P_PORT].GetUInt();
|
|
std::shared_ptr<tools::wallet2> w = wallet->wallet();
|
|
const bool success = !!w && wallet->set_daemon(address.CString(), port);
|
|
eventData[SetNodeConfig::P_SUCCESS] = success;
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleFoundCity(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to found a new city");
|
|
return;
|
|
}
|
|
|
|
cryptonote::cc_command_found_city_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.id = eventData[FoundCity::P_ID].GetUInt();
|
|
cmd.seed = eventData[FoundCity::P_SEED].GetUInt();
|
|
cmd.name = eventData[FoundCity::P_NAME].GetString().CString();
|
|
cmd.cost = eventData[FoundCity::P_COST].GetUInt64();
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::GoToCity(uint32_t city_id)
|
|
{
|
|
const bool reset_clouds = city_id != gameState.cityState.id;
|
|
gameState.cityState.SetCity(city_id);
|
|
if (wallet)
|
|
wallet->requestSnapshot(city_id);
|
|
startPosition_ = START_POSITION;
|
|
startRotation_ = START_ROTATION;
|
|
startPositionHugTerrain_ = true;
|
|
newCity_ = true;
|
|
|
|
// cheap way to reset clouds to give a feeling we've moved
|
|
if (reset_clouds && GetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_CLOUDS, DEFAULT_CLOUDS))
|
|
{
|
|
EnableClouds(false);
|
|
EnableClouds(true);
|
|
}
|
|
|
|
base_selection.clear();
|
|
selection.clear();
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleTravelToCity(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
|
|
const uint32_t city_id = eventData[TravelToCity::P_CITY].GetUInt();
|
|
GoToCity(city_id);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleGiveItems(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to give items");
|
|
return;
|
|
}
|
|
cryptonote::cc_command_give_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
if (!epee::string_tools::hex_to_pod(eventData[GiveItems::P_RECIPIENT].GetString().CString(), cmd.public_key))
|
|
{
|
|
new MessageBox(context_, "Invalid recipient public key");
|
|
return;
|
|
}
|
|
const std::vector<std::pair<uint32_t, uint32_t>> *v = (const std::vector<std::pair<uint32_t, uint32_t>>*)eventData[GiveItems::P_QUANTITIES].GetVoidPtr();
|
|
for (const auto &q: *v)
|
|
{
|
|
cryptonote::cc_command_give_t::item_t e;
|
|
e.type = q.first;
|
|
e.amount = q.second;
|
|
cmd.items.push_back(e);
|
|
}
|
|
cmd.flag = 0;
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleGiveMoney(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to give money");
|
|
return;
|
|
}
|
|
cryptonote::cc_command_transfer_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
if (!epee::string_tools::hex_to_pod(eventData[GiveMoney::P_RECIPIENT].GetString().CString(), cmd.public_key))
|
|
{
|
|
new MessageBox(context_, "Invalid recipient public key");
|
|
return;
|
|
}
|
|
const uint64_t amount = eventData[GiveMoney::P_AMOUNT].GetUInt64();
|
|
cmd.in_amount = amount;
|
|
cmd.out_amount = amount;
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleGiveFlag(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to give items");
|
|
return;
|
|
}
|
|
cryptonote::cc_command_give_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
if (!epee::string_tools::hex_to_pod(eventData[GiveFlag::P_RECIPIENT].GetString().CString(), cmd.public_key))
|
|
{
|
|
new MessageBox(context_, "Invalid recipient public key");
|
|
return;
|
|
}
|
|
cmd.flag = eventData[GiveFlag::P_FLAG].GetUInt();
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleNewItem(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to create a new item");
|
|
return;
|
|
}
|
|
cryptonote::cc_command_new_item_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.amount = eventData[NewItem::P_AMOUNT].GetUInt();
|
|
cmd.name = eventData[NewItem::P_NAME].GetString().CString();
|
|
cmd.is_group = eventData[NewItem::P_IS_GROUP].GetBool();
|
|
cmd.is_public = eventData[NewItem::P_IS_PUBLIC].GetBool();
|
|
cmd.group = eventData[NewItem::P_GROUP].GetUInt();
|
|
cmd.primary_description = eventData[NewItem::P_PRIMARY_DESCRIPTION].GetString().CString();
|
|
cmd.secondary_description = eventData[NewItem::P_SECONDARY_DESCRIPTION].GetString().CString();
|
|
cmd.gold = eventData[NewItem::P_GOLD].GetUInt64();
|
|
cmd.coin_design = eventData[NewItem::P_COIN_DESIGN].GetString().CString();
|
|
const String hash_string = eventData[NewItem::P_HASH].GetString();
|
|
if (hash_string.Empty())
|
|
cmd.hash = crypto::null_hash;
|
|
else if (!epee::string_tools::hex_to_pod(hash_string.CString(), cmd.hash))
|
|
{
|
|
new MessageBox(context_, "Invalid hash - unable to create a new item");
|
|
return;
|
|
}
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleDividend(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to create a dividend");
|
|
return;
|
|
}
|
|
cryptonote::cc_command_dividend_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.amount_to_distribute = eventData[Dividend::P_AMOUNT].GetUInt64();
|
|
cmd.owned_item = eventData[Dividend::P_OWNED_ITEM].GetUInt();
|
|
cmd.distributed_item = eventData[Dividend::P_DISTRIBUTED_ITEM].GetUInt();
|
|
cmd.note = eventData[Dividend::P_NOTE].GetString().CString();
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleNewMortgage(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to create a mortgage");
|
|
return;
|
|
}
|
|
std::shared_ptr<Flag> flag = map.get_flag(selection.x0, selection.y0);
|
|
if (!flag)
|
|
{
|
|
new MessageBox(context_, "Select a flag to use as security first");
|
|
return;
|
|
}
|
|
if (flag->owner != gameState.playerState.id)
|
|
{
|
|
new MessageBox(context_, "You do not own this flag");
|
|
return;
|
|
}
|
|
|
|
cryptonote::cc_command_create_mortgage_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.flag = flag->id;
|
|
cmd.item = eventData[NewMortgage::P_ITEM].GetUInt();
|
|
cmd.tick_payment = eventData[NewMortgage::P_TICK_PAYMENT].GetUInt64();
|
|
cmd.maturity_payment = eventData[NewMortgage::P_MATURITY_PAYMENT].GetUInt64();
|
|
cmd.num_ticks_delay = eventData[NewMortgage::P_NUM_TICKS_DELAY].GetUInt();
|
|
cmd.num_tick_payments = eventData[NewMortgage::P_NUM_TICK_PAYMENTS].GetUInt();
|
|
cmd.shares = eventData[NewMortgage::P_SHARES].GetUInt();
|
|
cmd.name = eventData[NewMortgage::P_NAME].GetString().CString();
|
|
cmd.description = eventData[NewMortgage::P_DESCRIPTION].GetString().CString();
|
|
SendCommand(cmd);
|
|
ui->AddToastNotification("Once the new mortgage is mined, you can start selling the shares to raise money");
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleEventBadge(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to create a new event badge");
|
|
return;
|
|
}
|
|
cryptonote::cc_command_event_badge_t cmd;
|
|
cmd.cc_account = wallet->get_cc_account();
|
|
cmd.badge_id = eventData[EventBadge::P_BADGE_ID].GetUInt();
|
|
cmd.name = eventData[EventBadge::P_NAME].GetString().CString();
|
|
cmd.design = eventData[EventBadge::P_DESIGN].GetString().CString();
|
|
cmd.description = eventData[EventBadge::P_DESCRIPTION].GetString().CString();
|
|
const std::vector<std::pair<uint32_t, uint8_t>> *v = (const std::vector<std::pair<uint32_t, uint8_t>>*)eventData[EventBadge::P_INSTANCES].GetVoidPtr();
|
|
for (const auto &e: *v)
|
|
{
|
|
std::string name, description;
|
|
std::string public_key, pmspk, pmvpk;
|
|
uint64_t balance;
|
|
std::map<uint32_t, uint32_t> item_balances;
|
|
std::vector<uint32_t> flags;
|
|
std::map<uint32_t, std::pair<uint8_t, uint64_t>> badges;
|
|
std::map<uint32_t, uint32_t> attributes;
|
|
bool ignore;
|
|
uint32_t inviting_account;
|
|
uint32_t num_invited;
|
|
uint32_t first_flag;
|
|
uint32_t script, script_state, script_city, script_owner;
|
|
std::map<uint32_t, std::pair<uint64_t, std::map<uint32_t, uint32_t>>> reserve;
|
|
std::map<std::string, uint64_t> script_local_variables;
|
|
uint32_t prestige;
|
|
if (!wallet->get_cc_account_data(e.first, public_key, balance, item_balances, flags, badges, attributes, name, description, ignore, inviting_account, num_invited, first_flag, script, script_state, script_city, script_owner, reserve, script_local_variables, prestige, pmspk, pmvpk))
|
|
{
|
|
new MessageBox(context_, "Failed to get account data for recipient");
|
|
return;
|
|
}
|
|
const auto it = badges.find(cmd.badge_id);
|
|
const uint8_t level = it == badges.end() ? 0 : it->second.first;
|
|
const uint64_t timestamp = it == badges.end() ? 0 : it->second.second;
|
|
cmd.instances.push_back({e.first, level, e.second, timestamp});
|
|
}
|
|
if (cmd.badge_id == 0 || !cmd.instances.empty())
|
|
SendCommand(cmd);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleGetIgnoreSettings(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to get ignore settings");
|
|
return;
|
|
}
|
|
tools::cc_ignore_t *ignore = (tools::cc_ignore_t*)eventData[GetIgnoreSettings::P_IGNORE].GetVoidPtr();
|
|
const bool master = eventData[GetIgnoreSettings::P_MASTER].GetBool();
|
|
if (master)
|
|
{
|
|
gameState.get_ignore_recommendations(ignore);
|
|
}
|
|
else
|
|
{
|
|
wallet->get_ignore_settings(*ignore);
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleSetIgnoreSettings(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
if (!wallet || !wallet->wallet())
|
|
{
|
|
new MessageBox(context_, "No wallet loaded - load a wallet to be able to set ignore settings");
|
|
return;
|
|
}
|
|
|
|
std::vector<uint32_t> ids;
|
|
std::set<std::shared_ptr<Flag>> res;
|
|
|
|
wallet->get_ignored_flag_ids(ids);
|
|
for (uint32_t id: ids)
|
|
if (auto f = map.get_flag(id))
|
|
res.insert(f);
|
|
|
|
const tools::cc_ignore_t *ignore = (const tools::cc_ignore_t*)eventData[SetIgnoreSettings::P_IGNORE].GetVoidPtr();
|
|
const bool master = eventData[SetIgnoreSettings::P_MASTER].GetBool();
|
|
|
|
if (master)
|
|
{
|
|
cryptonote::cc_command_ignore_t ignore_players, unignore_players;
|
|
cryptonote::cc_command_ignore_t ignore_flags, unignore_flags;
|
|
cryptonote::cc_command_ignore_t ignore_items, unignore_items;
|
|
cryptonote::cc_command_ignore_t ignore_cities, unignore_cities;
|
|
ignore_players.cc_account = ignore_flags.cc_account = ignore_items.cc_account = ignore_cities.cc_account = wallet->get_cc_account();
|
|
ignore_players.ignore = ignore_flags.ignore = ignore_items.ignore = ignore_cities.ignore = true;
|
|
unignore_players.cc_account = unignore_flags.cc_account = unignore_items.cc_account = unignore_cities.cc_account = wallet->get_cc_account();
|
|
unignore_players.ignore = unignore_flags.ignore = unignore_items.ignore = unignore_cities.ignore = false;
|
|
ignore_players.type = unignore_players.type = cryptonote::cc_command_ignore_t::type_account;
|
|
ignore_flags.type = unignore_flags.type = cryptonote::cc_command_ignore_t::type_flag;
|
|
ignore_items.type = unignore_items.type = cryptonote::cc_command_ignore_t::type_item;
|
|
ignore_cities.type = unignore_cities.type = cryptonote::cc_command_ignore_t::type_city;
|
|
ignore_players.note = unignore_players.note = ignore_flags.note = unignore_flags.note = ignore_items.note = unignore_items.note = ignore_cities.note = unignore_cities.note = eventData[SetIgnoreSettings::P_NOTE].GetString().CString();
|
|
gameState.set_ignore_recommendations(ignore, ignore_players.id, unignore_players.id, ignore_flags.id, unignore_flags.id, ignore_items.id, unignore_items.id, ignore_cities.id, unignore_cities.id);
|
|
if (!ignore_players.id.empty()) SendCommand(ignore_players);
|
|
if (!unignore_players.id.empty()) SendCommand(unignore_players);
|
|
if (!ignore_flags.id.empty()) SendCommand(ignore_flags);
|
|
if (!unignore_flags.id.empty()) SendCommand(unignore_flags);
|
|
if (!ignore_items.id.empty()) SendCommand(ignore_items);
|
|
if (!unignore_items.id.empty()) SendCommand(unignore_items);
|
|
if (!ignore_cities.id.empty()) SendCommand(ignore_cities);
|
|
if (!unignore_cities.id.empty()) SendCommand(unignore_cities);
|
|
}
|
|
else
|
|
{
|
|
wallet->set_ignore_settings(*ignore);
|
|
gameState.set_ignore(*ignore);
|
|
|
|
wallet->get_ignored_flag_ids(ids);
|
|
for (uint32_t id: ids)
|
|
if (auto f = map.get_flag(id))
|
|
gameState.add_dirty_flag(f, false);
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleCameraType(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
const TBID type = eventData[CameraType::P_TYPE].GetUInt64();
|
|
if (type == TBIDC("free"))
|
|
camera_ = new FreeCamera(cameraNode_);
|
|
else if (type == TBIDC("walker"))
|
|
camera_ = new WalkerCamera(cameraNode_);
|
|
else if (type == TBIDC("orbit"))
|
|
camera_ = new OrbitCamera(cameraNode_);
|
|
else
|
|
printf("Invalid camera type\n");
|
|
camera_->set_collidable_root(cityMesh->GetSelectableNode());
|
|
ui->SetCameraType(type);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleGroundType(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UnsetFocus();
|
|
const TBID type = eventData[GroundType::P_TYPE].GetUInt64();
|
|
GroundMode mode = GroundNatural;
|
|
if (type == TBIDC("natural"))
|
|
mode = GroundNatural;
|
|
else if (type == TBIDC("stability"))
|
|
mode = GroundStability;
|
|
else if (type == TBIDC("agricultural"))
|
|
mode = GroundAgricultural;
|
|
else if (type == TBIDC("geothermal"))
|
|
mode = GroundGeothermalHeating;
|
|
else if (type == TBIDC("gemstone"))
|
|
mode = GroundGemstone;
|
|
else if (type == TBIDC("wood-type"))
|
|
mode = GroundWoodType;
|
|
else if (type == TBIDC("wood-quantity"))
|
|
mode = GroundWoodQuantity;
|
|
else if (type == TBIDC("stone-type"))
|
|
mode = GroundStoneType;
|
|
else if (type == TBIDC("stone-quantity"))
|
|
mode = GroundStoneQuantity;
|
|
else if (type == TBIDC("cliff"))
|
|
mode = GroundCliff;
|
|
else if (type == TBIDC("neutral"))
|
|
mode = GroundNeutral;
|
|
else
|
|
printf("Invalid ground type\n");
|
|
const bool add = eventData[GroundType::P_ADD].GetBool();
|
|
cityMesh->setGroundMode(mode, add);
|
|
}
|
|
|
|
void CryptoCityUrho3D::HandleResized(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
const int width = eventData[Resized::P_WIDTH].GetInt();
|
|
const int height = eventData[Resized::P_HEIGHT].GetInt();
|
|
|
|
g_renderer->InvokeContextLost(); // Forget all bitmaps
|
|
g_renderer->InvokeContextRestored(); // Reload all bitmaps
|
|
|
|
UTBRendererBatcher::Singleton().Root().Invalidate();
|
|
|
|
RenderPath *effectRenderPath = viewport->GetRenderPath();
|
|
if (effectRenderPath)
|
|
effectRenderPath->SetShaderParameter("BlurHInvSize", Vector2(1.0f/(float)width, 1.0f/(float)height));
|
|
console->UpdateElements();
|
|
|
|
auto* graphics = GetSubsystem<Graphics>();
|
|
char s[256];
|
|
int monitor = graphics->GetFullscreen() ? graphics->GetCurrentMonitor() : -1;
|
|
int refresh_rate = graphics->GetFullscreen() ? graphics->GetRefreshRate() : -1;
|
|
snprintf(s, sizeof(s), "%d:%dx%d@%d", monitor, width, height, refresh_rate);
|
|
SetConfigValue(CONFIG_GRAPHICS_SECTION, CONFIG_GRAPHICS_MODE, String(s));
|
|
}
|
|
|
|
void CryptoCityUrho3D::SaveBackup()
|
|
{
|
|
if (!flagUnderConstruction)
|
|
return;
|
|
const uint32_t id = std::get<0>(*flagUnderConstruction);
|
|
const std::shared_ptr<Flag> flag = map.get_flag(id);
|
|
if (!flag)
|
|
return;
|
|
|
|
std::string data;
|
|
if (!SaveModelData(flag, data, false, true))
|
|
{
|
|
new MessageBox(context_, "Failed to save backup");
|
|
return;
|
|
}
|
|
|
|
data = "Townforge backup\nFlag " + std::to_string(flag->id) + "\n" + data;
|
|
|
|
if (!epee::file_io_utils::save_string_to_file(building_backup_filename.CString(), data))
|
|
{
|
|
new MessageBox(context_, "Failed to save model");
|
|
return;
|
|
}
|
|
}
|
|
|
|
void CryptoCityUrho3D::RemoveBackup()
|
|
{
|
|
boost::system::error_code ec{};
|
|
boost::filesystem::remove(building_backup_filename.CString(), ec);
|
|
}
|
|
|
|
void CryptoCityUrho3D::CheckModelBackup()
|
|
{
|
|
building_backup_filename = config_filename.Replaced("/config.json", String("/realtime-backup-%A-%C.vox").Replaced("%A", String(gameState.playerState.id)).Replaced("%C", String(gameState.cityState.id)));
|
|
if (epee::file_io_utils::is_file_exist(building_backup_filename.CString()))
|
|
ui->OnModelBackupAvailable();
|
|
|
|
}
|
|
|
|
String CryptoCityUrho3D::GetScreenJoystickPatchString() const
|
|
{
|
|
return
|
|
"<patch>"
|
|
" <add sel=\"/element/element[./attribute[@name='Name' and @value='Hat0']]\">"
|
|
" <attribute name=\"Is Visible\" value=\"false\" />"
|
|
" </add>"
|
|
"</patch>";
|
|
}
|
|
|
|
URHO3D_DEFINE_APPLICATION_MAIN(CryptoCityUrho3D)
|