townforge/src/game/main-urho3d.cc

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 &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)