├── lib └── linux32 │ ├── libssl.a │ └── libcrypto.a ├── README.md ├── falconserver ├── slot │ ├── slot_data_types.nim │ ├── machine_candy2_types.nim │ ├── machine_candy_types.nim │ ├── machine_base_server.nim │ ├── machine_card_types.nim │ ├── machine_witch_types.nim │ ├── near_misses_pick.nim │ ├── machine_classic_server.nim │ ├── machine_candy2_server.nim │ ├── machine_mermaid_server.nim │ ├── near_misses.nim │ ├── machine_ufo_types.nim │ ├── machine_candy_server.nim │ ├── machine_card_server.nim │ ├── machine_ufo_server.nim │ ├── machine_witch_server.nim │ ├── machine_groovy_server.nim │ ├── machine_ufo_bonus.nim │ └── machine_balloon_server.nim ├── common │ ├── currency.nim │ ├── staging.nim │ ├── ab_constants.nim │ ├── object_id.nim │ ├── checks.nim │ ├── notifications_routes.nim │ ├── response_wrapper.nim │ ├── currency_exchange.nim │ ├── message.nim │ ├── response.nim │ ├── db.nim │ ├── stats.nim │ ├── bson_helper.nim │ └── get_balance_config.nim ├── routes │ ├── decision_maker.nim │ └── push_token_update_routes.nim ├── auth │ ├── profile_stats.nim │ ├── gameplay_config_decl.nim │ ├── profile_test.nim │ ├── gameplay_config.nim │ ├── profile_vip_helpers.nim │ ├── profile_helpers.nim │ ├── profile_types.nim │ ├── session.nim │ └── profile_random.nim ├── quest │ ├── quest_task_decl.nim │ ├── quest_decl.nim │ ├── quest_config_decl.nim │ ├── quest.nim │ ├── generator.nim │ ├── quest_task.nim │ └── quest_types.nim ├── admin │ ├── admin_permissions.nim │ ├── admin_profile.nim │ └── slot_zsm_test.nim ├── map │ ├── building │ │ ├── jsonbuilding.nim │ │ └── builditem.nim │ ├── map.nim │ └── collect.nim ├── features │ └── features.nim ├── maintenance.nim ├── tutorial │ ├── tutorial_server.nim │ └── tutorial_types.nim ├── free_rounds │ ├── free_rounds_routes.nim │ └── free_rounds.nim ├── boosters │ ├── boosters_routes.nim │ └── boosters.nim ├── nakefile.nim ├── tests │ ├── machine_candy_math_test.nim │ ├── machine_candy2_math_test.nim │ ├── machine_classic_math_test.nim │ ├── tests.nim │ ├── machine_classic_creation_test.nim │ └── machine_groovy_math_test.nim ├── tournament │ ├── rewards.nim │ ├── tournament_routes.nim │ └── tournaments_data.nim ├── fortune_wheel │ ├── fortune_wheel_routes.nim │ └── fortune_wheel.nim ├── cheats │ ├── cheats_config.json │ └── cardSlot │ │ └── cheats.json └── utils │ └── asyncspawn.nim ├── .gitignore ├── falconserver.nimble ├── unittests.nim ├── LICENSE └── integrational_tests.nim /lib/linux32/libssl.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OnsetGame/falconserver/HEAD/lib/linux32/libssl.a -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FalconServer 2 | ============ 3 | 4 | Server-side component for the 'falcon' game project. 5 | -------------------------------------------------------------------------------- /lib/linux32/libcrypto.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OnsetGame/falconserver/HEAD/lib/linux32/libcrypto.a -------------------------------------------------------------------------------- /falconserver/slot/slot_data_types.nim: -------------------------------------------------------------------------------- 1 | {.deprecated.} # Use shafa 2 | import shafa.slot.slot_data_types 3 | export slot_data_types 4 | -------------------------------------------------------------------------------- /falconserver/common/currency.nim: -------------------------------------------------------------------------------- 1 | type 2 | Currency* {.pure.} = enum 3 | Unknown = "unknown" 4 | Chips = "chips" 5 | Bucks = "bucks" 6 | Parts = "parts" 7 | TournamentPoint = "tp" 8 | -------------------------------------------------------------------------------- /falconserver/common/staging.nim: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | var isStage* = false 4 | if getEnv("FALCON_ENV") == "stage": 5 | isStage = true 6 | 7 | const stageApiUrl* = "https://stage-api.onsetgame.com" 8 | const prodApiUrl* = "https://game-api.onsetgame.com" 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries 2 | main 3 | nakefile 4 | nimcache 5 | machine_classic 6 | machine_balloon 7 | machine_classic_test 8 | integrational_tests 9 | profile_test 10 | unittests 11 | *.so 12 | *.exe 13 | 14 | # Documentation-related 15 | nimsuggest.log 16 | -------------------------------------------------------------------------------- /falconserver/common/ab_constants.nim: -------------------------------------------------------------------------------- 1 | 2 | const abStartingChips* = "startingChips" 3 | const abStartingBucks* = "startingBucks" 4 | const abGB* = "gameBalance" 5 | const abStoryQuest* = "storyQuest" 6 | const abDailyQuest* = "dailyQuest" 7 | 8 | proc abDefaultSlotZsmName*(slotName: string): string = slotName & "_Default" 9 | -------------------------------------------------------------------------------- /falconserver/routes/decision_maker.nim: -------------------------------------------------------------------------------- 1 | import falconserver.nester 2 | import falconserver / common / [ response_wrapper, config, response ] 3 | import falconserver / auth / [ profile, session ] 4 | import json, logging, tables 5 | 6 | sharedRouter().routes: 7 | sessionPost "/decisionPoint": 8 | respJson Http200, json.`%*`({"status":StatusCode.OK.int, "reason": "decisionPoint is empty"}) 9 | return 10 | -------------------------------------------------------------------------------- /falconserver.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | version = "0.2" 3 | author = "OnsetGame" 4 | description = "Falcon slots game server" 5 | license = "MIT" 6 | 7 | requires "https://github.com/yglukhov/nest" 8 | requires "nake" 9 | requires "nuuid" 10 | requires "nimongo" 11 | requires "https://github.com/onionhammer/nim-templates" 12 | requires "https://github.com/Tormund/boolseq" 13 | requires "jwt" 14 | requires "nimSHA2" 15 | requires "hmac" 16 | -------------------------------------------------------------------------------- /falconserver/common/object_id.nim: -------------------------------------------------------------------------------- 1 | # This is a wrapper for Oids that is compatible with JS target 2 | when defined(js): 3 | type ObjectId* = string 4 | template toString*(o: ObjectId): string = o 5 | template toObjectId*(s: string): ObjectId = s 6 | else: 7 | import oids 8 | type ObjectId* = Oid 9 | template toString*(o: ObjectId): string = $o 10 | template toObjectId*(s: string): ObjectId = parseOid(s) 11 | -------------------------------------------------------------------------------- /unittests.nim: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | #import falconserver.auth.profile_test 4 | import falconserver.tests.machine_classic_creation_test 5 | import falconserver.tests.machine_classic_math_test 6 | import falconserver.tests.machine_candy_math_test 7 | import falconserver.tests.machine_candy2_math_test 8 | import falconserver.tests.machine_groovy_math_test 9 | import falconserver.tests.quest_system_test 10 | #import falconserver.map.maptest 11 | -------------------------------------------------------------------------------- /falconserver/common/checks.nim: -------------------------------------------------------------------------------- 1 | 2 | const protocolVersion* = 4 3 | 4 | template serverOnly*(body: untyped): untyped = 5 | when defined(falconServer): 6 | body 7 | 8 | template clientOnly*(body: untyped): untyped = 9 | when not defined(falconServer): 10 | body 11 | 12 | proc isServerCompatibleWithClientProtocolVersion*(clientProtocolVersion: int): bool = 13 | if clientProtocolVersion <= protocolVersion: return true 14 | -------------------------------------------------------------------------------- /falconserver/slot/machine_candy2_types.nim: -------------------------------------------------------------------------------- 1 | import machine_base_types 2 | export machine_base_types 3 | 4 | const NUMBER_OF_REELS* = 5 5 | const ELEMENTS_COUNT* = 15 6 | const LINE_COUNT* = 20 7 | const WILD* = 0'i8 8 | const SCATTER* = 1'i8 9 | const BONUS* = 2'i8 10 | const BONUS_MIN_SYMBOLS* = 3 11 | 12 | type SpinResult* = ref object of RootObj 13 | stage*: Stage 14 | field*: seq[int8] 15 | lines*: seq[WinningLine] 16 | freeSpinsCount*: int 17 | 18 | 19 | -------------------------------------------------------------------------------- /falconserver/auth/profile_stats.nim: -------------------------------------------------------------------------------- 1 | import json 2 | import nimongo.bson 3 | import nimongo.mongo 4 | import falconserver.common.orm 5 | 6 | TransparentObj ProfileStats: 7 | realMoneySpent: int64("ms", "realMoneySpent") 8 | spinsOnSlots: Bson("sp", "spinsOnSlots") 9 | 10 | scenesLoaded: Bson("sl", "scenesLoaded") 11 | 12 | questsCompleted: Bson("qc", "questsCompleted") 13 | 14 | lastRequestTime: float("rt", "lastRequestTime") 15 | registrationTime: float("prt", "registrationTime") 16 | -------------------------------------------------------------------------------- /falconserver/quest/quest_task_decl.nim: -------------------------------------------------------------------------------- 1 | import falconserver.map.building.builditem 2 | export builditem 3 | 4 | import quest_types 5 | export quest_types 6 | 7 | 8 | type 9 | TaskProgress* = ref object 10 | current*: uint64 11 | total*: uint64 12 | index*: uint 13 | 14 | QuestTask* = ref object of RootObj 15 | kind*: QuestTaskType 16 | target*: BuildingId 17 | difficulty*: DailyDifficultyType 18 | progresses*: seq[TaskProgress] 19 | prevStage*: string 20 | progressState*: QuestTaskProgress 21 | -------------------------------------------------------------------------------- /falconserver/slot/machine_candy_types.nim: -------------------------------------------------------------------------------- 1 | import machine_base_types 2 | export machine_base_types 3 | 4 | 5 | ## Candy Slot Machine Model implementation 6 | const ReelCountCandy* = 5 7 | const CANDY_MAX_SCATTERS* = 5 8 | const LINE_COUNT* = 20 9 | ## Number of slot machine reels (columns on field) 10 | 11 | type BonusDishes* {.pure.} = enum 12 | Icecream, 13 | Candy, 14 | Cake, 15 | 16 | type SpinResult* = ref object of RootObj 17 | stage*: Stage 18 | field*: seq[int8] 19 | lines*: seq[WinningLine] 20 | dishesValue*: seq[int64] 21 | wildIndexes*: seq[int] 22 | -------------------------------------------------------------------------------- /falconserver/admin/admin_permissions.nim: -------------------------------------------------------------------------------- 1 | import nimongo.bson 2 | import falconserver.admin.admin_profile 3 | 4 | type APermission* = enum 5 | apDbView 6 | apDbModify 7 | apGrantPermissions 8 | 9 | proc checkPermission*(b: Bson, feature: APermission): bool = 10 | let accLevel = b[$apfAccountLevel].toInt().AdminAccountLevel 11 | case feature: 12 | of apDbView: 13 | result = accLevel > AdminAccountLevel.None 14 | of apDbModify: 15 | result = accLevel > AdminAccountLevel.View 16 | of apGrantPermissions: 17 | result = accLevel > AdminAccountLevel.Modify 18 | 19 | -------------------------------------------------------------------------------- /falconserver/auth/gameplay_config_decl.nim: -------------------------------------------------------------------------------- 1 | import json 2 | export json 3 | 4 | import falconserver.common.game_balance 5 | export game_balance 6 | 7 | import falconserver.quest.quest_config_decl 8 | export quest_config_decl 9 | 10 | import falconserver.slot.near_misses 11 | export near_misses 12 | 13 | 14 | type GameplayConfig* = ref object of RootObj 15 | gameBalance*: GameBalance 16 | storyConfig*: StoryQuestConfig 17 | dailyConfig*: DailyGeneratorConfig 18 | offersConfig*: JsonNode 19 | clientConfig*: JsonNode 20 | predefinedSpinsData*: JsonNode 21 | nearMissesConfig*: NearMissConfig 22 | -------------------------------------------------------------------------------- /falconserver/common/notifications_routes.nim: -------------------------------------------------------------------------------- 1 | import json, logging, oids, sequtils, strutils, tables, times, asyncdispatch 2 | import os except DeviceId 3 | 4 | import nimongo / [ bson, mongo ] 5 | 6 | import falconserver / [ schedule, nester ] 7 | import falconserver / auth / [ profile_types, profile ] 8 | import falconserver / common / [ db, bson_helper, stats, config, staging ] 9 | 10 | import notifications 11 | export notifications 12 | 13 | 14 | # Debugging: 15 | if isStage: 16 | sharedRouter().routes: 17 | get "/sendDebugPush": 18 | ## Test purpose only 19 | resp Http400, "Bad request" 20 | -------------------------------------------------------------------------------- /falconserver/common/response_wrapper.nim: -------------------------------------------------------------------------------- 1 | import httpcore, json, asyncdispatch, times, macros 2 | import falconserver / features / features 3 | import falconserver / common / [config, response] 4 | import falconserver / auth / profile 5 | export features 6 | 7 | 8 | proc wrapRespJson*(profile: Profile, content: JsonNode, clientVersion: string) {.async.} = 9 | if clientVersion != NO_MAINTENANCE_CLIENT_VERSION: 10 | content["maintenanceTime"] = %sharedGameConfig().maintenanceTime 11 | 12 | if not profile.isNil: 13 | await content.updateWithWheelFreeSpin(profile) 14 | await content.updateWithDiscountedExchange(profile) 15 | -------------------------------------------------------------------------------- /falconserver/map/building/jsonbuilding.nim: -------------------------------------------------------------------------------- 1 | import builditem 2 | import json 3 | #import oids 4 | 5 | proc toJson*(bi: BuildItem): JsonNode = 6 | result = newJObject() 7 | result["i"] = % bi.id.int 8 | result["l"] = %bi.level 9 | result["n"] = %bi.name 10 | # if bi.kind == res: 11 | result["t"] = %bi.lastCollectionTime 12 | 13 | proc toBuildItem*(b : JsonNode): BuildItem = 14 | let id = BuildingId(b["i"].num) 15 | result = newBuildItem(id) 16 | result.level = int(b["l"].num) 17 | 18 | if "n" in b: 19 | result.name = b["n"].getStr() 20 | 21 | if "t" in b: 22 | result.lastCollectionTime = b["t"].getFloat() 23 | -------------------------------------------------------------------------------- /falconserver/features/features.nim: -------------------------------------------------------------------------------- 1 | import times, json, asyncdispatch 2 | import falconserver / auth / [profile_random, session] 3 | import falconserver / common / [game_balance, get_balance_config, bson_helper] 4 | 5 | 6 | proc updateWithWheelFreeSpin*(resp: JsonNode, p: Profile) {.async.} = 7 | resp["prevFreeSpin"] = %p.fortuneWheelState.lastFreeTime 8 | resp["freeSpinTimeout"] = %p.getGameBalance().fortuneWheel.freeSpinTimeout 9 | resp["freeSpinsLeft"] = %p.fortuneWheelState.freeSpinsLeft 10 | 11 | 12 | proc updateWithDiscountedExchange*(resp: JsonNode, p: Profile) {.async.} = 13 | resp["nextDiscountedExchange"] = %p.nextExchangeDiscountTime 14 | resp["exchangeChips"] = p{$prfExchangeNum, $prfChips}.toJson() 15 | -------------------------------------------------------------------------------- /falconserver/slot/machine_base_server.nim: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import nimongo.bson 4 | 5 | import machine_base 6 | import falconserver.auth.profile 7 | 8 | import slot_data_types 9 | 10 | export machine_base 11 | 12 | method getResponseAndState*(sm: SlotMachine, profile: Profile, prevState: Bson, jData: JsonNode, resp: var JsonNode, slotState: var Bson) {.base, gcsafe.} = discard 13 | 14 | proc calcPayout*(stageResp: JsonNode): int64 = 15 | let stagePay = stageResp{$srtPayout} 16 | if not stagePay.isNil: 17 | result += stagePay.getBiggestInt() 18 | 19 | let lines = stageResp{$srtLines} 20 | if not lines.isNil: 21 | for l in lines: 22 | let linePay = l{$srtPayout} 23 | if not linePay.isNil: 24 | result += linePay.getBiggestInt() 25 | -------------------------------------------------------------------------------- /falconserver/quest/quest_decl.nim: -------------------------------------------------------------------------------- 1 | import quest_task_decl 2 | export quest_task_decl 3 | 4 | import quest_types 5 | export quest_types 6 | 7 | import nimongo.bson 8 | 9 | 10 | type 11 | Quest* = ref object of RootObj 12 | ## Quest - a task for a player to fulfill in order to get 13 | ## some `Reward`. 14 | data*: Bson 15 | tasks*: seq[QuestTask] 16 | # rewards*: seq[QuestReward] ## todo: move rewards into task 17 | kind*: QuestKind 18 | 19 | 20 | proc id*(quest: Quest): int = 21 | result = quest.data[$qfId].toInt() 22 | 23 | 24 | proc isQuestActive*(quest: Quest): bool = 25 | let qProg = quest.data[$qfStatus].toInt().QuestProgress 26 | result = qProg == QuestProgress.InProgress or 27 | (quest.kind == QuestKind.LevelUp ) #and (qProg != QuestProgress.GoalAchieved or qProg != QuestProgress.Completed) 28 | -------------------------------------------------------------------------------- /falconserver/admin/admin_profile.nim: -------------------------------------------------------------------------------- 1 | import oids 2 | import nimongo.bson 3 | 4 | const MongoCollectionAdmins*: string = "admins" 5 | const FindQueriesCollection*: string = "adminsFindQueries" 6 | 7 | type AdminAccountLevel* {.pure.} = enum 8 | None = "n" # new admin user's, waiting developers aproove 9 | View = "v" # view server stats 10 | Modify = "m" # modify game balanse 11 | Full = "f" # full access 12 | 13 | type AdminProfileFields* = enum 14 | apfMail = "m" 15 | apfSalt = "s" 16 | apfSecret = "q" 17 | apfAccountLevel = "l" 18 | apfRequestId = "r" 19 | 20 | proc newAdminProfile*(mail: string, sec: string): Bson = 21 | result = bson.`%*`({ 22 | $apfMail: mail, 23 | $apfSecret: sec, 24 | $apfSalt: "", 25 | $apfRequestId: "", 26 | $apfAccountLevel: AdminAccountLevel.None.int 27 | }) 28 | 29 | 30 | -------------------------------------------------------------------------------- /falconserver/maintenance.nim: -------------------------------------------------------------------------------- 1 | 2 | import json, logging 3 | 4 | import oids 5 | import random 6 | import sequtils 7 | import strutils 8 | import tables 9 | import times 10 | import os except DeviceId 11 | import nuuid 12 | 13 | import nimongo.bson except `()` 14 | import nimongo.mongo 15 | 16 | import falconserver.auth.profile_types 17 | 18 | import falconserver.slot.machine_base_server 19 | import falconserver.slot.machine_balloon_server 20 | import falconserver.slot.machine_classic_server 21 | import falconserver.slot.machine_candy_server 22 | import falconserver.slot.machine_ufo_server 23 | import falconserver.slot.machine_witch_server 24 | import falconserver.slot.machine_mermaid_server 25 | import falconserver.slot.machine_candy_server 26 | 27 | import falconserver.tournament.tournaments 28 | import falconserver.tournament.fake_participation 29 | 30 | import asyncdispatch 31 | 32 | #todo: totally remove this module 33 | -------------------------------------------------------------------------------- /falconserver/slot/machine_card_types.nim: -------------------------------------------------------------------------------- 1 | import machine_base_types 2 | export machine_base_types 3 | 4 | const NUMBER_OF_REELS* = 5 5 | const ELEMENTS_COUNT* = 15 6 | const LINE_COUNT* = 20 7 | 8 | type FreespinsType* {.pure.} = enum 9 | NoFreespin, 10 | Hidden, 11 | Multiplier, 12 | Shuffle 13 | 14 | type SpinResult* = ref object of RootObj 15 | stage*: Stage 16 | field*: seq[int8] 17 | lines*: seq[WinningLine] 18 | 19 | type SlotMachineCard* = ref object of SlotMachine 20 | wildsFeatureChances*: seq[tuple[multiplier: int, chance: float]] 21 | freespinChance*: float 22 | freespins*: int 23 | freegameMultiplierChances*: seq[tuple[multiplier: int, chance: float]] 24 | hiddenIndexes*: seq[int] 25 | hiddenChances*: seq[tuple[id:int, chance:float]] 26 | linesMultiplier*: int 27 | winFreespins*: bool 28 | freeSpinsCount*: int 29 | fsType*: FreespinsType 30 | reelsHidden*: Layout 31 | reelsMultiplier*: Layout 32 | reelsShuffle*: Layout 33 | maxPayout*: int64 34 | permutedField*: seq[int8] 35 | permutedLines*: seq[WinningLine] -------------------------------------------------------------------------------- /falconserver/auth/profile_test.nim: -------------------------------------------------------------------------------- 1 | import json 2 | import oids 3 | import unittest 4 | 5 | import nimongo.bson 6 | 7 | import falconserver.auth.profile 8 | import falconserver.auth.profile_types 9 | 10 | suite "Testing user's profile": 11 | 12 | test "Profile constructor": 13 | let p = newProfile() 14 | check: 15 | p["_id"] != Oid() 16 | p.bucks == INITIAL_BUCKS 17 | p.chips == INITIAL_CHIPS 18 | p.portrait == ppNotSet.int 19 | p.frame == pfFrame0.int 20 | 21 | test "Profile to Json (default)": 22 | let 23 | p = newProfile() 24 | j = p.toJsonProfile() 25 | check: 26 | j[$prfBucks].getNum() == p.bucks 27 | j[$prfChips].getNum() == p.chips 28 | j[$prfPortrait].getNum() == ppNotSet.int 29 | j[$prfFrame].getNum() == pfFrame0.int 30 | 31 | test "Profile to Json (full)": 32 | let 33 | p = newProfile() 34 | j = p.toJsonProfile(full = true) 35 | check: 36 | j.hasKey($prfDevices) 37 | j.hasKey($prfCheats) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-Present Onsetgame Limited 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /falconserver/tutorial/tutorial_server.nim: -------------------------------------------------------------------------------- 1 | import boolseq 2 | import json except `()` 3 | 4 | import nimongo.bson 5 | 6 | import falconserver.auth.profile_types 7 | import falconserver.auth.profile 8 | import falconserver.common.bson_helper 9 | import falconserver.common.response 10 | import falconserver.tutorial.tutorial_types 11 | 12 | export tutorial_types 13 | 14 | proc completeTutorialState*(p: Profile, tutorialId: TutorialState): Bson= 15 | var tutorialBoolSeq = newBoolSeq(p.tutorialState.binstr()) 16 | if tutorialBoolSeq.len <= tutorialId.int: 17 | tutorialBoolSeq.setLen(tutorialId.int + 1) 18 | 19 | tutorialBoolSeq[tutorialId.int] = true 20 | p.tutorialState = binuser(tutorialBoolSeq.string) 21 | 22 | result = p.tutorialState 23 | 24 | #echo "save tutorial state ", tutorialId, " upd ", result 25 | 26 | proc tutorialStateForClient*(p: Profile): JsonNode= 27 | var tutorialBoolSeq = newBoolSeq(p.tutorialState.binstr()) 28 | result = newJObject() 29 | for ts in low(TutorialState) .. high(TutorialState): 30 | if ts.int < 0: continue 31 | 32 | if ts.int < tutorialBoolSeq.len: 33 | result[$ts] = %tutorialBoolSeq[ts.int] 34 | else: 35 | result[$ts] = %false 36 | -------------------------------------------------------------------------------- /falconserver/routes/push_token_update_routes.nim: -------------------------------------------------------------------------------- 1 | import json, asyncdispatch, httpcore, oids 2 | 3 | import falconserver.nester 4 | import falconserver / auth / [ session, profile ] 5 | 6 | sharedRouter().routes: 7 | post "/updatePushTok": 8 | var body = parseJson(request.body) 9 | let profileId = body{"pid"}.getStr() 10 | let password = body{"pw"}.getStr() 11 | let newPushToken = body{"tok"}.getStr() 12 | let platform = body{"platform"}.getStr() 13 | var ok = false 14 | if profileId.len != 0 and password.len != 0 and newPushToken.len != 0 and 15 | platform.len != 0: 16 | 17 | let p = await findProfileById(parseOid(profileId)) 18 | if not p.isNil and p.password == password: 19 | case platform 20 | of "android": 21 | ok = true 22 | p.androidPushToken = newPushToken 23 | of "ios": 24 | ok = true 25 | p.iosPushToken = newPushToken 26 | else: 27 | discard 28 | 29 | if ok: 30 | await p.commit() 31 | 32 | if ok: 33 | resp Http200, "OK" 34 | else: 35 | resp Http400, "Bad request" 36 | -------------------------------------------------------------------------------- /falconserver/free_rounds/free_rounds_routes.nim: -------------------------------------------------------------------------------- 1 | import strutils, json 2 | 3 | import falconserver / [ nester ] 4 | import falconserver / map / building / [ builditem ] 5 | import falconserver / auth / [ profile, profile_random, profile_types, session, profile_helpers ] 6 | import falconserver / common / [ response, response_wrapper ] 7 | 8 | 9 | import free_rounds 10 | 11 | 12 | let router = sharedRouter() 13 | 14 | 15 | router.routes: 16 | sessionPost "/free-rounds/{gameSlotID}/get-reward": 17 | let gameSlotID = @"gameSlotID" 18 | 19 | let bid = parseEnum[BuildingId](gameSlotID, noBuilding) 20 | if bid.buidingKind() != slot: 21 | respJson Http200, json.`%*`({"status": StatusCode.Ok.int, "result": false, "reason": "Unknown slot " & gameSlotID}) 22 | return 23 | 24 | let freeRounds = await findFreeRounds(profile.id) 25 | let (err, reward) = await freeRounds.getFreeRoundReward(gameSlotID) 26 | if not err.isNil: 27 | err["status"] = %StatusCode.Ok.int 28 | err["result"] = %false 29 | respJson Http200, err 30 | return 31 | 32 | let resp = json.`%*`({"status": StatusCode.Ok.int, "result": true}) 33 | resp.updateWithFreeRounds(freeRounds) 34 | await wrapRespJson(clRequest.session.profile, resp, clRequest.clientVersion) 35 | 36 | respJson Http200, resp -------------------------------------------------------------------------------- /falconserver/boosters/boosters_routes.nim: -------------------------------------------------------------------------------- 1 | import falconserver.nester 2 | 3 | import falconserver / common / [ response_wrapper, orm ] 4 | #import falconserver / admin / [admin_profile, admin_permissions, slot_zsm_test] 5 | #import falconserver.auth.profile_types 6 | 7 | import falconserver.tournament.tournaments 8 | import falconserver.map.collect 9 | 10 | # import nimongo.bson 11 | # import nimongo.mongo 12 | 13 | import json, asyncdispatch, tables, strutils, sequtils 14 | import oids 15 | import sha1 16 | import times 17 | import logging 18 | import os 19 | 20 | import boosters 21 | export boosters 22 | 23 | echo "boosters_routes added" 24 | 25 | sharedRouter().routes: 26 | sessionPost "/boost/{booster}": 27 | let booster = @"booster" 28 | if booster == $btIncome: 29 | profile.saveReadyIncomeGain(Currency.Chips) 30 | profile.saveReadyIncomeGain(Currency.Bucks) 31 | if not await profile.boosters.start(booster): 32 | respJson Http200, json.`%*`({"status": "Invalid booster " & booster}) 33 | else: 34 | if booster == $btTournamentPoints: 35 | await boostProfileParticipations(profile) 36 | var resp = json.`%*`({"boosters": profile.boosters.stateResp()}) 37 | await profile.commit() 38 | if booster == $btIncome: 39 | resp["collectConfig"] = profile.collectConfig() 40 | await wrapRespJson(profile, resp, clRequest.clientVersion) 41 | respJson Http200, resp 42 | -------------------------------------------------------------------------------- /falconserver/nakefile.nim: -------------------------------------------------------------------------------- 1 | import nake 2 | 3 | let parallelBuild = "--parallelBuild:0" 4 | let nimVerbose = "--verbosity:0" 5 | 6 | const prefer32bit = true 7 | 8 | proc runNim(file: string, arguments: varargs[string]) = 9 | var args = @[nimExe, "c", parallelBuild, 10 | nimVerbose, "-d:release", "--opt:speed", "-d:ssl", "--stacktrace:on", "--linetrace:on", "--checks:on", "--threads:on", 11 | "--path:../", "-d:falconServer"] 12 | 13 | args.add arguments 14 | args.add file 15 | direShell args 16 | 17 | proc buildServer(arguments: varargs[string]) = 18 | var args = @arguments 19 | # There's a memory leak in 64bit mode, so for production we compile 32-bit 20 | # version with statically linked libssl. 21 | when defined(linux): 22 | args.add(["--cpu:i386", "--passC:-m32", "--passL:-m32"]) 23 | elif defined(macosx) and prefer32bit: 24 | args.add(["--cpu:i386", "--passC:-arch", "--passC:i386", "--passL:-arch", "--passL:i386"]) 25 | 26 | runNim("main.nim", args) 27 | 28 | task defaultTask, "Build and run": 29 | buildServer("--run") 30 | 31 | task "build", "Build": 32 | buildServer() 33 | 34 | task "maintenance", "Maintenance": 35 | runNim("maintenance.nim", "--run") 36 | 37 | task "tests", "Tests": 38 | withDir "..": 39 | putEnv("NIM_COVERAGE_DIR", "coverage_results") 40 | createDir("coverage_results") 41 | runNim("unittests.nim", "--run", "-d:tests") 42 | direShell("nimcoverage", "genreport") 43 | -------------------------------------------------------------------------------- /falconserver/common/currency_exchange.nim: -------------------------------------------------------------------------------- 1 | import currency 2 | import tables 3 | import game_balance 4 | 5 | import falconserver / auth / [profile_random, profile_vip_helpers] 6 | import falconserver.common.get_balance_config 7 | 8 | proc exchangeCritical*(p: Profile): int = 9 | ## Generates exchange critical - number of bonus multiplier 10 | ## which is given to user after exchange operation. 11 | let gbMults = p.getGameBalance().exchangeMultiplayers 12 | 13 | var mults = newSeq[int]() 14 | var chances = newSeq[float]() 15 | 16 | var tChance = 0.0 17 | for k, v in gbMults: 18 | mults.add k 19 | chances.add tChance 20 | tChance += v 21 | 22 | let r = p.random(tChance) 23 | for i, chance in chances: 24 | if r >= chance and i < chances.len - 1 and r <= chances[i + 1]: 25 | result = mults[i] 26 | break 27 | elif i == chances.len - 1: 28 | result = mults[i] 29 | 30 | proc exchange*(p: Profile, exchangeNumber: int, cTo: Currency): tuple[bucksSpent: int64, changedTo: int64, critical: int] = 31 | ## Return amount of money according to rates, and critical value which is randomly generated 32 | ## multiplier for the performed exchange operation. 33 | if cTo == Currency.Chips: 34 | let (bucks, change) = exchangeRates(p.getGameBalance(), exchangeNumber, cTo) 35 | p.bucks = p.bucks - bucks 36 | 37 | let crit = exchangeCritical(p) 38 | p.chips = p.chips + p.exchangeGain(change * crit) 39 | return (bucksSpent: bucks, changedTo: change, critical: crit) 40 | -------------------------------------------------------------------------------- /falconserver/auth/gameplay_config.nim: -------------------------------------------------------------------------------- 1 | import json, tables 2 | import nimongo.bson 3 | 4 | import gameplay_config_decl 5 | export gameplay_config_decl 6 | 7 | import falconserver.quest.quests_config 8 | export quests_config 9 | 10 | 11 | proc getGameBalance*(gconf: GameplayConfig): GameBalance = 12 | if gconf.gameBalance.isNil: 13 | gconf.gameBalance = sharedGameBalance() 14 | gconf.gameBalance 15 | 16 | proc getStoryConfig*(gconf: GameplayConfig): seq[QuestConfig] = 17 | if gconf.storyConfig.isNil: 18 | gconf.storyConfig = getDefaultQuestConfig() 19 | 20 | result = newSeq[QuestConfig](gconf.storyConfig.configs.len) 21 | 22 | var i = 0 23 | for value in gconf.storyConfig.configs.values(): 24 | result[i] = value 25 | i.inc 26 | 27 | proc getDailyConfig*(gconf: GameplayConfig): DailyGeneratorConfig = 28 | if gconf.dailyConfig.isNil: 29 | gconf.dailyConfig = sharedDailyGeneratorConfig() 30 | gconf.dailyConfig 31 | 32 | proc getClientConfig*(gconf: GameplayConfig): JsonNode = 33 | if gconf.clientConfig.isNil: 34 | gconf.clientConfig = newJObject() 35 | gconf.clientConfig 36 | 37 | proc predefinedSpin*(gconf: GameplayConfig, gameSlotID: string, step: int): Bson = 38 | if gconf.predefinedSpinsData.isNil: 39 | getPredefinedSpin(gameSlotID, step, sharedPredefinedSpinsData()) 40 | else: 41 | getPredefinedSpin(gameSlotID, step, gconf.predefinedSpinsData) 42 | 43 | proc nearMissConfig*(gconf: GameplayConfig): NearMissConfig = 44 | if gconf.nearMissesConfig.isNil: 45 | gconf.nearMissesConfig = sharedNearMissData() 46 | gconf.nearMissesConfig 47 | -------------------------------------------------------------------------------- /falconserver/common/message.nim: -------------------------------------------------------------------------------- 1 | import json 2 | import nimongo.bson 3 | import asyncdispatch 4 | 5 | import falconserver.auth.profile_types 6 | import falconserver.auth.profile 7 | import falconserver.auth.profile_helpers 8 | import falconserver.common.response 9 | import falconserver.common.bson_helper 10 | import shafa / game / [ message_types, reward_types ] 11 | export message_types 12 | 13 | template toBson*(m: Message): Bson = m.toJson().toBson() 14 | template loadMessage*(bs: Bson): Message = bs.toJson().loadMessage() 15 | 16 | proc addMessage*(p: Profile, msg: Message) = 17 | p[$prfMessages].add(msg.toBson()) 18 | p[$prfMessages] = p[$prfMessages] # to make object aware of changes 19 | 20 | proc processMessage*(command: string, request: JsonNode, profile: Profile): Future[JsonNode] {.async.} = 21 | result = newJObject() 22 | var messagesNode = newJArray() 23 | result.add("messages", messagesNode) 24 | 25 | case command: 26 | of "check": 27 | for msgb in profile[$prfMessages]: 28 | let msg = msgb.loadMessage() 29 | messagesNode.add(msg.toJson()) 30 | 31 | of "remove": 32 | var msgs = newBsonArray() 33 | for msgb in profile[$prfMessages]: 34 | let msg = msgb.loadMessage() 35 | if request{$mfKind}.getStr() == msg.kind: 36 | msg.showed = true 37 | await profile.acceptRewards(msg.rewards, result) 38 | else: 39 | msgs.add(msg.toBson()) 40 | profile[$prfMessages] = msgs 41 | 42 | else: 43 | echo "processMessage unkown command" 44 | 45 | result{"status"} = %StatusCode.Ok.int 46 | -------------------------------------------------------------------------------- /falconserver/common/response.nim: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | const NO_MAINTENANCE_CLIENT_VERSION* = "no-maintenance" 4 | 5 | const ERR_OK* = 0 6 | const ERR_MISSING_FIELDS* = 100 7 | const ERR_UNKNOWN* = 500 8 | 9 | type StatusCode* {.pure.} = enum 10 | Failed = -1 11 | Ok 12 | InvalidRequest ## client send wrong data for example: get reward of not completed quest 13 | NotEnougthChips 14 | NotEnougthBucks 15 | NotEnougthParts 16 | WrongMapRevision 17 | IncorrectSession 18 | LogginFailure 19 | QuestNotFound 20 | InvalidQuestState 21 | NotEnougthTourPoints 22 | IncorrectMinClientVersion 23 | MaintenanceInProgress 24 | NewMaintenanceInProgress 25 | 26 | proc errorResponse*(code : int, msg : string) : JsonNode = 27 | var o = newJObject() 28 | o["status"] = %code 29 | result = o 30 | 31 | proc checkJsonFields*(fields : openarray[string], node : JsonNode, err : var string) : bool = 32 | var er = "fields missing: " 33 | result = true 34 | for s in fields: 35 | if node[s].isNil: 36 | result = false 37 | er = er & s & ", " 38 | err = er 39 | 40 | template fieldsCheck*(params : JsonNode, fields : openarray[string], jobj : untyped, bod: untyped): untyped = 41 | var jobj = params 42 | var err : string 43 | if checkJsonFields(fields, jobj, err): 44 | bod 45 | else: 46 | result = errorResponse(ERR_MISSING_FIELDS, err) 47 | 48 | template responseOK*(jn : JsonNode, bod: untyped): untyped = 49 | let st = jn{"status"} 50 | if not st.isNil and st.kind == JInt and st.num == StatusCode.Ok.int: 51 | bod 52 | -------------------------------------------------------------------------------- /falconserver/slot/machine_witch_types.nim: -------------------------------------------------------------------------------- 1 | import machine_base_types 2 | import slot_data_types 3 | 4 | const NUMBER_OF_REELS* = 5 5 | const NUMBER_OF_ROWS* = 3 6 | const ELEMENTS_COUNT* = 15 7 | const WILD* = 0'i8 8 | const SCATTER* = 1'i8 9 | const POT_RUNE_STATES* = 4 10 | const LINE_COUNT* = 20 11 | 12 | type PotState* {.pure.} = enum 13 | Start, 14 | Red, 15 | Yellow, 16 | Green, 17 | Blue, 18 | Ready 19 | 20 | type BonusIngredients* = enum 21 | Ingredient1 22 | Ingredient2 23 | Ingredient3 24 | Ingredient4 25 | Ingredient5 26 | 27 | type Pot* = ref object of RootObj 28 | index*: int 29 | stateIndex*: int 30 | states*: seq[PotState] 31 | 32 | type SlotMachineWitch* = ref object of SlotMachine 33 | canStartBonus*: bool 34 | bonusPayout*: int64 35 | runeCounter*: int 36 | runeBetTotal*: int64 37 | bet*: int64 38 | freespinsMax*: int 39 | magicSpinChance*: int 40 | bonusRounds*: int 41 | bonusProbabilityBasis*: int 42 | bonusElementsChances*: seq[int] 43 | bonusElementsPaytable*: seq[seq[int]] 44 | 45 | type SpinResult* = ref object of RootObj 46 | stage*: Stage 47 | field*: seq[int8] 48 | lines*: seq[WinningLine] 49 | freeSpinsCount*: int 50 | pots*: string 51 | potsStates*: seq[int] 52 | bonusTotalBet*: int64 53 | isSpider*: bool 54 | 55 | proc getRoundPayout*(elementsPaytable: seq[seq[int]], s: openarray[int8]): int64 = 56 | var check: seq[int] = @[0, 0, 0, 0, 0] 57 | 58 | for i in 0..= 3: 64 | result += elementsPaytable[i][elems - 3] 65 | -------------------------------------------------------------------------------- /falconserver/common/db.nim: -------------------------------------------------------------------------------- 1 | import os except DeviceId 2 | import nimongo.mongo 3 | import nimongo.bson 4 | import asyncdispatch 5 | import logging 6 | import falconserver.auth.profile_types 7 | 8 | 9 | var gDB: Database[AsyncMongo] 10 | 11 | 12 | proc initDBWithURI*(uri: string) = 13 | assert(gDB.isNil) 14 | gDB = waitFor newAsyncMongoDatabase(uri, maxConnections = 4) 15 | if not gDB.isNil: 16 | echo "Successfully connected to Mongo." 17 | # discard printStats() 18 | else: 19 | raise newException(Exception, "Could not connect to Mongo. Please check if Mongo is started or its connection parameters.") 20 | 21 | 22 | proc printStats*() {.async.} = 23 | let s = await gDB[MongoCollectionProfiles].stats() 24 | echo "Mongo stats: ", s 25 | 26 | 27 | proc sharedDB*(): Database[AsyncMongo] = 28 | assert(not gDB.isNil) 29 | result = gDB 30 | 31 | 32 | proc profilesDB*(): Collection[AsyncMongo] = sharedDB()[MongoCollectionProfiles] 33 | 34 | 35 | proc bsonToSeq*[T](b: Bson, s: var seq[T]) = 36 | s.setLen(0) 37 | for v in b: 38 | let val: T = v 39 | s.add(val) 40 | 41 | proc checkMongoReply*(s: StatusReply) = 42 | if not s.ok: 43 | raise newException(Exception, "DB Error: " & s.err) 44 | 45 | elif "writeErrors" in s.bson: 46 | error "Mongo write error ", s.bson 47 | raise newException(Exception, "MongoDB write error") 48 | 49 | proc checkMongoReply*(s: Future[StatusReply]) {.async.} = 50 | checkMongoReply(await s) 51 | 52 | 53 | proc removeWithCheck*(c: Collection[AsyncMongo], selector: Bson, limit: int = 0, ordered: bool = true, writeConcern: Bson = nil) {.async.} = 54 | let res = await c.remove(selector, limit, ordered, writeConcern) 55 | res.checkMongoReply() 56 | -------------------------------------------------------------------------------- /falconserver/quest/quest_config_decl.nim: -------------------------------------------------------------------------------- 1 | when defined(client): 2 | {.error.} 3 | 4 | import tables 5 | export tables 6 | 7 | 8 | import falconserver.map.building.builditem 9 | export builditem 10 | 11 | import falconserver / common / currency 12 | export currency 13 | 14 | import shafa / game / [ feature_types, narrative_types, reward_types ] 15 | export feature_types, narrative_types 16 | 17 | import quest_decl 18 | export quest_decl 19 | 20 | type QuestConfig* = ref object of RootObj 21 | quest*: Quest 22 | rewards*: seq[Reward] 23 | depsStr*: seq[string] 24 | name*: string 25 | deps*:seq[QuestConfig] 26 | opens*: seq[QuestConfig] 27 | price*: int 28 | currency*: Currency 29 | target*: string 30 | time*: float 31 | autoComplete*: bool 32 | lockedByLevel*: int 33 | lockedByVipLevel*: int 34 | zoneImageTiledProp*: string 35 | decoreImageTiledProp*: string 36 | isMainQuest*: bool 37 | enabled*: bool 38 | vipOnly*: bool 39 | unlockFeature*: FeatureType 40 | narrative*: NarrativeData 41 | bubbleHead*: string 42 | 43 | const notLockedByLevel* = -1 44 | 45 | 46 | type StoryQuestConfig* = ref object 47 | configs: OrderedTable[int, QuestConfig] 48 | 49 | template configs*(s: StoryQuestConfig): OrderedTable[int, QuestConfig] = s.configs 50 | template `configs=`*(s: StoryQuestConfig, v: OrderedTable[int, QuestConfig]) = s.configs = v 51 | 52 | 53 | type 54 | StageConfig* = ref object 55 | difficulty*: DailyDifficultyType 56 | taskType*: string # may be empty 57 | 58 | DailyOnLevel* = seq[QuestTask] 59 | 60 | DailyGeneratorConfig* = ref object of RootObj 61 | stagesCyclicFrom*: int 62 | slotStages*: Table[BuildingId, seq[StageConfig]] 63 | rewards*: Table[DailyDifficultyType, seq[Reward]] 64 | skipCost*: Table[DailyDifficultyType, int] 65 | tasks*: Table[DailyDifficultyType, seq[DailyOnLevel]] 66 | -------------------------------------------------------------------------------- /falconserver/tests/machine_candy_math_test.nim: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import tests 4 | import falconserver.slot.machine_base 5 | import falconserver.slot.machine_candy 6 | 7 | doTests: 8 | const lines = LINE_COUNT 9 | 10 | template spinCandy(bet: int64, stage: Stage, field:seq[int8]):seq[SpinResult] = 11 | checkCond(checkField(field), "Spin field check failed!") 12 | newSlotMachineCandy(slotMachineDesc("falconserver/resources/slot_004_candy.zsm")).getFullSpinResult(nil, bet div lines, lines, stage, field, "") 13 | 14 | template hasBonusGame(spinRes: seq[SpinResult]):bool = spinRes.len == 2 15 | 16 | template payoutForLine(spinRes: seq[SpinResult], line:int): tuple[payout: int64, symbols: int] = 17 | checkCond(line < lines, "Incorrect line") 18 | (payout: spinRes[0].lines[line].payout, symbols: spinRes[0].lines[line].numberOfWinningSymbols) 19 | 20 | template getTotalPayout(spinRes:seq[SpinResult], bet:int64):int64 = 21 | newSlotMachineCandy("falconserver/resources/slot_004_candy.zsm").getFinalPayout(bet, spinRes) 22 | 23 | var spinRes: seq[SpinResult] 24 | 25 | test: 26 | let bet = 1000'i64 27 | let betPerLine = bet div lines 28 | let wildSpin = @[0.int8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 29 | spinRes = spinCandy(bet, Stage.Spin, wildSpin) 30 | 31 | checkCond(spinRes.len > 0, "Machine broken") 32 | checkCond(not hasBonusGame(spinRes), "No bonus in this field!") 33 | 34 | let line1Payout = payoutForLine(spinRes, 1) 35 | checkCond(line1Payout.payout == 150, "Incorrect payout for wild") 36 | checkCond(getTotalPayout(spinRes, betPerLine) == 150 * lines * betPerLine - bet, "Incorrect math") 37 | 38 | test: 39 | let bonusSpin = @[1.int8, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 2, 5, 5, 6] 40 | spinRes = spinCandy(1000'i64, Stage.Spin, bonusSpin) 41 | 42 | checkCond(spinRes.len > 0, "Machine broken") 43 | checkCond(hasBonusGame(spinRes), "Bonus game is broken!") 44 | -------------------------------------------------------------------------------- /falconserver/tests/machine_candy2_math_test.nim: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import tests 4 | import falconserver.slot.machine_base 5 | import falconserver.slot.machine_candy2 6 | 7 | doTests: 8 | const lines = LINE_COUNT 9 | 10 | template spinCandy(bet: int64, stage: Stage, field:seq[int8]):seq[SpinResult] = 11 | checkCond(checkField(field), "Spin field check failed!") 12 | newSlotMachineCandy2(slotMachineDesc("falconserver/resources/slot_007_candy2.zsm")).getFullSpinResult(nil, bet div lines, lines, stage, field, "") 13 | 14 | template hasBonusGame(spinRes: seq[SpinResult]):bool = spinRes.len == 2 15 | 16 | template payoutForLine(spinRes: seq[SpinResult], line:int): tuple[payout: int64, symbols: int] = 17 | checkCond(line < lines, "Incorrect line") 18 | (payout: spinRes[0].lines[line].payout, symbols: spinRes[0].lines[line].numberOfWinningSymbols) 19 | 20 | template getTotalPayout(spinRes:seq[SpinResult], bet:int64):int64 = 21 | newSlotMachineCandy2(slotMachineDesc("falconserver/resources/slot_007_candy2.zsm")).getFinalPayout(bet, spinRes) 22 | 23 | var spinRes: seq[SpinResult] 24 | 25 | test: 26 | let bet = 1000'i64 27 | let betPerLine = bet div lines 28 | let wildSpin = @[0.int8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 29 | spinRes = spinCandy(bet, Stage.Spin, wildSpin) 30 | 31 | checkCond(spinRes.len > 0, "Machine broken") 32 | checkCond(not hasBonusGame(spinRes), "No bonus in this field!") 33 | 34 | let line1Payout = payoutForLine(spinRes, 1) 35 | checkCond(line1Payout.payout == 100, "Incorrect payout for wild") 36 | checkCond(getTotalPayout(spinRes, betPerLine) == 100 * lines * betPerLine - bet, "Incorrect math") 37 | 38 | test: 39 | let bonusSpin = @[1.int8, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 2, 5, 5, 6] 40 | spinRes = spinCandy(1000'i64, Stage.Spin, bonusSpin) 41 | 42 | checkCond(spinRes.len > 0, "Machine broken") 43 | checkCond(hasBonusGame(spinRes), "Bonus game is broken!") 44 | -------------------------------------------------------------------------------- /falconserver/slot/near_misses_pick.nim: -------------------------------------------------------------------------------- 1 | import tables, sequtils 2 | 3 | import falconserver.slot.machine_base 4 | import falconserver.auth.profile_random 5 | import falconserver.common.config 6 | 7 | import near_misses 8 | export near_misses 9 | 10 | 11 | proc generateByPattern(slotConfig: SlotNearMissConfig, p: Profile, data: seq[int8]): seq[int8] = 12 | var subst = newTable[int8, int8]() 13 | for s in slotConfig.substitutions: 14 | p.shuffle(s.dstSet) 15 | for i in countup(0, s.srcSet.len - 1): 16 | subst[s.srcSet[i]] = s.dstSet[i] 17 | result = data.mapIt(if it in subst: subst[it] else: it) 18 | 19 | 20 | proc pick*(config: NearMissConfig, p: Profile, slotKey: string, preserveItems: ItemKind, count: int): seq[int8] = 21 | if slotKey notin config.slots: 22 | return 23 | 24 | let slotConfig = config.slots[slotKey] 25 | let r = p.random(1.0) 26 | logNearMisses "NearMiss random ", r, " vs probability ", slotConfig.probability 27 | if r > slotConfig.probability: 28 | return 29 | 30 | var patterns: seq[NearMissPattern] 31 | if count >= 0: 32 | case preserveItems: 33 | of IScatter: 34 | patterns = slotConfig.patterns.filterIt(it.scatterCount == count) 35 | of IBonus: 36 | patterns = slotConfig.patterns.filterIt(it.bonusCount == count) 37 | of IWild: 38 | patterns = slotConfig.patterns.filterIt(it.wildCount == count) 39 | else: 40 | patterns = slotConfig.patterns 41 | else: 42 | patterns = slotConfig.patterns 43 | 44 | if patterns.len == 0: 45 | logNearMisses "Required pattern not found, no change to spin result" 46 | return 47 | 48 | let pattern = p.random(patterns) 49 | result = generateByPattern(slotConfig, p, pattern.data) 50 | for s in slotConfig.substitutions: 51 | logNearMisses "Substituting ", s.srcSet, " to ", s.dstSet 52 | logNearMisses "'", pattern.name, "' ", pattern.data, " -> ", result 53 | -------------------------------------------------------------------------------- /falconserver/tournament/rewards.nim: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | 4 | proc calcRewardShare(playersCount: int, place: int): float = 5 | case playersCount: 6 | of 1..14: 7 | case place: 8 | of 1: result = 0.6 9 | of 2: result = 0.3 10 | of 3: result = 0.1 11 | else: discard 12 | of 15..49: 13 | case place: 14 | of 1: result = 0.5 15 | of 2: result = 0.26 16 | of 3: result = 0.08 17 | of 4..5: result = 0.08 18 | else: discard 19 | of 50..99: 20 | case place: 21 | of 1: result = 0.4 22 | of 2: result = 0.19 23 | of 3: result = 0.07 24 | of 4..5: result = 0.07 25 | of 6..10: result = 0.04 26 | else: discard 27 | of 100..199: 28 | case place: 29 | of 1: result = 0.3 30 | of 2: result = 0.16 31 | of 3: result = 0.04 32 | of 4..5: result = 0.04 33 | of 6..10: result = 0.028 34 | of 11..20: result = 0.028 35 | else: discard 36 | of 200..299: 37 | case place: 38 | of 1: result = 0.25 39 | of 2: result = 0.12 40 | of 3: result = 0.04 41 | of 4..5: result = 0.04 42 | of 6..10: result = 0.025 43 | of 11..20: result = 0.024 44 | of 21..50: result = 0.005 45 | else: discard 46 | else: 47 | case place: 48 | of 1: result = 0.24 49 | of 2: result = 0.11 50 | of 3: result = 0.035 51 | of 4..5: result = 0.035 52 | of 6..10: result = 0.023 53 | of 11..20: result = 0.023 54 | of 21..50: result = 0.005 55 | of 51..100: result = 0.001 56 | else: discard 57 | 58 | 59 | proc calcRewardPoints*(playersCount: int, place: int, score: int): int = 60 | result = max(1, score) 61 | # result = round(score / 2 + calcRewardShare(playersCount, place) * 500).int + 1 62 | 63 | 64 | proc calcRewardCurrency*(prizeFund: int64, playersCount: int, place: int): int64 = 65 | result = round(prizeFund.float * calcRewardShare(playersCount, place)).int64 66 | -------------------------------------------------------------------------------- /falconserver/slot/machine_classic_server.nim: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import nimongo.bson 4 | 5 | import falconserver.auth.profile_types 6 | import falconserver.auth.profile 7 | import falconserver.map.building.builditem 8 | import machine_base_server 9 | import machine_classic 10 | import slot_data_types 11 | 12 | export machine_classic 13 | 14 | method getResponseAndState*(sm: SlotMachineClassic, profile: Profile, prevState: Bson, jData: JsonNode, resp: var JsonNode, slotState: var Bson) = 15 | var freespinCount: int = 0 16 | var freespinTotalWin: int64 = 0 17 | var cheatSpin :seq[int8] 18 | 19 | if not prevState.isNil: 20 | if prevState[$sdtFreespinCount].isNil: 21 | freespinCount = 0 22 | else: 23 | freespinCount = prevState[$sdtFreespinCount] 24 | if prevState[$sdtFreespinTotalWin].isNil: 25 | freespinTotalWin = 0 26 | else: 27 | freespinTotalWin = prevState[$sdtFreespinTotalWin] 28 | if not prevState[$sdtCheatSpin].isNil and prevState[$sdtCheatSpin].kind != BsonKindNull: 29 | cheatSpin = @[] 30 | for v in prevState[$sdtCheatSpin]: 31 | cheatSpin.add(v.toInt().int8) 32 | 33 | let bet = jData[$srtBet].getBiggestInt().int64 34 | let lines = jData[$srtLines].getInt() 35 | let oldFreespins = freespinCount 36 | let spin = sm.getFullSpinResult(profile, bet, lines, freespinCount, freespinTotalWin, cheatSpin) 37 | resp = sm.createResponse(spin, profile[$prfChips], bet) 38 | 39 | if freespinCount > 0: 40 | if oldFreespins > 0: 41 | dec freespinCount 42 | if freespinCount == 0: 43 | freespinTotalWin = 0 44 | 45 | slotState = newBsonDocument() 46 | slotState[$sdtFreespinCount] = freespinCount.toBson() 47 | slotState[$sdtFreespinTotalWin] = freespinTotalWin.toBson() 48 | slotState[$sdtBet] = bet.toBson() 49 | slotState[$sdtCheatSpin] = null() 50 | slotState[$sdtCheatName] = null() 51 | 52 | 53 | const dreamTowerDefaultZsm* = staticRead("../resources/slot_001_dreamtower.zsm") 54 | registerSlotMachine($dreamTowerSlot, newSlotMachineClassic, dreamTowerDefaultZsm) 55 | -------------------------------------------------------------------------------- /falconserver/fortune_wheel/fortune_wheel_routes.nim: -------------------------------------------------------------------------------- 1 | import json, strutils, asyncdispatch, httpcore, strtabs, oids, times, logging 2 | 3 | import falconserver.nester 4 | import falconserver / common / response_wrapper 5 | 6 | import nimongo.bson 7 | 8 | import falconserver.auth.profile 9 | import falconserver.common.game_balance 10 | import falconserver.common.get_balance_config 11 | import fortune_wheel 12 | 13 | sharedRouter().routes: 14 | sessionPost "/fortune/state": 15 | let list = newJArray() 16 | let gb = profile.getGameBalance() 17 | for i in gb.fortuneWheel.gains: 18 | list.add(json.`%*`({"item": i.item, "count": i.count})) 19 | 20 | let fw = profile.fortuneWheelState 21 | let resp = json.`%*`({"prevFreeSpin": fw.lastFreeTime, "nextCost": profile.fortuneWheelSpinCost(fw), "list": list, "freeSpinTimeout": profile.getGameBalance().fortuneWheel.freeSpinTimeout, "freeSpinsLeft": fw.freeSpinsLeft}) 22 | if not fw.history.isNil: 23 | let history = newJArray() 24 | for i in fw.history: 25 | history.add(json.`%*`({"item": i["item"].toString(), "count": i["count"].toInt()})) 26 | resp["history"] = history 27 | 28 | await wrapRespJson(clRequest.session.profile, resp, clRequest.clientVersion) 29 | respJson Http200, resp 30 | 31 | sessionPost "/fortune/spin": 32 | let cost = profile.fortuneWheelSpinCost(profile.fortuneWheelState) 33 | if profile.bucks < cost: 34 | respJson Http200, json.`%*`({"status": "Not enough bucks"}) 35 | else: 36 | let index = await profile.spinFortuneWheel() 37 | let resp = json.`%*`({"prevFreeSpin": profile.fortuneWheelState.lastFreeTime, 38 | "freeSpinTimeout": profile.getGameBalance().fortuneWheel.freeSpinTimeout, 39 | "nextCost": profile.fortuneWheelSpinCost(profile.fortuneWheelState), 40 | "choice": index, 41 | "wallet": profile.getWallet(), 42 | "freeSpinsLeft": profile.fortuneWheelState.freeSpinsLeft}) 43 | await wrapRespJson(clRequest.session.profile, resp, clRequest.clientVersion) 44 | respJson Http200, resp 45 | -------------------------------------------------------------------------------- /falconserver/tests/machine_classic_math_test.nim: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import tests 4 | import falconserver.slot.machine_base 5 | import falconserver.slot.machine_classic 6 | 7 | doTests: 8 | var 9 | ftw:int64 = 0 10 | freeSpins:int = 0 11 | 12 | const lines = 20 13 | 14 | template spinEiffel(bet: int64, field:seq[int8]):seq[SpinResult] = 15 | checkCond(checkField(field), "Spin field check failed!") 16 | freeSpins = 0 17 | ftw = 0'i64 18 | newSlotMachineClassic(slotMachineDesc("falconserver/resources/slot_001_dreamtower.zsm")).getFullSpinResult(nil, bet div lines, lines, freeSpins, ftw, field) 19 | 20 | template hasBonusGame(spinRes: seq[SpinResult]):bool = spinRes.len == 2 21 | 22 | template payoutForLine(spinRes: seq[SpinResult], line:int): tuple[payout: int64, symbols: int] = 23 | checkCond(line < lines, "Incorect lines") 24 | (payout: spinRes[0].lines[line].payout, symbols: spinRes[0].lines[line].numberOfWinningSymbols) 25 | 26 | template getTotalPayout(spinRes:seq[SpinResult],bet:int64):int64 = 27 | getFinalPayout(bet, spinRes) #machine_classic 28 | 29 | var spinRes: seq[SpinResult] 30 | 31 | test: 32 | let bet = 1000'i64 33 | let betPerLine = bet div lines 34 | let wildSpin = @[0.int8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 35 | spinRes = spinEiffel(bet, wildSpin) 36 | 37 | checkCond(spinRes.len > 0, "Machine broken") 38 | checkCond(not hasBonusGame(spinRes), "No bonus in this field!") 39 | let line9Payout = payoutForLine(spinRes, 9) 40 | 41 | checkCond(line9Payout.payout == 5000, "Incorrect payout for wild") 42 | checkCond(getTotalPayout(spinRes, betPerLine) == 5000 * lines * betPerLine - bet, "Incorrect math") 43 | 44 | test: 45 | let bonusSpin = @[1.int8, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 2, 5, 5, 6] 46 | spinRes = spinEiffel(1000'i64, bonusSpin) 47 | 48 | checkCond(spinRes.len > 0, "Machine broken") 49 | checkCond(hasBonusGame(spinRes), "Bonus game is broken!") 50 | 51 | test: 52 | let freeSpin = @[1.int8, 2, 3, 4, 5, 6, 7, 8, 9, 1, 1, 2, 5, 5, 6] 53 | spinRes = spinEiffel(1000'i64, freeSpin) 54 | 55 | checkCond(spinRes.len > 0, "Machine broken") 56 | checkCond(freeSpins > 0, "Freespins broken") 57 | 58 | -------------------------------------------------------------------------------- /falconserver/slot/machine_candy2_server.nim: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import nimongo.bson 4 | import falconserver.auth.profile_types 5 | import falconserver.auth.profile 6 | import falconserver.map.building.builditem 7 | import machine_base_server 8 | import machine_candy2 9 | import slot_data_types 10 | 11 | method getResponseAndState*(sm: SlotMachineCandy2, profile: Profile, prevState: Bson, jData: JsonNode, resp: var JsonNode, slotState: var Bson) = 12 | 13 | let slotID = sm.getSlotID() 14 | var freeSpinsCount: int 15 | var freespinsTotalWin: int64 16 | var cheatSpin :seq[int8] 17 | var cheatName: string 18 | 19 | if not prevState.isNil: 20 | if not prevState{$sdtFreespinCount}.isNil: 21 | freeSpinsCount = prevState{$sdtFreespinCount}.toInt32() 22 | if not prevState{$sdtFreespinTotalWin}.isNil: 23 | freespinsTotalWin = prevState{$sdtFreespinTotalWin}.toInt64() 24 | if not prevState[$sdtCheatName].isNil: 25 | cheatName = prevState[$sdtCheatName].toString() 26 | if not prevState[$sdtCheatSpin].isNil and prevState[$sdtCheatSpin].kind != BsonKindNull: 27 | cheatSpin = @[] 28 | for v in prevState[$sdtCheatSpin]: 29 | cheatSpin.add(v.toInt().int8) 30 | 31 | let bet = jData[$srtBet].getBiggestInt().int64 32 | let lines = jData[$srtLines].getInt() 33 | var stage = Stage.Spin 34 | 35 | if freeSpinsCount > 0: 36 | stage = Stage.FreeSpin 37 | 38 | let spin = sm.getFullSpinResult(profile, bet, lines, stage, cheatSpin, cheatName) 39 | 40 | if stage == Stage.FreeSpin: 41 | freespinsTotalWin += sm.getPayout(bet, spin[0]) 42 | else: 43 | freespinsTotalWin = 0 44 | 45 | resp = sm.createResponse(spin, profile[$prfChips], bet, freeSpinsCount, freespinsTotalWin) 46 | freeSpinsCount = resp[$srtFreespinCount].getInt() 47 | 48 | if stage == Stage.FreeSpin: 49 | freeSpinsCount.dec() 50 | 51 | slotState = newBsonDocument() 52 | slotState[$sdtFreespinCount] = freeSpinsCount.toBson() 53 | slotState[$sdtFreespinTotalWin] = freespinsTotalWin.toBson() 54 | slotState[$sdtBet] = bet.toBson() 55 | slotState[$sdtCheatSpin] = null() 56 | slotState[$sdtCheatName] = null() 57 | 58 | 59 | const candy2DefaultZsm* = staticRead("../resources/slot_007_candy2.zsm") 60 | registerSlotMachine($candySlot2, newSlotMachineCandy2, candy2DefaultZsm) -------------------------------------------------------------------------------- /falconserver/slot/machine_mermaid_server.nim: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import nimongo.bson 4 | 5 | import falconserver.auth.profile_types 6 | import falconserver.auth.profile 7 | import falconserver.map.building.builditem 8 | import machine_base_server 9 | import machine_mermaid 10 | import slot_data_types 11 | 12 | export machine_mermaid 13 | 14 | method getResponseAndState*(sm: SlotMachineMermaid, profile: Profile, prevState: Bson, jData: JsonNode, resp: var JsonNode, slotState: var Bson) = 15 | var freespinCount: int = 0 16 | var freespinTotalWin: int64 = 0 17 | var cheatSpin: seq[int8] 18 | 19 | if not prevState.isNil: 20 | if prevState[$sdtFreespinCount].isNil: 21 | freespinCount = 0 22 | else: 23 | freespinCount = prevState[$sdtFreespinCount] 24 | if prevState[$sdtFreespinTotalWin].isNil: 25 | freespinTotalWin = 0 26 | else: 27 | freespinTotalWin = prevState[$sdtFreespinTotalWin] 28 | if not prevState[$sdtCheatSpin].isNil and prevState[$sdtCheatSpin].kind != BsonKindNull: 29 | cheatSpin = @[] 30 | for v in prevState[$sdtCheatSpin]: 31 | cheatSpin.add(v.toInt().int8) 32 | if not prevState[$sdtFreeMultipleWild].isNil: 33 | sm.wildMultiplier = prevState[$sdtFreeMultipleWild].toInt() 34 | 35 | let bet = jData[$srtBet].getBiggestInt().int64 36 | let lines = jData[$srtLines].getInt() 37 | let oldFreespins = freespinCount 38 | let spinResult = sm.getFullSpinResult(profile, bet, lines, freespinCount, freespinTotalWin, cheatSpin) 39 | resp = sm.createResponse(spinResult.spin, spinResult.mermaidPos, profile[$prfChips], bet, lines) 40 | 41 | if freespinCount > 0 : 42 | if oldFreespins > 0: 43 | dec freespinCount 44 | if freespinCount == 0: 45 | freespinTotalWin = 0 46 | 47 | slotState = newBsonDocument() 48 | slotState[$sdtFreespinCount] = freespinCount.toBson() 49 | slotState[$sdtFreespinTotalWin] = freespinTotalWin.toBson() 50 | slotState[$sdtBet] = bet.toBson() 51 | slotState[$sdtFreeMultipleWild] = sm.wildMultiplier.toBson() 52 | slotState[$sdtCheatSpin] = null() 53 | slotState[$sdtCheatName] = null() 54 | 55 | 56 | const mermaidDefaultZsm* = staticRead("../resources/slot_006_mermaid.zsm") 57 | registerSlotMachine($mermaidSlot, newSlotMachineMermaid, mermaidDefaultZsm) 58 | -------------------------------------------------------------------------------- /falconserver/common/stats.nim: -------------------------------------------------------------------------------- 1 | import json, logging 2 | 3 | import oids 4 | import sequtils 5 | import strutils 6 | import tables 7 | import times 8 | import os except DeviceId 9 | import nuuid 10 | 11 | import nimongo.bson 12 | import nimongo.mongo 13 | 14 | import falconserver.auth.profile_types 15 | 16 | import asyncdispatch 17 | 18 | 19 | import falconserver.auth.profile 20 | import falconserver.common.db 21 | import falconserver.common.bson_helper 22 | import falconserver.schedule 23 | 24 | proc statsDB(): Collection[AsyncMongo] = 25 | result = sharedDB()["statistics"] 26 | 27 | 28 | proc dateString(date: float): string = 29 | result = date.fromSeconds().getLocalTime().format("dd-MM-yyyy") 30 | 31 | proc currentDateString(): string = 32 | result = epochtime().dateString() 33 | 34 | 35 | proc statsIncrease(incB: Bson): Future[void] {.async.} = 36 | var dateStr = currentDateString() 37 | await checkMongoReply statsDB().update(bson.`%*`({"date": dateStr}), 38 | bson.`%*`({"$setOnInsert": {"date": dateStr}, "$inc": incB}), 39 | multi = false, upsert = true) 40 | 41 | 42 | proc statsIncrease(tag: string, amount: int64 = 1): Future[void] {.async.} = 43 | if amount != 0: 44 | await statsIncrease(bson.`%*`({tag: amount})) 45 | 46 | 47 | proc reportNewUser*(): Future[void] {.async.} = 48 | await statsIncrease("newUsers") 49 | 50 | 51 | proc tryReportActiveUser*(p: Profile): Future[void] {.async.} = 52 | var prevRequestTime = p.prevRequestTime 53 | if prevRequestTime == 0: 54 | prevRequestTime = p.statistics.lastRequestTime 55 | if prevRequestTime.dateString() != currentDateString(): 56 | await statsIncrease("activeUsers") 57 | 58 | 59 | proc reportSlotSpin*(slotName: string, bet: int64, payout: int64): Future[void] {.async.} = 60 | await statsIncrease(bson.`%*`({slotName & ".spins": 1, slotName & ".bets": bet, slotName & ".payout": payout})) 61 | 62 | 63 | proc reportSlotPayout*(slotName: string, payout: int64): Future[void] {.async.} = 64 | await statsIncrease(slotName & ".payout", payout) 65 | 66 | 67 | proc reportTournamentCreated*(amount: int, fast: bool): Future[void] {.async.} = 68 | await statsIncrease(if fast: "fastTournamentsCreated" else: "tournamentsCreated", amount) 69 | 70 | 71 | proc reportPushNotificationSent*(id: string): Future[void] {.async.} = 72 | await statsIncrease("pushNotifications." & id) 73 | -------------------------------------------------------------------------------- /falconserver/slot/near_misses.nim: -------------------------------------------------------------------------------- 1 | import json, strutils, strutils, tables 2 | import falconserver.auth.profile_types 3 | import falconserver.quest.quest_types 4 | import falconserver / common / [ currency, checks, config ] 5 | 6 | 7 | type NearMissPattern* = ref object 8 | data*: seq[int8] 9 | name*: string 10 | weight*: int 11 | scatterCount*: int 12 | wildCount*: int 13 | bonusCount*: int 14 | 15 | type NearMissSubstitution* = ref object 16 | srcSet*: seq[int8] 17 | dstSet*: seq[int8] 18 | 19 | type SlotNearMissConfig* = ref object 20 | probability*: float 21 | substitutions*: seq[NearMissSubstitution] 22 | patterns*: seq[NearMissPattern] 23 | 24 | type NearMissConfig* = ref object 25 | slots*: TableRef[string, SlotNearMissConfig] 26 | 27 | 28 | import falconserver.slot.machine_base 29 | 30 | proc parseSlotNearMissConfig(slotKey: string, j: JsonNode): SlotNearMissConfig = 31 | # echo slotKey 32 | # echo j 33 | let sm = getSlotMachineByGameID(slotKey) 34 | 35 | result.new() 36 | result.probability = j["probability"].to(float) 37 | result.substitutions = j["substitutions"].to(seq[NearMissSubstitution]) 38 | result.patterns = @[] 39 | 40 | for v in j["patterns"]: 41 | let pattern = NearMissPattern.new() 42 | pattern.data = v["data"].to(seq[int8]) 43 | pattern.name = v["name"].to(string) 44 | pattern.weight = v["weight"].to(int) 45 | pattern.scatterCount = sm.countSymbolsOfType(pattern.data, IScatter) 46 | pattern.wildCount = sm.countSymbolsOfType(pattern.data, IWild) 47 | pattern.bonusCount = sm.countSymbolsOfType(pattern.data, IBonus) 48 | logNearMisses pattern.data, " -> ", pattern.scatterCount, " scatters, ", pattern.wildCount, " wilds, ", pattern.bonusCount, " bonuses" 49 | result.patterns.add(pattern) 50 | 51 | 52 | proc parseNearMisses*(j: JsonNode): NearMissConfig = 53 | result.new() 54 | result.slots = newTable[string, SlotNearMissConfig]() 55 | for k, v in j: 56 | result.slots[k] = parseSlotNearMissConfig(k, v) 57 | 58 | 59 | const nearMissData* = staticRead("../resources/balance/near_miss_patterns.json") 60 | var gNearMissData: NearMissConfig 61 | 62 | proc sharedNearMissData*(): NearMissConfig = 63 | if gNearMissData.isNil: 64 | gNearMissData = parseNearMisses(parseJson(nearMissData)) 65 | gNearMissData 66 | 67 | 68 | 69 | # testNearMiss() 70 | # discard sharedNearMissData() 71 | -------------------------------------------------------------------------------- /falconserver/tests/tests.nim: -------------------------------------------------------------------------------- 1 | template doTests*(body:untyped) = 2 | var 3 | testPassedCounter = 0 4 | testFailedCounter = 0 5 | testCounter = 0 6 | testChecks = 0 7 | checksFailed = 0 8 | checksPassed = 0 9 | checks:seq[string] = @[] 10 | 11 | let pos = instantiationInfo() 12 | bind testPassedCounter, testFailedCounter, checksFailed, testCounter, testChecks, pos 13 | 14 | proc printFails()= 15 | if checks.len > 0: 16 | inc testFailedCounter 17 | for tf in checks: 18 | echo " [>] ", tf 19 | else: 20 | inc testPassedCounter 21 | 22 | try: 23 | 24 | template test(testName = "", testBody:untyped)= 25 | testChecks = 0 26 | checksFailed = 0 27 | checksPassed = 0 28 | checks = @[] 29 | 30 | inc testCounter 31 | testBody 32 | if checksFailed == 0: 33 | echo " Test ", testName, " # ", testCounter, "\t\t OK" 34 | else: 35 | echo " Test ", testName, " # ", testCounter, "\t\t FAILED" 36 | echo " [+] ", checksPassed, " checks passed." 37 | echo " [-] ", checksFailed, " checks failed." 38 | printFails() 39 | 40 | template test(testBody:untyped)= 41 | test("", testBody) 42 | 43 | template checkCond(con:bool, msg: string)= 44 | inc testChecks 45 | if con: 46 | inc checksPassed 47 | else: 48 | inc checksFailed 49 | 50 | template buildMsg():string = 51 | "Check #" & $testChecks & ": " & msg & " \n\t" & astToStr(con) 52 | if false: 53 | echo buildMsg() 54 | else: 55 | checks.add(buildMsg()) 56 | 57 | template checkField(field:seq[int8]):bool = not field.isNil and field.len == 15 58 | 59 | echo "\nStart testing module: ", pos.filename 60 | 61 | body 62 | 63 | echo " [+] ", testPassedCounter, " tests passed" 64 | echo " [-] ", testFailedCounter, " tests failed" 65 | if testFailedCounter > 0: 66 | quit(1) 67 | except: 68 | 69 | let e = getCurrentException() 70 | writeStackTrace() 71 | echo " [!] Suite failed. Error happened: ", e.msg, " ", e.getStackTrace() 72 | printFails() 73 | quit(1) 74 | -------------------------------------------------------------------------------- /falconserver/quest/quest.nim: -------------------------------------------------------------------------------- 1 | ## Quests-related logic 2 | 3 | import quest_decl 4 | export quest_decl 5 | 6 | import quest_task 7 | export quest_task 8 | 9 | import falconserver.common.bson_helper 10 | import times 11 | 12 | 13 | proc onStoryStart*(quest: Quest, time: float, autoComplete: bool)= 14 | if quest.kind == QuestKind.Story: 15 | quest.data[$qfState][$qdfCompleteTime] = (epochTime() + time).toBson() 16 | quest.data[$qfState][$qdfAutoComplete] = autoComplete.toBson() 17 | 18 | 19 | proc onStorySpeedUp*(quest: Quest)= 20 | if quest.kind == QuestKind.Story: 21 | quest.data[$qfState][$qdfCompleteTime] = (epochTime() - 1.0).toBson() 22 | 23 | proc loadQuestData(quest: Quest, data: Bson = nil) = 24 | quest.data = newBsonDocument() 25 | for f in low(QuestFields) .. high(QuestFields): 26 | if not data{$f}.isNil: 27 | quest.data[$f] = data[$f] 28 | 29 | proc loadQuest*(bson: Bson): Quest= 30 | result.new() 31 | result.loadQuestData(bson) 32 | 33 | var tasks = newSeq[QuestTask]() 34 | for t in result.data[$qfTasks]: 35 | tasks.add(loadTask(t, result.id)) 36 | 37 | result.tasks = tasks 38 | if not result.data[$qfKind].isNil: 39 | result.kind = result.data[$qfKind].int.QuestKind 40 | else: 41 | result.kind = QuestKind.Daily 42 | result.data[$qfKind] = result.kind.int.toBson() 43 | 44 | proc timeToGoalAchieved*(quest: Quest): float= 45 | if quest.kind == QuestKind.Story and quest.isQuestActive(): 46 | if not quest.data[$qfState][$qdfCompleteTime].isNil: 47 | result = quest.data[$qfState][$qdfCompleteTime].toFloat64().float 48 | 49 | proc toBson*(quest: Quest): Bson= 50 | quest.data[$qfKind] = quest.kind.int.toBson() 51 | 52 | var tasks = newBsonArray() 53 | for t in quest.tasks: 54 | tasks.add(t.toBson()) 55 | 56 | quest.data[$qfTasks] = tasks 57 | result = quest.data 58 | 59 | proc isCompleted*(quest: Quest): bool= 60 | result = quest.data[$qfStatus].toInt().QuestProgress == QuestProgress.Completed 61 | 62 | proc createQuest*(id: int, tasks: seq[QuestTask]): Quest= 63 | var data = newBsonDocument() 64 | data[$qfStatus] = QuestProgress.Ready.int32.toBson() 65 | data[$qfState] = newBsonDocument() 66 | data[$qfId] = id.toBson() 67 | data[$qfKind] = 0.toBson() 68 | 69 | data[$qfTasks] = newBsonArray() 70 | for t in tasks: 71 | data[$qfTasks].add(t.toBson()) 72 | 73 | result.new() 74 | result.loadQuestData(data) 75 | result.tasks = tasks 76 | -------------------------------------------------------------------------------- /falconserver/slot/machine_ufo_types.nim: -------------------------------------------------------------------------------- 1 | import sequtils, tables 2 | import slot_data_types 3 | 4 | const PIPES_IN_ROW* = 3 5 | const PIPES_IN_COLUMN* = 3 6 | 7 | type PipeDirection* = enum 8 | Left, # 0 9 | Top, # 1 10 | Right, # 2 11 | Down # 3 12 | 13 | type MilkFillDirection* = enum 14 | direct = 0, 15 | indirect, 16 | both 17 | 18 | type FillInfo* = ref object of RootObj 19 | pipePos*:int8 20 | fillAnimDirection*:MilkFillDirection 21 | 22 | type BonusField* = seq[int8] 23 | type FillOrder* = seq[seq[FillInfo]] 24 | type FillStatus* = seq[bool] 25 | 26 | type Pipes* = enum 27 | pipeCross, #[ + ]##0 28 | pipeTopRight, #[|`>]##1 29 | pipeTopLeft, #[<`|]##2 30 | pipeHorizontal, #[---]##3 31 | pipeVertical, #[ | ]##4 32 | pipeBottomLeft, #[<_|]##5 33 | pipeBottomRight #[|_>]##6 34 | 35 | const pipesConnectors* : array[low(Pipes) .. high(Pipes), array[4, bool]] = [ 36 | [true,true,true,true], # pipe has connector in direction : left, top, right, down 37 | [false,true,true,false], 38 | [true,true,false,false], 39 | [true, false, true, false], 40 | [false,true,false,true], 41 | [true,false,false,true], 42 | [false,false,true,true] 43 | ] 44 | 45 | const pipesFillAnimDirection* = { 46 | pipeCross: {Left:direct}.toTable(), # pipe's milk fill animation direction when milk comes from connector. 47 | pipeTopLeft: {Left:direct,Top:indirect}.toTable(), 48 | pipeTopRight: {Top:direct,Right:indirect}.toTable(), 49 | pipeBottomLeft: {Left:direct,Down:indirect}.toTable(), 50 | pipeBottomRight: {Right:indirect,Down:direct}.toTable(), 51 | pipeVertical: {Top:direct,Down:indirect}.toTable(), 52 | pipeHorizontal: {Left:direct,Right:indirect}.toTable() 53 | }.toTable() 54 | 55 | type BonusSpinResult* = ref object of RootObj 56 | field* : BonusField 57 | fillOrder* : FillOrder 58 | stateOfFullness* : seq[bool] 59 | win*: int64 60 | hasDouble*:bool 61 | 62 | type UfoBonusData* = ref object of RootObj 63 | spinResults*: seq[BonusSpinResult] 64 | totalBet*: int64 65 | totalWin*: int64 66 | 67 | proc getNeighborPipesIndexes*(pipeIndex: int8): seq[int8] = 68 | result = newSeq[int8](4) 69 | 70 | result[Left.int] = -1 71 | if pipeIndex mod PIPES_IN_ROW != 0: 72 | result[Left.int] = pipeIndex - 1 73 | 74 | result[Top.int] = pipeIndex - PIPES_IN_ROW 75 | 76 | result[Right.int] = pipeIndex + 1 77 | if result[Right.int] mod PIPES_IN_ROW == 0: 78 | result[Right.int] = -1 79 | 80 | result[Down.int] = pipeIndex + PIPES_IN_ROW 81 | 82 | proc couldPipeProvideMilk*(p:Pipes, dir:PipeDirection): bool = 83 | result = pipesConnectors[p][dir.int] 84 | -------------------------------------------------------------------------------- /falconserver/slot/machine_candy_server.nim: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import nimongo.bson 4 | 5 | import falconserver.auth.profile_types 6 | import falconserver.auth.profile 7 | import falconserver.map.building.builditem 8 | import machine_base_server 9 | import machine_candy 10 | import slot_data_types 11 | 12 | export machine_candy 13 | 14 | method getResponseAndState*(sm: SlotMachineCandy, profile: Profile, prevState: Bson, jData: JsonNode, resp: var JsonNode, slotState: var Bson) = 15 | 16 | let slotID = sm.getSlotID() 17 | var scatters, freeSpinsCount: int 18 | var freespinsTotalWin, scattersTotalBet: int64 19 | var cheatSpin :seq[int8] 20 | var cheatName: string 21 | 22 | if not prevState.isNil: 23 | if not prevState{$sdtScatters}.isNil: 24 | scatters = prevState{$sdtScatters}.toInt32() 25 | if not prevState{$sdtFreespinCount}.isNil: 26 | freeSpinsCount = prevState{$sdtFreespinCount}.toInt32() 27 | if not prevState{$sdtFreespinTotalWin}.isNil: 28 | freespinsTotalWin = prevState{$sdtFreespinTotalWin}.toInt64() 29 | if not prevState{$sdtScattersTotalBet}.isNil: 30 | scattersTotalBet = prevState{$sdtScattersTotalBet}.toInt64() 31 | if not prevState[$sdtCheatName].isNil: 32 | cheatName = prevState[$sdtCheatName].toString() 33 | if not prevState[$sdtCheatSpin].isNil and prevState[$sdtCheatSpin].kind != BsonKindNull: 34 | cheatSpin = @[] 35 | for v in prevState[$sdtCheatSpin]: 36 | cheatSpin.add(v.toInt().int8) 37 | 38 | var bet = jData[$srtBet].getBiggestInt() 39 | let lines = jData[$srtLines].getInt() 40 | var stage = Stage.Spin 41 | 42 | if scatters == CANDY_MAX_SCATTERS: 43 | scatters = 0 44 | 45 | if freeSpinsCount > 0 or scatters == CANDY_MAX_SCATTERS: 46 | stage = Stage.FreeSpin 47 | bet = scattersTotalBet div 5 48 | 49 | let spin = sm.getFullSpinResult(profile, bet, lines, stage, cheatSpin, cheatName) 50 | let newScatters = sm.numberOfNewScatters(spin[0].field) 51 | 52 | if newScatters == 1: 53 | scatters.inc() 54 | scattersTotalBet += bet 55 | 56 | if scatters == CANDY_MAX_SCATTERS: 57 | freeSpinsCount = sm.freespinsMax 58 | 59 | if stage == Stage.FreeSpin: 60 | freespinsTotalWin += sm.getPayout(bet, spin[0]) 61 | if freeSpinsCount == 1: 62 | scattersTotalBet = 0 63 | freeSpinsCount.dec() 64 | else: 65 | freespinsTotalWin = 0 66 | 67 | resp = sm.createResponse(spin, profile[$prfChips], bet, scatters, freeSpinsCount, freespinsTotalWin) 68 | slotState = newBsonDocument() 69 | slotState[$sdtScatters] = scatters.toBson() 70 | slotState[$sdtFreespinCount] = freeSpinsCount.toBson() 71 | slotState[$sdtFreespinTotalWin] = freespinsTotalWin.toBson() 72 | slotState[$sdtScattersTotalBet] = scattersTotalBet.toBson() 73 | slotState[$sdtBet] = bet.toBson() 74 | slotState[$sdtCheatSpin] = null() 75 | slotState[$sdtCheatName] = null() 76 | 77 | const candyDefaultZsm* = staticRead("../resources/slot_004_candy.zsm") 78 | registerSlotMachine($candySlot, newSlotMachineCandy, candyDefaultZsm) 79 | -------------------------------------------------------------------------------- /falconserver/tutorial/tutorial_types.nim: -------------------------------------------------------------------------------- 1 | 2 | 3 | type TutorialState* = enum 4 | tsInvalidStep = (-1, "tutorialNotValid") 5 | tsPlayButton = "TS_PLAY_BUTTON" 6 | tsSpinButton = "TS_SPIN_BUTTON" 7 | tsTaskProgressPanel = "TS_TASK_PROGRESS_PANEL" 8 | tsTournamentButton = "TS_TOURNAMENT_BUTTON" 9 | tsTournamentInfoBar = "TS_TOURNAMENT_INFO_BAR" 10 | tsTournamentJoin = "TS_TOURNAMENT_JOIN" 11 | tsTournamentSpin = "TS_TOURNAMENT_SPIN" 12 | tsTournamentInfoBarPoints = "TS_TOURNAMENT_INFOBAR_POINTS" 13 | tsQuestWindowBar = "UNUSED" 14 | tsMapQuestAvailble = "TS_MAP_QUEST_AVAILBLE" 15 | tsMapQuestGet = "TS_MAP_QUEST_GET" 16 | tsMapQuestComplete = "TS_MAP_QUEST_COMPLETE" 17 | tsMapQuestReward = "TS_MAP_QUEST_REWARD" 18 | tsMapQuestProgress = "UNUSED" 19 | tsMapQuestSpeedup = "UNUSED" 20 | tsQuestMapBttn = "UNUSED" 21 | tsMapQuestComplete2 = "TS_MAP_QUEST_COMPLETE_2" 22 | tsMapQuestReward2 = "TS_MAP_QUEST_REWARD_2" 23 | tsMapSpotEiffel = "UNUSED" 24 | tsMapPlaySlot = "TS_MAP_PLAY_SLOT" 25 | tsSlotNewTaskBtn = "TS_SLOT_NEW_TASK_BTTN" 26 | tsRestaurantCollectRes = "TS_RESTAURANT_COLLECT_RES" 27 | tsMapQuestGet2 = "TS_MAP_QUEST_GET_2" 28 | tsMapQuestAvailble2 = "TS_MAP_QUEST_AVAILBLE_2" 29 | tsWheelQuest = "TS_WHEEL_QUEST" 30 | tsWheelGuiButton = "TS_WHEEL_GUI_BUTTON" 31 | tsWheelSpin = "TS_WHEEL_SPIN" 32 | 33 | tsTaskWinPlaySlot = "TS_TASKWIN_PLAY_SLOT" 34 | tsWheelQuestAvailble = "TS_WHEEL_QUEST_AVAILBLE" 35 | tsWheelQuestComplete = "TS_WHEEL_QUEST_COMPLETE" 36 | tsWheelQuestReward = "TS_WHEEL_QUEST_REWARD" 37 | tsTournamentQuestAvailble = "TS_TOURNAMENT_QUEST_AVAILBLE" 38 | tsUfoQuestAvailble = "TS_UFO_QUEST_AVAILBLE" 39 | tsSlotBonusButton = "TS_SLOT_BONUS_BTTN" 40 | 41 | tsWheelClose = "TS_WHEEL_CLOSE" 42 | tsGasStationQuestAvailble = "TS_GASSTATION_QUEST_AVAILBLE" 43 | tsGasStationCollectRes = "TS_GASSTATION_COLLECT_RES" 44 | tsBankQuestAvailble = "TS_BANK_QUEST_AVAILBLE" 45 | tsBankFeatureBttn = "TS_BANK_FEATURE_BTTN" 46 | tsBankWinExchangeBttn = "TS_BANK_WIN_EXCHANGE_BTTN" 47 | tsBankWinClose = "TS_BANK_WIN_CLOSE" 48 | tsCandyQuestAvailble = "TS_CANDY_QUEST_AVAILBLE" 49 | tsMapPlayCandy = "TS_MAP_PLAY_CANDY" 50 | tsCurrencyEnergy = "TS_CURRENCY_ENERGY" 51 | 52 | tsBoosterQuestAvailble = "TS_BOOSTER_QUEST_AVAILBLE" 53 | tsBoosterFeatureButton = "TS_BOOSTER_FEATURE_BUTTON" 54 | tsBoosterWindow = "TS_BOOSTER_WINDOW" 55 | tsBoosterIndicators = "TS_BOOSTER_INDICATORS" 56 | 57 | tsNotEnoughTp = "TS_NOT_ENOUGH_TP" 58 | tsShowTpPanel = "TS_SHOW_TP_PANEL" 59 | tsBankQuestReward = "TS_BANK_QUEST_REWARD" 60 | tsMapCandyQuestReward = "TS_MAP_CANDY_QUEST_REWARD" 61 | tsStadiumQuestReward = "TS_TOURNAMENT_QUEST_REWARD" 62 | tsFirstTournamentReward = "TS_FIRST_TOURNAMENT_REWARD" 63 | 64 | 65 | -------------------------------------------------------------------------------- /falconserver/quest/generator.nim: -------------------------------------------------------------------------------- 1 | ## Quest Generator for Falcon Players 2 | import json 3 | import falconserver.auth.profile_random 4 | import sequtils 5 | import strutils 6 | import tables 7 | import math 8 | import nimongo.bson 9 | import times 10 | 11 | import falconserver.auth.profile_types 12 | import falconserver.auth.profile 13 | import falconserver.map.building.builditem 14 | import falconserver.map.map 15 | import falconserver.common / [ game_balance, get_balance_config ] 16 | import falconserver.quest.quest 17 | import falconserver.quest.quest_task 18 | import falconserver.quest.quest_types 19 | import falconserver.quest.quests_config 20 | 21 | import shafa.game.feature_types 22 | 23 | 24 | # template logQuests*(args: varargs[untyped]) = 25 | # echo "[quests]: ", args 26 | 27 | 28 | proc generateQuestTaskForSlot(p: Profile, target: BuildingId, stage: int): QuestTask = 29 | let dgc = p.getDailyConfig() 30 | let stageConfig = dgc.stageConfig(target, stage) 31 | #logQuests "generateQuestTaskForSlot ", target, " @ ", stage, " => stage config is difficulty = ", stageConfig.difficulty, ", taskType = ", stageConfig.taskType 32 | #var slotTasks = dgc.allAvailableTasksForSlot(target, p.level, stageConfig.difficulty) 33 | var slotTasks = dgc.allAvailableTasksForSlotStage(target, stage, p.level) 34 | if slotTasks.len > 0: 35 | result = slotTasks[p.random(slotTasks.len)] 36 | 37 | 38 | proc generateSlotQuest*(profile: Profile, target: BuildingId): tuple[stage: int, quest: Quest] = 39 | var slotQuests = profile.slotQuests 40 | if slotQuests.isNil: 41 | slotQuests = newBsonDocument() 42 | 43 | var slotQuest = slotQuests{$target} 44 | if slotQuest.isNil: 45 | slotQuest = newBsonDocument() 46 | slotQuests{$target} = slotQuest 47 | 48 | var stageLevel = if slotQuest{"s"}.isNil: 0 else: slotQuest{"s"}.toInt32 49 | let task = profile.generateQuestTaskForSlot(target, stageLevel) 50 | assert(not task.isNil) 51 | inc stageLevel 52 | slotQuest["s"] = stageLevel.toBson() 53 | 54 | let dgc = profile.getDailyConfig() 55 | var quest = createQuest(profile.questsGenId, @[task]) 56 | quest.kind = QuestKind.Daily 57 | profile.questsGenId = profile.questsGenId + 1 58 | 59 | slotQuest["q"] = quest.id.toBson() 60 | 61 | profile.slotQuests = slotQuests 62 | result.stage = stageLevel 63 | result.quest = quest 64 | 65 | 66 | proc generateTaskForCheat*(profile: Profile, target: BuildingId, qtt: QuestTaskType, qCounter: int): Quest= 67 | var activeSlots = @[target] 68 | let plLevel = profile.level - 1 69 | let config = profile.getDailyConfig() 70 | let allTasks = config.tasks 71 | echo "generateTaskForCheat ", target, " qtt ", qtt 72 | for difficulty, tasks in allTasks: 73 | let levelTasks = tasks[clamp(plLevel, 0, tasks.len)] 74 | echo "generateTaskForCheat at level ", plLevel 75 | for lt in levelTasks: 76 | echo lt.target, "\t", difficulty, "\t", lt.kind 77 | if lt.kind == qtt and lt.progresses[0].total > 0'u64 and lt.target == target: 78 | result = createQuest(qCounter, @[lt]) 79 | result.kind = QuestKind.Daily 80 | echo " task generated ", qtt, " for ", plLevel 81 | return 82 | -------------------------------------------------------------------------------- /falconserver/map/building/builditem.nim: -------------------------------------------------------------------------------- 1 | import tables 2 | import times 3 | 4 | import falconserver.common.currency 5 | import falconserver.common.game_balance 6 | 7 | when not defined(js): 8 | import oids 9 | export oids 10 | 11 | type 12 | BuildingKind* = enum 13 | slot, res, unic, bonus, decor, nokind 14 | 15 | BuildingId* = enum 16 | noBuilding = -1, 17 | cityHall = 0, 18 | dreamTowerSlot, 19 | balloonSlot, 20 | restaurant, 21 | ufoSlot, 22 | facebook, 23 | candySlot, 24 | ratings, 25 | witchSlot, 26 | gasStation, 27 | mermaidSlot, 28 | groovySlot, 29 | testSlot, 30 | bank, 31 | anySlot, 32 | store, 33 | candySlot2, 34 | cardSlot 35 | 36 | BuildItem* = ref object 37 | id*: BuildingId 38 | level*: int 39 | name*: string 40 | lastCollectionTime*: float 41 | 42 | proc buidingKind*(id: BuildingId): BuildingKind = 43 | case id 44 | of dreamTowerSlot, balloonSlot, ufoSlot, candySlot, witchSlot, mermaidSlot, testSlot, groovySlot, cardSlot, anySlot, candySlot2: 45 | slot 46 | of restaurant, gasStation: 47 | res 48 | of cityHall, facebook, ratings, bank, store: 49 | unic 50 | of noBuilding: 51 | nokind 52 | else: 53 | nokind 54 | 55 | proc newBuildItem*(id: BuildingId): BuildItem = 56 | result.new() 57 | result.id = id 58 | # result.kind = buidingKind(id) 59 | 60 | proc configForBuildingId(conf:seq[BuildingConfig], bi: BuildingId): BuildingConfig= 61 | for c in conf: 62 | if c.buildingIdStr == $bi or (c.buildingIdStr == $anySlot and bi.buidingKind == slot): 63 | return c 64 | 65 | proc configForBuildingName(conf: seq[BuildingConfig], name: string): BuildingConfig= 66 | for c in conf: 67 | if name == c.buildingIdStr: 68 | return c 69 | 70 | proc configForBuilding(bi: BuildItem, gb: GameBalance): BuildingConfig= 71 | result = gb.buildingsConfig.configForBuildingName(bi.name) 72 | if result.isNil: 73 | result = gb.buildingsConfig.configForBuildingId(bi.id) 74 | 75 | proc resourcePerHour*(bi: BuildItem, forLevel: int = 0, gb: GameBalance): int = 76 | let bc = bi.configForBuilding(gb) 77 | if not bc.isNil: 78 | if bc.levels.len > forLevel: 79 | result = bc.levels[forLevel].income 80 | else: 81 | result = bc.levels[bc.levels.len - 1].income 82 | 83 | proc resourceCurrency*(bi: BuildItem, gb: GameBalance): Currency= 84 | let bc = bi.configForBuilding(gb) 85 | if bc.isNil: 86 | result = Currency.Unknown 87 | else: 88 | result = bc.currency 89 | 90 | proc resourceCapacity*(bi: BuildItem, gb: GameBalance): int = 91 | let bc = bi.configForBuilding(gb) 92 | if not bc.isNil: 93 | if bc.levels.len > bi.level: 94 | result = bc.levels[bi.level].capacity 95 | else: 96 | result = bc.levels[bc.levels.len - 1].capacity 97 | 98 | proc availableSlotsForIntro*(): seq[BuildingId] = 99 | return @[ 100 | dreamTowerSlot, 101 | candySlot, 102 | balloonSlot 103 | ] 104 | -------------------------------------------------------------------------------- /falconserver/slot/machine_card_server.nim: -------------------------------------------------------------------------------- 1 | import json, strutils 2 | 3 | import nimongo.bson 4 | import falconserver.auth.profile_types 5 | import falconserver.auth.profile 6 | import falconserver.auth.profile_random 7 | import falconserver.map.building.builditem 8 | import machine_base_server 9 | import machine_card 10 | import slot_data_types 11 | 12 | method getResponseAndState*(sm: SlotMachineCard, profile: Profile, prevState: Bson, jData: JsonNode, resp: var JsonNode, slotState: var Bson) = 13 | let slotID = sm.getSlotID() 14 | var freespinsTotalWin: int64 15 | var cheatSpin :seq[int8] 16 | var cheatName: string 17 | 18 | if not prevState.isNil: 19 | if not prevState{$sdtWinFreespins}.isNil: 20 | sm.winFreespins = prevState{$sdtWinFreespins}.toBool() 21 | if not prevState{$sdtFreespinCount}.isNil: 22 | sm.freeSpinsCount = prevState{$sdtFreespinCount}.toInt32() 23 | if not prevState{$sdtCardFreespinsType}.isNil: 24 | sm.fsType = parseEnum[FreespinsType](prevState{$sdtCardFreespinsType}.toString()) 25 | if not prevState{$sdtFreespinTotalWin}.isNil: 26 | freespinsTotalWin = prevState{$sdtFreespinTotalWin}.toInt32() 27 | if not prevState[$sdtCheatName].isNil: 28 | cheatName = prevState[$sdtCheatName].toString() 29 | if not prevState[$sdtCheatSpin].isNil and prevState[$sdtCheatSpin].kind != BsonKindNull: 30 | cheatSpin = @[] 31 | for v in prevState[$sdtCheatSpin]: 32 | cheatSpin.add(v.toInt().int8) 33 | 34 | let bet = jData[$srtBet].getBiggestInt().int64 35 | let lines = jData[$srtLines].getInt() 36 | var stage = Stage.Spin 37 | 38 | if sm.freeSpinsCount > 0: 39 | stage = Stage.FreeSpin 40 | 41 | if jData.hasKey($srtData): 42 | var fsType = try: parseEnum[FreespinsType](jData[$srtData].getStr()) except: jData[$srtData].getInt().FreespinsType 43 | if fsType != FreespinsType.NoFreespin: 44 | sm.fsType = fsType 45 | else: 46 | if sm.freeSpinsCount == 0: 47 | sm.fsType = NoFreespin 48 | 49 | let spin = sm.getFullSpinResult(profile, bet, lines, stage, cheatSpin, cheatName) 50 | 51 | if sm.winFreespins: 52 | sm.freeSpinsCount = sm.freespins 53 | 54 | # let r = profile.random(1..4) 55 | # sm.fsType = r.FreespinsType 56 | 57 | if stage == Stage.FreeSpin: 58 | freespinsTotalWin += sm.getPayout(bet, spin[0]) 59 | else: 60 | freespinsTotalWin = 0 61 | 62 | resp = sm.createResponse(spin, profile[$prfChips], bet, freespinsTotalWin) 63 | sm.freeSpinsCount = resp[$srtFreespinCount].getInt() 64 | 65 | if stage == Stage.FreeSpin: 66 | sm.freeSpinsCount.dec() 67 | 68 | slotState = newBsonDocument() 69 | slotState[$sdtFreespinCount] = sm.freeSpinsCount.toBson() 70 | slotState[$sdtFreespinTotalWin] = freespinsTotalWin.toBson() 71 | slotState[$sdtBet] = bet.toBson() 72 | slotState[$sdtCardFreespinsType] = ($sm.fsType).toBson() 73 | slotState[$sdtWinFreespins] = sm.winFreespins.toBson() 74 | slotState[$sdtCheatSpin] = null() 75 | slotState[$sdtCheatName] = null() 76 | 77 | const cardDefaultZsm* = staticRead("../resources/slot_009_card.zsm") 78 | registerSlotMachine($cardSlot, newSlotMachineCard, cardDefaultZsm) 79 | -------------------------------------------------------------------------------- /falconserver/tests/machine_classic_creation_test.nim: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import falconserver.slot.machine_base 4 | import falconserver.slot.machine_classic 5 | 6 | try: 7 | var 8 | testSuccessCounter = 0 # Indicates how much tests were passed 9 | testFailCounter = 0 # Indicates how much tests were failed 10 | testFails: seq[string] = @[] # Fail messages 11 | testCounter = 0 # Total test counter 12 | 13 | template MegaAssert(e: expr, o: string, v: expr = "") = 14 | bind testSuccessCounter, testFailCounter, testCounter 15 | inc(testCounter) 16 | if not e: 17 | inc(testFailCounter) 18 | write(stdout, "F") 19 | testFails.add("Test #" & $testCounter & ": " & o & " with condition: " & astToStr(e) & " having value of: " & $v) 20 | else: 21 | inc(testSuccessCounter) 22 | write(stdout, ".") 23 | 24 | stdout.write("[*] Running 'machine_classic_test.nim' tests: ") 25 | # Test machine creation 26 | let machine = newSlotMachineClassic(slotMachineDesc("falconserver/resources/slot_001_dreamtower.zsm")) 27 | MegaAssert(true, "Could not create machine from file", true) 28 | MegaAssert(machine.fieldHeight == 3, "Wrong value for 'fieldHeight' read from file", machine.fieldHeight) 29 | MegaAssert(machine.items.len == 12, "Wrong sequence of items read from file", machine.items.len) 30 | MegaAssert(machine.lines.len == 20, "Wrong number of lines read from file", machine.lines.len) 31 | for i in machine.lines[0]: 32 | MegaAssert(i == 1, "Wrong line read from file", i) 33 | 34 | # Test just spinning 35 | let field = machine.reels.spin(nil, machine.fieldHeight) 36 | 37 | MegaAssert( 38 | field.len() == (machine.fieldHeight * machine.reelCount()), 39 | "Bad 'field' generated from 'justSpin'", 40 | field.len() 41 | ) 42 | 43 | # Test combinations 44 | let combo = machine.combinations(field, 1) 45 | 46 | MegaAssert(len(combo) == 1, "Bad 'combinations' size after 'combinations' call", len(combo)) 47 | 48 | # Test public uber spin 49 | let spinResult = machine.spin(nil, 0.Stage, 10, 1) 50 | 51 | if spinResult.lines[0].numberOfWinningSymbols == 1: 52 | MegaAssert(spinResult.lines[0].payout == 0, "Bad payout value for non-winning combination", spinResult.lines[0].payout) 53 | else: 54 | MegaAssert(spinResult.lines[0].payout >= 0, "Bad payout value for winning combination", spinResult.lines[0].payout) 55 | 56 | let lines = machine.payouts(machine.combinations(@[9.int8, 5, 9, 7, 11, 11, 11, 6, 4, 7, 6, 3, 7, 6, 6], 20)) 57 | const nw : WinningLine = (1, 0.int64) 58 | MegaAssert(lines == @[(2, 0.int64), nw, nw, nw, nw, nw, nw, nw, nw, nw, nw, nw, nw, nw, nw, (2, 0.int64), (2, 0.int64), nw, nw, nw], 59 | "Wrong value for 'lines' for spin result", lines) 60 | 61 | # Test Results 62 | echo "\n[+] ", testSuccessCounter, " tests passed.\n" 63 | echo "[-] ", testFailCounter, " tests failed.\n" 64 | 65 | if len(testFails) > 0: 66 | echo "[>]" 67 | # Post-test output 68 | for msg in testFails: 69 | echo "[>] " & msg 70 | echo "[>]" 71 | quit(1) 72 | except: 73 | let e = getCurrentException() 74 | echo "[-] Suite failed. Error happened: ", e.msg 75 | quit(1) 76 | -------------------------------------------------------------------------------- /falconserver/tests/machine_groovy_math_test.nim: -------------------------------------------------------------------------------- 1 | import unittest 2 | import tests 3 | import falconserver.slot.machine_base 4 | import falconserver.slot.machine_groovy 5 | 6 | doTests: 7 | const lines = 25 8 | 9 | template spinGroovy(prevBet, betPerLine: int, rd: var GroovyRestoreData, field:seq[int8]): SpinResult = 10 | checkCond(checkField(field), "Spin field check failed!") 11 | newSlotMachineGroovy(slotMachineDesc("falconserver/resources/slot_008_groovy.zsm")).getSpinResult(nil, prevBet, betPerLine, lines, rd, field) 12 | 13 | var spinRes: SpinResult 14 | var rd: GroovyRestoreData 15 | rd.sevenWildInReel = newSeq[bool](newSlotMachineGroovy(slotMachineDesc("falconserver/resources/slot_008_groovy.zsm")).reelCount) 16 | 17 | let bet = 5000 18 | let betPerLine = bet div lines 19 | 20 | proc hasWon(spinRes: SpinResult, st: Stage): bool = 21 | if spinRes.stage != st: 22 | return false 23 | for ln in spinRes.lines: 24 | if ln.payout > 0: 25 | return true 26 | return false 27 | 28 | test: # nowin 29 | let noWinSpin = @[9.int8,1,1,1,1,6,1,1,1,1,7,1,1,1,1] 30 | spinRes = spinGroovy(betPerLine, betPerLine, rd, noWinSpin) 31 | checkCond(not spinRes.hasWon(Stage.Spin), "win error") 32 | 33 | test: # bars freespin 34 | let winSpinBars = @[9.int8,9,9,1,1,6,1,1,1,1,7,1,1,1,1] 35 | spinRes = spinGroovy(betPerLine, betPerLine, rd, winSpinBars) 36 | checkCond(spinRes.hasWon(Stage.Spin), "no win error 1") 37 | checkCond(rd.barsFreespinProgress == 1, "bar progress 1 error") 38 | 39 | spinRes = spinGroovy(betPerLine, betPerLine, rd, winSpinBars) 40 | checkCond(spinRes.hasWon(Stage.Spin), "no win error 2") 41 | checkCond(rd.barsFreespinProgress == 2, "bar progress 2 error") 42 | 43 | spinRes = spinGroovy(betPerLine, betPerLine, rd, winSpinBars) 44 | checkCond(spinRes.hasWon(Stage.Freespin), "no win error 3") 45 | checkCond(rd.barsFreespinCount == 10, "bar freespins error") 46 | checkCond(rd.barsFreespinProgress == 0, "bar progress 0 error") 47 | 48 | 49 | test: # sevens freespin 50 | rd.barsFreespinCount = 0 51 | 52 | var winSpinSevens = @[5.int8,5,5,1,1,6,1,1,1,1,7,1,1,1,1] 53 | spinRes = spinGroovy(betPerLine, betPerLine, rd, winSpinSevens) 54 | checkCond(spinRes.hasWon(Stage.Spin), "no win error 1") 55 | checkCond(rd.sevensFreespinProgress == 1, "sevens progress 1 error") 56 | 57 | winSpinSevens = @[5.int8,5,10,2,8,6,6,2,3,1,7,7,4,10,2] 58 | spinRes = spinGroovy(betPerLine, betPerLine, rd, winSpinSevens) 59 | checkCond(spinRes.hasWon(Stage.Respin), "no win error 2") 60 | checkCond(rd.sevensFreespinProgress == 2, "sevens progress 2 error") 61 | 62 | winSpinSevens = @[5.int8,5,9,2,1,6,6,3,3,2,7,7,10,10,10] 63 | spinRes = spinGroovy(betPerLine, betPerLine, rd, winSpinSevens) 64 | checkCond(spinRes.hasWon(Stage.Respin), "no win error 3") 65 | checkCond(rd.sevensFreespinProgress == 3, "sevens progress 3 error") 66 | 67 | spinRes = spinGroovy(betPerLine, betPerLine, rd, winSpinSevens) 68 | checkCond(spinRes.hasWon(Stage.Freespin), "no win error 4") 69 | checkCond(rd.sevensFreespinProgress == 0, "sevens progress 4 error") 70 | checkCond(rd.sevensFreespinCount == 10, "sevens freespins error") 71 | -------------------------------------------------------------------------------- /falconserver/slot/machine_ufo_server.nim: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import nimongo.bson 4 | 5 | import falconserver.auth.profile_types 6 | import falconserver.auth.profile 7 | import falconserver.map.building.builditem 8 | import machine_base_server 9 | import machine_ufo 10 | import slot_data_types 11 | 12 | export machine_ufo 13 | 14 | method getResponseAndState*(sm: SlotMachineUfo, profile: Profile, prevState: Bson, jData: JsonNode, resp: var JsonNode, slotState: var Bson) = 15 | 16 | var wildPos: seq[WildPos] = @[] 17 | var freespinCount = 0 18 | var freespinTotalWin = 0'i64 19 | var cheatSpin :seq[int8] 20 | 21 | block ufo_loadFromDB: 22 | if not prevState.isNil: 23 | if not prevState{$sdtFreespinCount}.isNil: 24 | freespinCount = prevState{$sdtFreespinCount} 25 | if not prevState{$sdtFreespinTotalWin}.isNil: 26 | freespinTotalWin = prevState{$sdtFreespinTotalWin} 27 | var bwp:Bson = nil 28 | if not prevState{"wp"}.isNIl: 29 | bwp = prevState{"wp"} 30 | if not bwp.isNil and bwp.kind.int != BsonKindNull.int: 31 | for v in bwp: 32 | var wp = new(WildPos) 33 | wp.id = v["id"].toInt().ItemKind 34 | wp.pos = @[] 35 | var index = 0 36 | for pos in v["pos"]: 37 | if index >= 2: 38 | break 39 | wp.pos.add(pos) 40 | inc index 41 | 42 | wildPos.add(wp) 43 | 44 | if not prevState[$sdtCheatSpin].isNil and prevState[$sdtCheatSpin].kind != BsonKindNull: 45 | cheatSpin = @[] 46 | for v in prevState[$sdtCheatSpin]: 47 | cheatSpin.add(v.toInt().int8) 48 | 49 | var oldFreespins = freespinCount 50 | let bet = jData[$srtBet].getBiggestInt().int64 51 | let lines = jData[$srtLines].getInt() 52 | var linesPayout = 0'i64 53 | let spin = sm.getFullSpinResult(profile, bet, lines, wildPos, freespinCount, freespinTotalWin, linesPayout, cheatSpin) 54 | resp = sm.createResponse(spin, profile.chips, linesPayout, bet, lines) 55 | 56 | wildPos = spin[0].wildPos 57 | 58 | if freespinCount > 0: 59 | if oldFreespins > 0: 60 | dec freespinCount 61 | if freespinCount == 0: 62 | wildPos.setLen(0) 63 | freespinTotalWin = 0 64 | 65 | slotState = newBsonDocument() 66 | slotState[$sdtFreespinCount] = freespinCount.toBson() 67 | slotState[$sdtFreespinTotalWin] = freespinTotalWin.toBson() 68 | slotState[$sdtBet] = bet.toBson() 69 | slotState[$sdtCheatSpin] = null() 70 | slotState[$sdtCheatName] = null() 71 | 72 | if wildPos.len > 0: 73 | var wild_dict = newBsonArray() 74 | for index, wp in wildPos: 75 | var doc = newBsonDocument() 76 | doc["id"] = (wp.id).int.toBson() 77 | doc["pos"] = newBsonArray() 78 | for pos in wp.pos: 79 | doc["pos"].add(pos.toBson()) 80 | wild_dict.add(doc) 81 | slotState["wp"] = wild_dict 82 | slotState[$sdtRespinsCount] = 1.toBson() 83 | else: 84 | slotState["wp"] = null() 85 | slotState[$sdtRespinsCount] = 0.toBson() 86 | 87 | const ufoDefaultZsm* = staticRead("../resources/slot_002_ufo.zsm") 88 | registerSlotMachine($ufoSlot, newSlotMachineUfo, ufoDefaultZsm) 89 | -------------------------------------------------------------------------------- /falconserver/common/bson_helper.nim: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import times 4 | import boolseq 5 | 6 | import nimongo.bson 7 | export bson 8 | 9 | 10 | proc toBson*(s: seq[int8]): Bson = 11 | result = newBsonArray() 12 | for i in s: 13 | discard result.add(i.toBson) 14 | 15 | proc toSeqTyped*[baseT, T](b: Bson, s: var seq[T]) = 16 | s.setLen(b.len) 17 | 18 | var i = 0 19 | for v in b: 20 | s[i] = T(v.baseT) 21 | inc i 22 | 23 | proc toSeqTyped*[baseT, T](b: Bson): seq[T] = 24 | toSeqTyped[baseT, T](b, result) 25 | 26 | proc toSeqInt*[T](b: Bson, s: var seq[T]) = 27 | toSeqTyped[int, T](b, s) 28 | 29 | proc toSeqInt*[T](b: Bson): seq[T] = 30 | toSeqTyped[int, T](b, result) 31 | 32 | proc toSeqFloat*[T](b: Bson): seq[T] = 33 | toSeqTyped[float, T](b, result) 34 | 35 | proc toSeqInt8*(b: Bson, s: var seq[int8]) {.deprecated, inline.} = 36 | toSeqInt(b, s) 37 | 38 | proc toBson*(s: seq[tuple[k: int, v: int]]): Bson = 39 | var arr = newBsonArray() 40 | for i in s: 41 | discard arr.add(i.k.toBson) 42 | discard arr.add(i.v.toBson) 43 | return arr 44 | 45 | proc toBson*(s: seq[tuple[k: int, v: int64]]): Bson = 46 | var arr = newBsonArray() 47 | for i in s: 48 | discard arr.add(i.k.toBson) 49 | discard arr.add(i.v.toBson) 50 | return arr 51 | 52 | proc toBoolSeq(jn: JsonNode): BoolSeq= 53 | var questate = "" 54 | for ji in jn: 55 | questate.add(ji.getInt().char) 56 | 57 | result = newBoolSeq(questate) 58 | 59 | proc toBson*(json: JsonNode): Bson = 60 | case json.kind: 61 | of JNull: 62 | result = null() 63 | of JBool: 64 | result = json.getBool().toBson() 65 | of JInt: 66 | result = json.getBiggestInt().toBson() 67 | of JFloat: 68 | result = json.getFloat().toBson() 69 | of JString: 70 | result = json.getStr().toBson() 71 | of JObject: 72 | if "boolseq" in json: 73 | result = binuser(json["boolseq"].toBoolSeq().string) 74 | else: 75 | result = newBsonDocument() 76 | for k, v in json: 77 | result[k] = v.toBson() 78 | of JArray: 79 | result = newBsonArray() 80 | for v in json: 81 | result.add(v.toBson()) 82 | 83 | proc toJson*(bson: Bson): JsonNode = 84 | case bson.kind 85 | of BsonKindInt32: 86 | result = %bson.toInt32() 87 | of BsonKindInt64: 88 | result = %bson.toInt64() 89 | of BsonKindDouble: 90 | result = %bson.toFloat64() 91 | of BsonKindNull: 92 | result = newJNull() 93 | of BsonKindStringUTF8: 94 | result = %bson.toString() 95 | of BsonKindArray: 96 | result = newJArray() 97 | for ch in bson: 98 | result.add(ch.toJson()) 99 | of BsonKindBool: 100 | result = %bson.toBool() 101 | of BsonKindDocument: 102 | result = newJObject() 103 | for key, value in bson: 104 | result[key] = value.toJson() 105 | of BsonKindOid: 106 | result = %($bson) 107 | of BsonKindTimeUTC: 108 | result = %($(bson.toTime())) 109 | of BsonKindBinary: 110 | result = newJObject() 111 | result["boolseq"] = %newBoolSeq(bson.binstr()).toIntSeq() 112 | else: 113 | result = newJNull() 114 | echo "toJson(bson: Bson): not implemeted convertation from : ", bson.kind 115 | -------------------------------------------------------------------------------- /falconserver/tournament/tournament_routes.nim: -------------------------------------------------------------------------------- 1 | import tables, json 2 | import asyncdispatch 3 | import falconserver.common.orm 4 | 5 | import falconserver.nester 6 | import falconserver / common / response_wrapper 7 | #import falconserver.routes.auth_routes 8 | 9 | import tournaments 10 | import fake_participation 11 | 12 | 13 | echo "tournament_routes added" 14 | 15 | let router = sharedRouter() 16 | router.routes: 17 | sessionPost "/tournaments/createfast": 18 | await createFastTournament() 19 | let r = await listTournaments(session.profile, clRequest.body{"sinceTime"}) 20 | await wrapRespJson(clRequest.session.profile, r, clRequest.clientVersion) 21 | respJson Http200, r 22 | 23 | sessionPost "/tournaments/tutorial": 24 | let participations = await session.profile.findProfileParticipations() 25 | if participations.len > 0: 26 | let r = await listTournaments(session.profile, nil) 27 | await wrapRespJson(clRequest.session.profile, r, clRequest.clientVersion) 28 | respJson Http200, r 29 | else: 30 | let t = await session.profile.findOrCreateTutorialTournament() 31 | while t.playersCount < 3: 32 | await t.tournamentBotsJoin() 33 | let r = await t.getTutorialTournamentResp() 34 | await wrapRespJson(clRequest.session.profile, r, clRequest.clientVersion) 35 | respJson Http200, r 36 | 37 | sessionPost "/tournaments/bots/join": 38 | let r = await tryBotsJoinTournament(clRequest.body) 39 | await wrapRespJson(clRequest.session.profile, r, clRequest.clientVersion) 40 | respJson Http200, r 41 | 42 | sessionPost "/tournaments/finish": 43 | let r = await tryForceFinishTournament(session.profile, clRequest.body) 44 | await wrapRespJson(clRequest.session.profile, r, clRequest.clientVersion) 45 | respJson Http200, r 46 | 47 | sessionPost "/tournaments/gain": 48 | let r = await tryGainTournamentScore(clRequest.session.profile, clRequest.body) 49 | await wrapRespJson(clRequest.session.profile, r, clRequest.clientVersion) 50 | respJson Http200, r 51 | 52 | sessionPost "/tournaments/list": 53 | let r = await listTournaments(clRequest.session.profile, clRequest.body{"sinceTime"}) 54 | await wrapRespJson(clRequest.session.profile, r, clRequest.clientVersion) 55 | respJson Http200, r 56 | 57 | sessionPost "/tournaments/join": 58 | let resp = await tryJoinTournament(session.profile, clRequest.body) 59 | if not resp.hasKey("partId"): 60 | echo resp["status"].getStr() 61 | 62 | await wrapRespJson(clRequest.session.profile, resp, clRequest.clientVersion) 63 | respJson Http200, resp 64 | 65 | sessionPost "/tournaments/leave": 66 | let r = await tryLeaveTournament(session.profile, clRequest.body) 67 | await wrapRespJson(clRequest.session.profile, r, clRequest.clientVersion) 68 | respJson Http200, r 69 | 70 | sessionPost "/tournaments/claim": 71 | let r = await tryClaimTournamentReward(session.profile, clRequest.body) 72 | await wrapRespJson(clRequest.session.profile, r, clRequest.clientVersion) 73 | respJson Http200, r 74 | 75 | sessionPost "/tournaments/info": 76 | let r = await tryGetTournamentDetails(session.profile, clRequest.body) 77 | await wrapRespJson(clRequest.session.profile, r, clRequest.clientVersion) 78 | respJson Http200, r 79 | -------------------------------------------------------------------------------- /falconserver/slot/machine_witch_server.nim: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import nimongo.bson 4 | 5 | import falconserver.auth.profile_types 6 | import falconserver.auth.profile 7 | import falconserver.map.building.builditem 8 | import falconserver.common.bson_helper 9 | import machine_base_server 10 | import machine_witch 11 | import slot_data_types 12 | 13 | export machine_witch 14 | 15 | 16 | method createInitialState*(sm: SlotMachineWitch, profile: Profile): Bson = 17 | result = procCall sm.SlotMachine.createInitialState(profile) 18 | var potsStates = sm.initNewPots(profile) 19 | result[$sdtPotsStates] = potsStates.toBson() 20 | 21 | 22 | method getResponseAndState*(sm: SlotMachineWitch, profile: Profile, prevState: Bson, jData: JsonNode, resp: var JsonNode, slotState: var Bson) = 23 | var freeSpinsCount, runeCounter: int 24 | var runeBetTotal: int64 25 | var freespinsTotalWin: int64 26 | var pots: string = "00000" 27 | var potsStates: seq[int] = @[] 28 | var cheatSpin :seq[int8] 29 | var cheatName: string 30 | 31 | if not prevState.isNil: 32 | if not prevState{$sdtFreespinCount}.isNil: 33 | freeSpinsCount = prevState{$sdtFreespinCount}.toInt32() 34 | if not prevState{$sdtFreespinTotalWin}.isNil: 35 | freespinsTotalWin = prevState{$sdtFreespinTotalWin}.toInt64() 36 | if not prevState{$sdtPots}.isNil: 37 | pots = prevState{$sdtPots}.toString() 38 | if not prevState{$sdtPotsStates}.isNil: 39 | prevState{$sdtPotsStates}.toSeqInt(potsStates) 40 | else: 41 | potsStates = sm.initNewPots(profile) 42 | if not prevState{$sdtRuneCounter}.isNil: 43 | runeCounter = prevState{$sdtRuneCounter}.toInt32() 44 | if not prevState{$sdtRuneBetTotal}.isNil: 45 | runeBetTotal = prevState{$sdtRuneBetTotal}.toInt64() 46 | if not prevState[$sdtCheatName].isNil: 47 | cheatName = prevState[$sdtCheatName].toString() 48 | if not prevState[$sdtCheatSpin].isNil and prevState[$sdtCheatSpin].kind != BsonKindNull: 49 | cheatSpin = @[] 50 | for v in prevState[$sdtCheatSpin]: 51 | cheatSpin.add(v.toInt().int8) 52 | 53 | let bet = jData[$srtBet].getBiggestInt().int64 54 | let lines = jData[$srtLines].getInt() 55 | var stage = Stage.Spin 56 | 57 | if freeSpinsCount > 0: 58 | stage = Stage.FreeSpin 59 | let spin = sm.getFullSpinResult(profile, bet, lines, runeCounter, runeBetTotal, pots, potsStates, stage, cheatSpin, cheatName) 60 | 61 | if stage == Stage.FreeSpin: 62 | freespinsTotalWin += sm.getPayout(bet, spin[0]) 63 | freeSpinsCount.dec() 64 | else: 65 | freespinsTotalWin = 0 66 | resp = sm.createResponse(spin, profile[$prfChips], bet, freeSpinsCount, pots, potsStates, freespinsTotalWin) 67 | freeSpinsCount = resp[$srtFreespinCount].getInt() 68 | 69 | if spin.len > 1: 70 | sm.runeCounter = 0 71 | sm.runeBetTotal = 0 72 | 73 | slotState = newBsonDocument() 74 | slotState[$sdtFreespinCount] = freeSpinsCount.toBson() 75 | slotState[$sdtFreespinTotalWin] = freespinsTotalWin.toBson() 76 | slotState[$sdtBet] = bet.toBson() 77 | slotState[$sdtPots] = spin[0].pots.toBson() 78 | slotState[$sdtPotsStates] = spin[0].potsStates.toBson() 79 | slotState[$sdtRuneCounter] = sm.runeCounter.toBson() 80 | slotState[$sdtRuneBetTotal] = sm.runeBetTotal.toBson() 81 | slotState[$sdtCheatSpin] = null() 82 | slotState[$sdtCheatName] = null() 83 | 84 | const witchDefaultZsm* = staticRead("../resources/slot_005_witch.zsm") 85 | registerSlotMachine($witchSlot, newSlotMachineWitch, witchDefaultZsm) -------------------------------------------------------------------------------- /falconserver/fortune_wheel/fortune_wheel.nim: -------------------------------------------------------------------------------- 1 | import times 2 | import sequtils 3 | import nimongo.bson 4 | import asyncdispatch 5 | import math 6 | 7 | import falconserver.common.db 8 | import falconserver.common.orm 9 | import falconserver.auth.profile_random 10 | import falconserver.common.notifications 11 | import falconserver.common.game_balance 12 | import falconserver.common.get_balance_config 13 | 14 | type WheelSpinCostType* {.pure.}= enum 15 | Timeout, 16 | FreeSpin, 17 | Currency 18 | 19 | proc fortuneWheelCostType(p: Profile, s: FortuneWheelState): WheelSpinCostType = 20 | if epochTime() - s.lastFreeTime >= p.getGameBalance().fortuneWheel.freeSpinTimeout: 21 | result = WheelSpinCostType.Timeout 22 | elif s.freeSpinsLeft > 0: 23 | result = WheelSpinCostType.FreeSpin 24 | else: 25 | result = WheelSpinCostType.Currency 26 | 27 | proc fortuneWheelSpinCost*(p: Profile, s: FortuneWheelState): int = 28 | let t = p.fortuneWheelCostType(s) 29 | case t 30 | of WheelSpinCostType.Currency: 31 | result = p.getGameBalance().fortuneWheel.spinCost 32 | else: 33 | result = 0 34 | 35 | 36 | proc getWeightedRandom(p: Profile, probs: seq[float]): int = 37 | var roll = p.random(probs.sum()) 38 | for i in 0 ..< probs.len: 39 | if roll <= probs[i]: 40 | return i 41 | else: 42 | roll -= probs[i] 43 | return probs.len # should cause an error 44 | 45 | proc spinFortuneWheel*(p: Profile): Future[int] {.async.} = 46 | var s = p.fortuneWheelState 47 | 48 | let costType = p.fortuneWheelCostType(s) 49 | case costType 50 | of WheelSpinCostType.Timeout: 51 | s.lastFreeTime = epochTime() 52 | await notifyNextFreeFortuneWheelSpin(p.id, s.lastFreeTime + p.getGameBalance().fortuneWheel.freeSpinTimeout) 53 | of WheelSpinCostType.FreeSpin: 54 | s.freeSpinsLeft = s.freeSpinsLeft - 1 55 | of WheelSpinCostType.Currency: 56 | let cost = p.fortuneWheelSpinCost(s) 57 | p.bucks = p.bucks - cost 58 | 59 | let fwData = p.getGameBalance().fortuneWheel 60 | result = p.getWeightedRandom(if s.history.isNil: fwData.firstTimeProbs else: fwData.defaultProbs) 61 | var gain = fwData.gains[result] 62 | case gain.item: 63 | of "chips": 64 | p.chips = p.chips + gain.count 65 | of "bucks": 66 | p.bucks = p.bucks + gain.count 67 | of "beams", "parts", "energy": 68 | p.parts = p.parts + gain.count 69 | 70 | if s.history.isNil or s.history.kind != BsonKindArray: # for some reason, some old accounts kind is Document 71 | s.history = bson.`%*`([{"item": gain.item, "count": gain.count}]) 72 | elif s.history.len() < 3: 73 | discard s.history.add bson.`%*`({"item": gain.item, "count": gain.count}) 74 | else: 75 | var newHistory = newBsonArray() 76 | for i in 1 ..< s.history.len(): 77 | discard newHistory.add s.history[i] 78 | discard newHistory.add bson.`%*`({"item": gain.item, "count": gain.count}) 79 | s.history = newHistory 80 | 81 | # temporary fix, as ORM doesn't handle well child objects 82 | p.fortuneWheelB = wheelStateToBson(s) 83 | await p.commit() 84 | 85 | proc setNextFreeSpinTime*(p: Profile, seconds:int) = 86 | var s = p.fortuneWheelState 87 | s.lastFreeTime = epochTime() - p.getGameBalance().fortuneWheel.freeSpinTimeout + seconds.float64 88 | 89 | p.fortuneWheelB = wheelStateToBson(s) 90 | 91 | proc addWheelFreeSpins*(p: Profile, amount:int) = 92 | var s = p.fortuneWheelState 93 | s.freeSpinsLeft = s.freeSpinsLeft + amount 94 | 95 | p.fortuneWheelB = wheelStateToBson(s) 96 | 97 | -------------------------------------------------------------------------------- /falconserver/cheats/cheats_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "common":{ 3 | "balance":[ 4 | { 5 | "name":"chips", 6 | "request":"/cheats/chips", 7 | "value":0 8 | },{ 9 | "name":"chips", 10 | "request":"/cheats/chips", 11 | "value":100000 12 | },{ 13 | "name":"chips", 14 | "request":"/cheats/chips", 15 | "value":10000000 16 | },{ 17 | "name":"parts", 18 | "request":"/cheats/parts", 19 | "value":0 20 | },{ 21 | "name":"parts", 22 | "request":"/cheats/parts", 23 | "value":1000 24 | },{ 25 | "name":"parts", 26 | "request":"/cheats/parts", 27 | "value":100000 28 | },{ 29 | "name":"tourPoints", 30 | "request":"/cheats/tourpoints", 31 | "value":0 32 | },{ 33 | "name":"tourPoints", 34 | "request":"/cheats/tourpoints", 35 | "value":15000 36 | },{ 37 | "name":"exp", 38 | "request":"/cheats/exp", 39 | "value":1000 40 | },{ 41 | "name":"exp", 42 | "request":"/cheats/exp", 43 | "value":"level_up" 44 | },{ 45 | "name":"bucks", 46 | "request":"/cheats/bucks", 47 | "value":10000 48 | },{ 49 | "name":"bucks", 50 | "request":"/cheats/bucks", 51 | "value":100 52 | },{ 53 | "name":"bucks", 54 | "request":"/cheats/bucks", 55 | "value":0 56 | },{ 57 | "name":"resetProgress", 58 | "request":"/cheats/reset_progress", 59 | "value":"Progress" 60 | },{ 61 | "name":"resetTutorial", 62 | "request":"/cheats/reset_tutorial", 63 | "value":"tutorial" 64 | },{ 65 | "name":"gameOver", 66 | "request":"/cheats/gameOver", 67 | "value":0 68 | },{ 69 | "name":"skip_tutorial", 70 | "request":"/cheats/skip_tutorial", 71 | "value":0 72 | },{ 73 | "name":"clearOffers", 74 | "request":"/cheats/clear_offers", 75 | "value":0 76 | },{ 77 | "name":"resetFreeGiftTimers", 78 | "request":"/cheats/reset_free_gift_timers", 79 | "value":0 80 | },{ 81 | "name":"add income booster", 82 | "request":"/cheats/addbooster_inc", 83 | "value":"60" 84 | },{ 85 | "name":"add exp booster", 86 | "request":"/cheats/addbooster_exp", 87 | "value":"60" 88 | },{ 89 | "name":"add TP booster", 90 | "request":"/cheats/addbooster_tp", 91 | "value":"60" 92 | },{ 93 | "name":"remove boosters", 94 | "request":"/cheats/removeboosters", 95 | "value":"" 96 | },{ 97 | "name":"send push", 98 | "request":"/cheats/send_notification", 99 | "value":"" 100 | } 101 | ] 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /falconserver/utils/asyncspawn.nim: -------------------------------------------------------------------------------- 1 | when defined(windows): 2 | import asyncdispatch 3 | 4 | proc asyncSpawn*[T](v: T): Future[T] {.async.} = 5 | # TODO: ... 6 | return v 7 | else: 8 | import asyncdispatch, threadpool, locks, asyncfile 9 | import posix except spawn 10 | 11 | type AsyncSpawnCtx = ref object {.inheritable.} 12 | dispatcher: proc(ctx: AsyncSpawnCtx) {.nimcall.} 13 | fut: pointer 14 | 15 | var gChannel: Channel[AsyncSpawnCtx] 16 | gChannel.open() 17 | 18 | var pipeLock: Lock 19 | var pipeFd: cint 20 | 21 | 22 | proc runMainThreadSelector(s: AsyncFile) {.async.} = 23 | var p: pointer 24 | while true: 25 | discard await s.readBuffer(addr p, sizeof(p)) 26 | let ctx = gChannel.recv() 27 | ctx.dispatcher(ctx) 28 | GC_unref(cast[RootRef](ctx.fut)) 29 | 30 | proc setNonBlocking(fd: cint) {.inline.} = 31 | var x = fcntl(fd, F_GETFL, 0) 32 | if x != -1: 33 | var mode = x or O_NONBLOCK 34 | discard fcntl(fd, F_SETFL, mode) 35 | 36 | proc initDispatch() = 37 | initLock(pipeLock) 38 | var pipeFds: array[2, cint] 39 | discard posix.pipe(pipeFds) 40 | pipeFd = pipeFds[1] 41 | setNonBlocking(pipeFds[0]) 42 | let file = newAsyncFile(AsyncFD(pipeFds[0])) 43 | asyncCheck runMainThreadSelector(file) 44 | 45 | initDispatch() 46 | 47 | type PerformProcWrapper = object 48 | pr: proc() 49 | 50 | proc performOnBackgroundThread(o: PerformProcWrapper) = 51 | o.pr() 52 | 53 | template asyncSpawn*(p: typed): auto = 54 | block: 55 | # let a = p 56 | when compiles(p is void): 57 | # echo "Compiles" 58 | type RetType = type(p) 59 | else: 60 | type RetType = void 61 | 62 | type Ctx = ref object of AsyncSpawnCtx 63 | when RetType isnot void: 64 | res: RetType 65 | 66 | var f = newFuture[RetType]() 67 | GC_ref(f) 68 | 69 | let pfut = cast[pointer](f) 70 | 71 | proc disp(ctx: AsyncSpawnCtx) {.nimcall.} = 72 | let ctx = cast[Ctx](ctx) 73 | let f = cast[Future[RetType]](ctx.fut) 74 | when RetType is void: 75 | f.complete() 76 | else: 77 | f.complete(ctx.res) 78 | 79 | proc b() = 80 | var ctx: Ctx 81 | ctx.new() 82 | ctx.dispatcher = disp 83 | ctx.fut = pfut 84 | 85 | when RetType is void: 86 | p 87 | else: 88 | ctx.res = p 89 | 90 | gChannel.send(ctx) 91 | pipeLock.acquire() 92 | var dummy: pointer 93 | discard posix.write(pipeFd, unsafeAddr dummy, sizeof(dummy)) 94 | pipeLock.release() 95 | 96 | # This dancing with the wrapper is required because of nim bug #7057 97 | 98 | var w: PerformProcWrapper 99 | w.pr = b 100 | 101 | threadpool.spawn performOnBackgroundThread(w) 102 | f 103 | 104 | 105 | when isMainModule: 106 | import times, strutils 107 | 108 | proc foo(a: int, b: int): int = 109 | echo "hi", a, b 110 | a + b 111 | 112 | proc b() = 113 | let a = 3 114 | let i = waitFor asyncSpawn foo(a, 2) 115 | echo "bye: ", i 116 | # sync() 117 | echo "done" 118 | 119 | b() 120 | -------------------------------------------------------------------------------- /falconserver/auth/profile_vip_helpers.nim: -------------------------------------------------------------------------------- 1 | import profile 2 | export profile 3 | 4 | import shafa / game / vip_types 5 | export vip_types 6 | 7 | import falconserver / auth / profile_helpers 8 | import falconserver / fortune_wheel / fortune_wheel 9 | import falconserver / boosters / boosters 10 | 11 | import falconserver / quest / quest_manager 12 | 13 | import math 14 | import asyncdispatch 15 | import sequtils 16 | 17 | 18 | proc chipsPurchaseBonus*(p: Profile): float = 19 | let cfg = p.gconf.getGameBalance().vipConfig 20 | cfg.levels[max(p.vipLevel.int, 0)].chipsBonus 21 | 22 | 23 | proc bucksPurchaseBonus*(p: Profile): float = 24 | let cfg = p.gconf.getGameBalance().vipConfig 25 | cfg.levels[max(p.vipLevel.int, 0)].bucksBonus 26 | 27 | proc exchangeBonus*(p: Profile): float = 28 | let cfg = p.gconf.getGameBalance().vipConfig 29 | cfg.levels[max(p.vipLevel.int, 0)].exchangeBonus 30 | 31 | 32 | proc exchangeGain*(p: Profile, value: int64): int64 = 33 | result = value + round(value.float * p.exchangeBonus()).int64 34 | 35 | 36 | proc giftBonus*(p: Profile): float = 37 | let cfg = p.gconf.getGameBalance().vipConfig 38 | cfg.levels[max(p.vipLevel.int, 0)].giftsBonus 39 | 40 | 41 | proc giftGain*(p: Profile, value: int): int = 42 | result = value + round(value.float * p.giftBonus()).int 43 | 44 | 45 | proc wheelSpinsReward*(config: VipConfig, fromLevel, toLevel: int): int = 46 | result = 0 47 | for i in fromLevel + 1 .. toLevel: 48 | let reward = config.levels[i].getReward(RewardKind.wheel) 49 | if not reward.isNil: 50 | result += reward.amount.int 51 | 52 | 53 | proc gainVipLevelRewards*(profile: Profile, fromLevel, toLevel: int, res: JsonNode) {.async.} = 54 | let vipConfig = profile.gconf.getGameBalance().vipConfig 55 | var rewards = newSeq[Reward]() 56 | 57 | for lvl in fromLevel .. toLevel: 58 | rewards.add(vipConfig.levels[lvl].rewards) 59 | 60 | var qm: QuestManager 61 | rewards.keepIf(proc(x: Reward): bool = 62 | if x.kind == RewardKind.vipaccess: 63 | if qm.isNil: 64 | qm = profile.newQuestManager() 65 | let zoneReward = x.ZoneReward 66 | qm.gainVipAccess(zoneReward.zone) 67 | result = false 68 | else: 69 | result = true 70 | ) 71 | 72 | await profile.acceptRewards(rewards, res) 73 | 74 | 75 | proc vipLevelForPoints*(profile: Profile, points: int64, flomLevel: int64 = 0): int = 76 | let vipConfig = profile.gconf.getGameBalance().vipConfig 77 | result = vipConfig.levels[vipConfig.levels.high].level 78 | 79 | for i in flomLevel .. vipConfig.levels.high: 80 | let lvl = vipConfig.levels[i.int] 81 | if lvl.pointsRequired > profile.vipPoints: 82 | result = lvl.level - 1 83 | break 84 | 85 | 86 | proc gainVipPoints*(profile: Profile, vipPoints: int64, res: JsonNode) {.async.} = 87 | let vipConfig = profile.gconf.getGameBalance().vipConfig 88 | 89 | let currentVipLevel = vipConfig.levels[max(profile.vipLevel.int, 0)] 90 | 91 | profile.vipPoints = profile.vipPoints + vipPoints 92 | profile.vipLevel = profile.vipLevelForPoints(profile.vipPoints, max(profile.vipLevel, 0)) 93 | 94 | res["vip"] = %{"points": %profile.vipPoints, "level": %profile.vipLevel} 95 | await profile.gainVipLevelRewards(currentVipLevel.level + 1, profile.vipLevel.int, res) 96 | 97 | 98 | proc gainVipPointsForPurchase*(profile: Profile, usdPrice: float, res: JsonNode) {.async.} = 99 | let points = profile.gconf.getGameBalance().vipConfig.vipPointsForPrice(usdPrice) 100 | await profile.gainVipPoints(points, res) 101 | -------------------------------------------------------------------------------- /falconserver/slot/machine_groovy_server.nim: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import nimongo.bson 4 | 5 | import falconserver.auth.profile_types 6 | import falconserver.auth.profile 7 | import falconserver.map.building.builditem 8 | import falconserver.common.bson_helper 9 | import machine_base_server 10 | import machine_groovy 11 | import slot_data_types 12 | 13 | export machine_groovy 14 | 15 | method getResponseAndState*(sm: SlotMachineGroovy, profile: Profile, prevState: Bson, jData: JsonNode, resp: var JsonNode, slotState: var Bson) = 16 | var cheatSpin :seq[int8] 17 | var rd: GroovyRestoreData 18 | var prevBet: int64 19 | rd.sevenWildInReel = newSeq[bool](sm.reelCount) 20 | 21 | if not prevState.isNil: 22 | if prevState[$sdtSevensFreespinCount].isNil: rd.sevensFreespinCount = 0 23 | else: rd.sevensFreespinCount = prevState[$sdtSevensFreespinCount] 24 | if prevState[$sdtSevensFreespinTotalWin].isNil: rd.sevensFreespinTotalWin = 0 25 | else: rd.sevensFreespinTotalWin = prevState[$sdtSevensFreespinTotalWin] 26 | if prevState[$sdtSevensFreespinProgress].isNil: rd.sevensFreespinProgress = 0 27 | else: rd.sevensFreespinProgress = prevState[$sdtSevensFreespinProgress] 28 | 29 | if prevState[$sdtBarsFreespinCount].isNil: rd.barsFreespinCount = 0 30 | else: rd.barsFreespinCount = prevState[$sdtBarsFreespinCount] 31 | if prevState[$sdtBarsFreespinTotalWin].isNil: rd.barsFreespinTotalWin = 0 32 | else: rd.barsFreespinTotalWin = prevState[$sdtBarsFreespinTotalWin] 33 | if prevState[$sdtBarsFreespinProgress].isNil: rd.barsFreespinProgress = 0 34 | else: rd.barsFreespinProgress = prevState[$sdtBarsFreespinProgress] 35 | 36 | if not prevState[$sdtSevenWildInReel].isNil: 37 | let sevensInReels = prevState[$sdtSevenWildInReel] 38 | for i in 0.. 0: 61 | slotState[$sdtFreespinCount] = rd.sevensFreespinCount.toBson() 62 | elif rd.barsFreespinCount > 0: 63 | slotState[$sdtFreespinCount] = rd.barsFreespinCount.toBson() 64 | else: 65 | slotState[$sdtFreespinCount] = 0.toBson() 66 | slotState[$sdtSevenWildInReel] = rd.sevenWildInReel.toBson() 67 | slotState[$sdtRespinsCount] = rd.sevensFreespinProgress.toBson() 68 | if not resp{"stages"}.isNil and not resp["stages"][0]{"field"}.isNil: 69 | slotState[$sdtLastField] = resp["stages"][0]["field"].toBson() 70 | #todo: make respins restore on client 71 | # slotState[$sdtRespinsCount] = resp{$srtRespinCount}.getInt(0).toBson() 72 | slotState[$sdtBet] = bet.toBson() 73 | slotState[$sdtCheatSpin] = null() 74 | slotState[$sdtCheatName] = null() 75 | 76 | const groovyDefaultZsm* = staticRead("../resources/slot_008_groovy.zsm") 77 | registerSlotMachine($groovySlot, newSlotMachineGroovy, groovyDefaultZsm) 78 | 79 | -------------------------------------------------------------------------------- /falconserver/boosters/boosters.nim: -------------------------------------------------------------------------------- 1 | import json, oids, times, sets, asyncdispatch 2 | import nimongo / [ mongo, bson ] 3 | 4 | import falconserver / common / [ bson_helper, db, game_balance, notifications ] 5 | import falconserver / auth / [ profile_types, profile_stats, profile ] 6 | import shafa / game / booster_types 7 | 8 | export BoosterTypes 9 | 10 | 11 | type Boosters* = ref object of RootObj 12 | profile: Profile 13 | data: Bson 14 | 15 | 16 | proc boosters*(p: Profile): Boosters = 17 | result = Boosters.new 18 | result.profile = p 19 | result.data = p.boostersData 20 | 21 | 22 | proc getData(b: Boosters, name: string): Bson = 23 | if not b.data.isNil and name in b.data: 24 | result = b.data[name] 25 | 26 | 27 | proc getOrCreateData(b: Boosters, name: string): Bson = 28 | if b.data.isNil: 29 | b.data = newBsonDocument() 30 | 31 | if name in b.data: 32 | result = b.data[name] 33 | else: 34 | result = newBsonDocument() 35 | b.data[name] = result 36 | 37 | 38 | proc add*(b: Boosters, name: string, t: float, isFree: bool = false): Future[void] {.async.} = 39 | var data = b.getOrCreateData(name) 40 | 41 | if $bfActiveUntil in data and epochTime() < data[$bfActiveUntil].toFloat64(): 42 | let endTime = data[$bfActiveUntil].toFloat64() + t 43 | data[$bfActiveUntil] = endTime.toBson() 44 | await b.profile.id.notifyBoosterTimeLeft(name, endTime) 45 | # echo "DDD adding time to booster ", name, " -> ", b.data 46 | elif $bfCharged in data: 47 | data[$bfCharged] = (data[$bfCharged].toFloat64() + t).toBson() 48 | # echo "DDD adding charge to booster ", name, " -> ", b.data 49 | else: 50 | data[$bfCharged] = t.toBson() 51 | # echo "DDD setting charge to booster ", name, " -> ", b.data 52 | 53 | if $bfCharged in data and isFree: 54 | data[$bfFree] = true.toBson() 55 | 56 | b.profile.boostersData = b.data # to mark changes 57 | # echo "DDD result = ", b.profile.boostersData 58 | 59 | 60 | proc start*(b: Boosters, name: string): Future[bool] {.async.} = 61 | # echo "DDD starting booster ", name 62 | let data = b.getData(name) 63 | 64 | if data.isNil or $bfCharged notin data or ($bfActiveUntil in data and epochTime() < data[$bfActiveUntil].toFloat64()): 65 | # echo "DDD invalid booster start -> ", b.profile.boostersData 66 | return false 67 | 68 | if $bfActiveUntil in data: 69 | data.del($bfActiveUntil) 70 | 71 | let endTime = epochTime() + data[$bfCharged].toFloat64() 72 | data[$bfActiveUntil] = endTime.toBson() 73 | data.del $bfCharged 74 | b.profile.boostersData = b.data # to mark changes 75 | await b.profile.id.notifyBoosterTimeLeft(name, endTime) 76 | result = true 77 | 78 | 79 | proc stateResp*(b: Boosters): JsonNode = 80 | if b.data.isNil: 81 | result = json.`%*`({}) 82 | else: 83 | result = b.data.toJson() 84 | 85 | 86 | proc ratesResp*(gconf: GameplayConfig, b: Boosters): JsonNode = 87 | result = newJObject() 88 | result[$btTournamentPoints] = %gconf.getGameBalance().boosters.tournamentPointsRate 89 | result[$btExperience] = %gconf.getGameBalance().boosters.experienceRate 90 | result[$btIncome] = %gconf.getGameBalance().boosters.incomeRate 91 | 92 | 93 | proc activeUntilT*(b: Boosters, name: string): float = 94 | let data = b.getData(name) 95 | if data.isNil or $bfActiveUntil notin data: 96 | result = 0 97 | else: 98 | result = data[$bfActiveUntil].toFloat64() 99 | 100 | 101 | proc isActive*(b: Boosters, name: string): bool = 102 | result = epochTime() <= b.activeUntilT(name) 103 | 104 | 105 | proc affectsTournaments*(b: Boosters): bool = 106 | b.isActive $btTournamentPoints 107 | 108 | 109 | proc affectsExperience*(b: Boosters): bool = 110 | b.isActive $btExperience 111 | 112 | # proc expRate*(b: Boosters): float = 113 | # if b.isActive($btExperience): 114 | # result = 1.5 115 | # else: 116 | # result = 1.0 117 | -------------------------------------------------------------------------------- /falconserver/map/map.nim: -------------------------------------------------------------------------------- 1 | import falconserver.auth.profile 2 | import falconserver.auth.profile_types 3 | import falconserver.map.building.builditem 4 | import falconserver.map.collect 5 | import falconserver.quest.quests_config 6 | import falconserver / common / [ bson_helper, notifications, game_balance, currency, get_balance_config ] 7 | import nimongo.bson 8 | import strutils, json, times, tables 9 | import shafa.game.feature_types 10 | 11 | type MapState = tuple[key: string, val: Bson] 12 | type StateFromQuest = proc(qc: QuestConfig, p: Profile): MapState 13 | 14 | 15 | proc toBson*(b: BuildItem): Bson= 16 | result = newBsonDocument() 17 | result["i"] = b.id.int.toBson() 18 | result["n"] = b.name.toBson() 19 | result["l"] = b.level.toBson() 20 | result["t"] = b.lastCollectionTime.toBson() 21 | 22 | proc toBuildItem*(b: Bson): BuildItem= 23 | let id = b["i"].toInt32().BuildingId 24 | 25 | result = newBuildItem(id) 26 | if "n" in b: 27 | result.name = b["n"].toString() 28 | 29 | result.level = b["l"].toInt32() 30 | result.lastCollectionTime = b["t"].toFloat64().float 31 | 32 | proc slotsBuilded*(p: Profile): seq[BuildingId]= 33 | result = @[] 34 | let clientStates = p[$prfState] 35 | if "slots" in clientStates: 36 | for bid in clientStates["slots"]: 37 | var bi = parseEnum[BuildingId](bid.toString(), noBuilding) 38 | if bi != noBuilding: 39 | result.add(bi) 40 | 41 | proc getInitialSlot*(p: Profile): BuildingId= 42 | let clientStates = p[$prfState] 43 | if not clientStates.isNil: 44 | let bInitial_slot = clientStates["initial_slot"] 45 | if not bInitial_slot.isNil: 46 | return bInitial_slot.toInt32().BuildingId 47 | result = anySlot 48 | 49 | proc setInitialSlotOnMap*(p: Profile, bi: BuildingId)= 50 | let key = "initial_slot" 51 | let val = bi.int.toBson() 52 | p.setClientState(key, val) 53 | 54 | proc buildSlot*():StateFromQuest= 55 | result = proc(qc: QuestConfig, p: Profile): MapState= 56 | var slotsState = p[$prfState]["slots"] 57 | if slotsState.isNil: 58 | slotsState = newBsonArray() 59 | 60 | let target = try: parseEnum[BuildingId](qc.target) 61 | except: noBuilding 62 | if target != noBuilding: 63 | discard slotsState.add(($target).toBson()) 64 | else: 65 | raise newException(Exception, "buildSlot unknown slot quest : " & qc.name & " with target " & qc.target) 66 | 67 | result.key = "slots" 68 | result.val = slotsState 69 | 70 | proc getSlotUnlock(p: Profile, name: string): StateFromQuest = 71 | for conf in getQuestConfigsForFeature(p.gconf.getStoryConfig(), FeatureType.Slot): 72 | if conf.name == name: 73 | return buildSlot() 74 | 75 | if name == "stadium_restore": 76 | return proc(qc: QuestConfig, p:Profile): MapState = 77 | result.key = "tournaments" 78 | result.val = true.toBson() 79 | 80 | if name == "bank_restore": 81 | return proc(qc: QuestConfig, p:Profile): MapState = 82 | result.key = "exchange" 83 | result.val = true.toBson() 84 | 85 | 86 | proc updateMapStateOnQuestComplete*(p: Profile, qc: QuestConfig) = 87 | var currency = qc.rewards.questIncomeCurrency() 88 | if currency != Currency.Unknown: 89 | let fullTime = p.resourceIncomeFullTime(currency) 90 | let ct = epochTime() 91 | var lct = p.lastCollectionTime(currency) 92 | if lct <= 1.0: # firstIncomeQuest 93 | lct = epochTime() 94 | 95 | if ct - lct < fullTime * 0.5: 96 | lct = ct - fullTime * 0.5 97 | 98 | p.setClientState(collectTimeKey(currency), lct.toBson()) 99 | let capTime = fullTime - (ct - lct) 100 | p.sheduleCollectNotification(currency, lct + capTime) 101 | 102 | let stateForClient = p.getSlotUnlock(qc.name) 103 | if not stateForClient.isNil: 104 | let (key, val) = stateForClient(qc, p) 105 | p.setClientState(key, val) 106 | -------------------------------------------------------------------------------- /falconserver/admin/slot_zsm_test.nim: -------------------------------------------------------------------------------- 1 | import json, tables, asyncdispatch, times, strutils, logging, threadpool, locks, asyncfile 2 | import nimongo / bson 3 | import shafa / slot / slot_data_types 4 | import falconserver / common / [ db, bson_helper ] 5 | import falconserver / auth / [ profile_types, profile, profile_random ] 6 | import falconserver / slot / [ machine_base, machine_base_server, machine_balloon_server, machine_classic_server, 7 | machine_candy_server, machine_ufo_server, machine_witch_server, machine_mermaid_server, machine_candy2_server, 8 | machine_groovy_server, machine_card_server ] 9 | 10 | import shafa / bot / [slot_statistic, slot_protocols] 11 | import falconserver / utils / asyncspawn 12 | 13 | 14 | proc performTest(machine: SlotMachine, name: string, zsm: JsonNode, spins: int): JsonNode {.gcsafe.} = 15 | let gameSlotID = zsm["slot"].getStr() 16 | let slotId = machine.getSlotID() 17 | 18 | let profile = newProfile(nil) 19 | profile.chips = 100_000_000_000 20 | var state = machine.createInitialState(profile) 21 | profile{$sdtSlotResult, slotId} = state 22 | 23 | var linesCoords: seq[seq[int]] = @[] 24 | let jLines = zsm["lines"] 25 | for jl in jLines: 26 | var l = newSeq[int]() 27 | for ji in jl: 28 | l.add(ji.getInt()) 29 | linesCoords.add(l) 30 | 31 | let lines = jLines.len 32 | let bet = 1_000 div lines 33 | var statistic = newStatistic(name, lines * bet, lines, spins, profile.chips.int64, gameSlotID, false, linesCoords) 34 | statistic.protocol = Protocols.new() 35 | statistic.protocol.readProtocols() 36 | 37 | var fst = 0 38 | 39 | for i in 0 ..< spins: 40 | var nextState: Bson 41 | var resp: JsonNode 42 | 43 | var jData = json.`%*`({$srtBet: bet, $srtLines: lines}) 44 | if gameSlotID == "cardSlot" and fst > 0: 45 | jData["data"] = %fst 46 | 47 | machine.getResponseAndState(profile, state, jData, resp, nextState) 48 | 49 | if gameSlotID == "cardSlot": 50 | var winFreespins = false 51 | if $srtWinFreespins in resp: 52 | winFreespins = resp[$srtWinFreespins].getBVal() 53 | 54 | if winFreespins: 55 | fst = profile.random(1..4) 56 | 57 | elif $sdtCardFreespinsType in resp: 58 | fst = resp[$sdtCardFreespinsType].getInt() 59 | 60 | statistic.parseResponce(resp, i + 1) 61 | 62 | profile{$sdtSlotResult, slotId} = nextState 63 | profile.chips = resp["chips"].getBiggestInt() 64 | state = nextState 65 | 66 | result = statistic.collect() 67 | 68 | 69 | proc startTest(machine: SlotMachine, name: string, zsm: JsonNode, spins: int, initialData: (string, JsonNode)) {.async.} = 70 | discard await sharedDB()["zsm"].update( 71 | bson.`%*`({"name": name}), 72 | bson.`%*`({"$set": {"stat." & initialData[0]: initialData[1].toBson()}}), 73 | false, true 74 | ) 75 | 76 | info "Start testing: ", name, ", ", initialData[0] 77 | 78 | var res = await asyncSpawn machine.performTest(name, zsm, spins) 79 | res["endDate"] = %epochTime() 80 | for k, v in initialData[1]: 81 | if k notin res: 82 | res[k] = v 83 | 84 | discard await sharedDB()["zsm"].update( 85 | bson.`%*`({"name": name}), 86 | bson.`%*`({"$set": {"stat." & initialData[0]: res.toBson()}}), 87 | false, false 88 | ) 89 | 90 | info "Complete testing: ", name, ", ", initialData[0] 91 | 92 | 93 | proc startTest*(name: string, zsm: JsonNode, spins: int): (string, JsonNode) = 94 | let gameSlotID = zsm["slot"].getStr() 95 | 96 | let initializer = slotMachineInitializers.getOrDefault(gameSlotID) 97 | if initializer.isNil: 98 | return 99 | 100 | let machine = initializer(zsm) 101 | 102 | let startDate = epochTime() 103 | result[0] = (startDate * 1_000_000).int64.toHex(13) 104 | result[1] = json.`%*`({"startDate": startDate}) 105 | 106 | asyncCheck machine.startTest(name, zsm, spins, result) 107 | -------------------------------------------------------------------------------- /falconserver/auth/profile_helpers.nim: -------------------------------------------------------------------------------- 1 | import json, math, strutils 2 | import asyncdispatch 3 | 4 | import falconserver.auth.profile 5 | export profile 6 | 7 | import falconserver / common / [ get_balance_config, game_balance ] 8 | import falconserver / quest / [ quest, quest_task ] 9 | import falconserver.map.building.builditem 10 | import falconserver.boosters.boosters 11 | import falconserver.free_rounds.free_rounds 12 | import falconserver.fortune_wheel.fortune_wheel 13 | import shafa / game / reward_types 14 | 15 | 16 | proc getLevelData*(p: Profile): JsonNode = 17 | let lvl = p.level 18 | result = newJObject() 19 | result["level"] = %p.level 20 | result["xpCur"] = %p.experience 21 | result["xpTot"] = %p.getGameBalance().levelProgress[lvl - 1].experience 22 | 23 | proc acceptFreeRoundsRewards(profile: Profile, ar: seq[Reward], resp: JsonNode) {.async.} = 24 | when defined(tests): 25 | return 26 | 27 | var freeRounds: FreeRounds 28 | 29 | for r in ar: 30 | if r.kind == RewardKind.freerounds: 31 | let reward = r.ZoneReward 32 | if freeRounds.isNil: 33 | freeRounds = await getOrCreateFreeRounds(profile.id) 34 | freeRounds.addFreeRounds(reward.zone, reward.amount.int) 35 | 36 | if not freeRounds.isNil: 37 | resp.updateWithFreeRounds(freeRounds) 38 | await freeRounds.save() 39 | 40 | proc acceptReward(p: Profile, r: Reward, resp: JsonNode): Future[void] {.async.} 41 | 42 | proc acceptRewards*(p: Profile, ar: seq[Reward], resp: JsonNode): Future[void] {.async.} = 43 | for r in ar: 44 | await p.acceptReward(r, resp) 45 | await p.acceptFreeRoundsRewards(ar, resp) 46 | 47 | proc acceptReward(p: Profile, r: Reward, resp: JsonNode): Future[void] {.async.} = 48 | let gb = p.getGameBalance() 49 | 50 | # if r.kind >= qrBoosterExp and r.kind <= qrBoosterAll: 51 | if r.isBooster(): 52 | let t = r.amount.float 53 | case r.kind: 54 | of RewardKind.boosterExp: 55 | await p.boosters.add($btExperience, t, isFree = true) 56 | of RewardKind.boosterIncome: 57 | await p.boosters.add($btIncome, t, isFree = true) 58 | of RewardKind.boosterTourPoints: 59 | await p.boosters.add($btTournamentPoints, t, isFree = true) 60 | of RewardKind.boosterAll: 61 | await p.boosters.add($btExperience, t, isFree = true) 62 | await p.boosters.add($btIncome, t, isFree = true) 63 | await p.boosters.add($btTournamentPoints, t, isFree = true) 64 | else: 65 | discard 66 | return 67 | 68 | # if r.kind == qrExp: 69 | if r.kind == RewardKind.exp: 70 | var gain: int = r.amount.int 71 | if p.boosters.affectsExperience(): 72 | gain = round(gain.float * p.getGameBalance().boosters.experienceRate).int 73 | 74 | var levelExp = gb.levelProgress[p.level - 1].experience 75 | var exp = p.experience + gain 76 | while exp >= levelExp: 77 | if p.level < gb.levelProgress.len: 78 | exp -= levelExp 79 | p.level = p.level + 1 80 | levelExp = gb.levelProgress[p.level - 1].experience 81 | else: 82 | exp = min(exp, levelExp) 83 | break 84 | p.experience = exp 85 | return 86 | 87 | if r.kind == RewardKind.wheel: 88 | p.addWheelFreeSpins(r.amount.int) 89 | return 90 | 91 | if r.isCurrencyReward(): 92 | p[$r.kind] = (p[$r.kind].toInt64() + r.amount).toBson() 93 | return 94 | 95 | 96 | proc createLevelUpQuest*(p: Profile): Quest= 97 | let gb = p.getGameBalance() 98 | let lvl = p.level 99 | if lvl < gb.levelProgress.len: 100 | let lvlData = gb.levelProgress[lvl - 1] 101 | let lvlRewardsData = gb.levelProgress[lvl] 102 | let task = createTask(qttLevelUp, @[lvlData.experience], noBuilding) 103 | 104 | result = createQuest(qttLevelUp.int, @[task]) 105 | result.data[$qfStatus] = QuestProgress.InProgress.int32.toBson() 106 | result.kind = QuestKind.LevelUp 107 | 108 | -------------------------------------------------------------------------------- /falconserver/tournament/tournaments_data.nim: -------------------------------------------------------------------------------- 1 | import tables,os,strutils,sequtils 2 | 3 | ## Holds configuration data for all slots which supports tournaments. 4 | 5 | type 6 | SlotTournamentData* = ref object of RootObj 7 | botSpan*: float 8 | botScores*: seq[int] 9 | botProbs*: seq[float] 10 | tournamentNames*: seq[string] 11 | tutorialTournamentName*: string 12 | 13 | proc `$`*(std: SlotTournamentData): string = 14 | result = """ 15 | botSpan: $#, 16 | botScores: $#, 17 | botProbs: $#, 18 | tournamentNames: $# 19 | """.format($std.botSpan, $std.botScores, $std.botProbs, $std.tournamentNames) 20 | 21 | var slots* = { 22 | "dreamTowerSlot": SlotTournamentData( 23 | botSpan: 5, 24 | botScores: @[0, 1, 2, 3, 4, 8, 10, 20, 30, 40], 25 | botProbs: @[0.73, 0.09, 0.06, 0.04, 0.05, 0.0092, 0.0136, 0.00, 0.013, 0.0013], 26 | tournamentNames: @[ 27 | "Eiffel Tower’s Event", 28 | "Artist’s Event", 29 | "Mademoiselle’s Event", 30 | "Sommelier's Event", 31 | "Scatter’s Event", 32 | "Tourist’s Event", 33 | "Dove’s Event", 34 | "Wild’s Event", 35 | "Lift Attendant's Event", 36 | "Garçon’s Event", 37 | "Chef’s Event", 38 | "Mime’s Event"], 39 | tutorialTournamentName: "Mime Challenger", 40 | ), 41 | "balloonSlot": SlotTournamentData( 42 | botSpan: 9, 43 | botScores: @[0, 1, 2, 3, 4, 8, 10, 20, 30, 40], 44 | botProbs: @[0.72, 0.07, 0.06, 0.04, 0.06, 0.01, 0.0255, 0.007, 0.0033, 0.0042], 45 | tournamentNames: @[ 46 | "Calm Event", 47 | "Breezy Event", 48 | "Draft Event", 49 | "Gust Event", 50 | "Monsoon Event", 51 | "Tropical Storm Event", 52 | "Windy Event", 53 | "Flurry Event", 54 | "Gale Event", 55 | "Storm Event", 56 | "Cyclone Event", 57 | "Hurricane Event"], 58 | tutorialTournamentName: "Helium Challenger", 59 | ), 60 | "candySlot": SlotTournamentData( 61 | botSpan: 5, 62 | botScores: @[0, 1, 2, 3, 4, 8, 10, 20], 63 | botProbs: @[0.76, 0.1, 0.065, 0.011, 0.024, 0.008, 0.025, 0.007], 64 | tournamentNames: @[ 65 | "Bubble Gum Event", 66 | "Candy Cane Event", 67 | "Fudge Event", 68 | "Jawbreaker Event", 69 | "Pez Event", 70 | "Taffy Event", 71 | "Cotton Candy Event", 72 | "Cupcake Event", 73 | "Gummi Bear Event", 74 | "Lollipop Event", 75 | "Licorice Event", 76 | "Marshmallow Event"], 77 | tutorialTournamentName: "Squib Challenger", 78 | ), 79 | "witchSlot": SlotTournamentData( 80 | botSpan: 5, 81 | botScores: @[0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 15], 82 | botProbs: @[0.6, 0.28, 0.018, 0.057, 0.013, 0.0106, 0.004, 0.0036, 0.0025, 0.004, 0.003, 0.0043], 83 | tournamentNames: @[ 84 | "Flying Broom Event", 85 | "Magic Spell Event", 86 | "Talking Wart Event", 87 | "Pointy Hat Event", 88 | "Slimy Toad Event", 89 | "Witching Hour Event", 90 | "Magic Wand Event", 91 | "Hocus-Pocus Event", 92 | "Love Potion Event", 93 | "Black Cat Event", 94 | "Bubbling Cauldron Event", 95 | "Book of Shadows Event"], 96 | tutorialTournamentName: "Spinner Challenger", 97 | ), 98 | "mermaidSlot": SlotTournamentData( 99 | botSpan: 5, 100 | botScores: @[0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 15], 101 | botProbs: @[0.84, 0.048, 0.017, 0.012, 0.011, 0.006, 0.01, 0.0056, 0.011, 0.0097, 0.0088, 0.012], 102 | tournamentNames: @[ 103 | "Starfish Event", 104 | "Dolphin Event", 105 | "Sea Turtle Event", 106 | "Sea Horse Event", 107 | "Neptune Event", 108 | "Pearl Event", 109 | "Necklace Event", 110 | "Fin Event", 111 | "Goldfish Event", 112 | "Ship Event", 113 | "Prince Event", 114 | "Treasure Chest Event"], 115 | tutorialTournamentName: "Crucian Challenger", 116 | ), 117 | }.toTable() 118 | 119 | # if getEnv("FALCON_ENV") == "stage": 120 | # ## For testing new slots which are not in production. 121 | # let stageSlots = { 122 | # }.toTable() 123 | 124 | # for key, value in stageSlots.pairs: 125 | # slots.add(key, value) 126 | 127 | echo "Tournaments enabled for slots ", toSeq(slots.keys) 128 | -------------------------------------------------------------------------------- /falconserver/auth/profile_types.nim: -------------------------------------------------------------------------------- 1 | 2 | import tables 3 | 4 | const QUEST_GEN_START_ID* = 100000 5 | 6 | type 7 | Achievement* = tuple[title: string, description: string] 8 | ## Achievement is granted for some long-time tasks 9 | 10 | ProfileFields* = enum 11 | ## Field names for storing into DB. 12 | ## Field names are minified for storage optimization 13 | ## (at least for MongoDB). 14 | prfId = "_id" ## Object ID 15 | prfPassword = "pw" 16 | prfDevices = "ds" ## Connected devices 17 | prfName = "n" ## User's name 18 | prfTitle = "t" ## User's title 19 | prfTimeZone = "tz" ## Time Zone 20 | 21 | prfBucks = "cb" ## Bucks - premium currency 22 | prfChips = "cc" ## Chips - ordinary currency for betting on slot 23 | prfParts = "cp" ## Parts - currency for build building 24 | prfExchangeNum = "ce" ## Currency exchange nums for chips and parts - auto-incremented on each exchange 25 | 26 | prfCheats = "cs" ## Special sequence of spin responses for testing 27 | 28 | prfTourPoints = "tp" ## Tournament points 29 | prfPvpPoints = "pp" ## PvP Points 30 | 31 | prfExperience = "x" ## Experience (city points) 32 | prfLevel = "l" ## Level (increases depending on experience) 33 | prfVipPoints = "vp" ## VIP points (for currency exchange mechanics) 34 | prfVipLevel = "vl" ## VIP level (for currency exchange mechanics) 35 | 36 | prfFrame = "f" ## Frame around user's portrait on profile view 37 | prfPortrait = "p" ## User's portrait (either from Facebook or predefined one) 38 | prfMessages = "rs" ## Now its message system 39 | prfAchieves = "as" ## Large rewards for doind bunch of quest goals 40 | 41 | prfState = "m" ## User's profile state 42 | 43 | prfQuests = "qs" ## Current quests for user 44 | prfNextExchangeDiscountTime = "qt" ## Next time exchange may be done with discount 45 | prfQuestsGenId = "qi" ## Last quest counter 46 | 47 | prfTutorial = "tu" ## Player's tutorial scenario 48 | prfTutorialState = "ts" ## Player's tutorial progress 49 | prfIntroSlot = "is" ## Player's intro slot chosen for A/B testing 50 | 51 | prfFBToken = "fb" ## Facebook Client's Auth Token 52 | prfVersion = "v" ## Profile version, used for migration from older to newer 53 | prfStatistics = "s" ## User's statistics: spins, realmoney spend etc 54 | prfNameChanged = "nc" ## User's name changing counter 55 | 56 | prfTaskStageOld = "aa" ## Stage level for tasks on slots, contains stage level as int and array of quest ids. 57 | ## Left for backward compatibility, should be replaced with usage of prfSlotQuests 58 | prfSlotQuests = "sq" ## Slot Task stages, containing stage level and task ID for each slot 59 | prfIsBro = "ib" ## Determines user is cheater 60 | 61 | prfFortuneWheel = "fw" ## Wheel of Fortune state 62 | 63 | prfSpecialOffer = "sf" ## Special offer purchase bool state 64 | 65 | prfBoosters = "b" ## Boosters 66 | 67 | prfABTestConfig = "ab" ## Name of AB test configuration 68 | 69 | prfAndroidPushToken = "ap" 70 | prfIosPushToken = "ip" 71 | prfSessionPlatform = "sp" ## Platform with which user was logged in this session. 72 | 73 | StateFields* = enum 74 | steResources = "r" 75 | steSlots = "s" 76 | steClient = "c" 77 | 78 | ProfileFrame* = enum 79 | ## Frame is changeable and/or buyable asset 80 | pfFrame0 = 0 81 | pfFrame1 82 | pfFrame2 83 | pfFrame3 84 | pfFrame4 85 | pfFrame5 86 | pfFrame6 87 | pfFrame7 88 | 89 | ProfilePortrait* = enum 90 | ## Player can either choose from 4 prerendered male 91 | ## or female portraits, or use his/her Facebook 92 | ## avatar for portrait if loggede in via Facebook 93 | ppNotSet = -2 94 | ppFacebook = -1 95 | ppMale1 = 0 96 | ppMale2 97 | ppMale3 98 | ppMale4 99 | ppFemale1 100 | ppFemale2 101 | ppFemale3 102 | ppFemale4 103 | 104 | LinkFacebookType* = enum 105 | lftNone = "none" 106 | lftDevice = "device" 107 | lftFacebook = "facebook" 108 | 109 | LinkFacebookResults* = enum 110 | larHasBeenLinked 111 | larCantBeLinked 112 | larCollision 113 | larHasBeenAlreadyLinked 114 | 115 | LinkFacebookRestartApp* = bool 116 | 117 | const 118 | TUTORIAL_FINISHED* = -1 119 | TUTORIAL_STARTED* = 0 120 | TUTORIAL_INTRO* = 1 121 | TUTORIAL_INTRO_SLOT* = 2 122 | TUTORIAL_INTRO2* = 3 123 | 124 | TUTORIAL_LAST* = 4 125 | 126 | const MongoCollectionProfiles*: string = "profiles" 127 | const MongoCollectionSavedProfiles*: string = "saved_profiles" 128 | const MongoCollectionCheaters*: string = "cheater_profiles" 129 | 130 | const Achievements*: OrderedTable[string, Achievement] = { 131 | "achFirstBlood": ("First Blood", "Build 3 Slot Buildings").Achievement 132 | }.toOrderedTable() 133 | -------------------------------------------------------------------------------- /falconserver/quest/quest_task.nim: -------------------------------------------------------------------------------- 1 | import quest_task_decl 2 | export quest_task_decl 3 | 4 | import nimongo.bson 5 | import json 6 | import falconserver.map.building.builditem 7 | import times, tables, strutils 8 | import falconserver.auth.profile_types 9 | import falconserver.common.game_balance 10 | import quest_types 11 | 12 | import falconserver.auth.gameplay_config_decl 13 | 14 | var progressDBWorkers* = initTable[QuestTaskType, proc(gconf: GameplayConfig, qt: QuestTask, data: Bson)]() 15 | var progressResponseWorkers* = initTable[QuestTaskType, proc(gconf: GameplayConfig, qt: QuestTask, data: JsonNode)]() 16 | 17 | proc toBson*(qt: QuestTask): Bson= 18 | result = newBsonDocument() 19 | result[$qtfType] = ($qt.kind).toBson() 20 | result[$qtfObject] = ($qt.target).toBson() 21 | result[$qtfProgress] = newBsonArray() 22 | result[$qtfDifficulty] = qt.difficulty.int.toBson() 23 | 24 | if qt.prevStage.len > 0: 25 | result[$qtfStage] = qt.prevStage.toBson() 26 | 27 | for tp in qt.progresses: 28 | var btp = newBsonDocument() 29 | btp[$qtfCurrentProgress] = tp.current.int64.toBson() 30 | btp[$qtfTotalProgress] = tp.total.int64.toBson() 31 | btp[$qtfProgressIndex] = tp.index.int.toBson() 32 | result[$qtfProgress].add(btp) 33 | 34 | proc loadTask*(data: Bson, id: int): QuestTask = 35 | if not data.isNil(): 36 | result.new() 37 | result.kind = parseQuestTaskType(data[$qtfType].toString(), id) 38 | result.target = parseEnum[BuildingId](data[$qtfObject].toString()) 39 | result.progresses = @[] 40 | if $qtfProgress in data: 41 | for bp in data[$qtfProgress]: 42 | var tp = new(TaskProgress) 43 | tp.current = bp[$qtfCurrentProgress].toInt64().uint64 44 | tp.total = bp[$qtfTotalProgress].toInt64().uint64 45 | tp.index = bp[$qtfProgressIndex].toInt32().uint 46 | result.progresses.add(tp) 47 | 48 | if not data{$qtfDifficulty}.isNil: 49 | result.difficulty = data[$qtfDifficulty].toInt32().DailyDifficultyType 50 | 51 | if not data{$qtfStage}.isNil: 52 | result.prevStage = data[$qtfStage].toString() 53 | 54 | proc createTask*(t: QuestTaskType, taskTotalProgs: seq[int | int32 | int64], target: BuildingId, diff: DailyDifficultyType = trivial): QuestTask = 55 | result.new() 56 | result.kind = t 57 | result.target = target 58 | result.progresses = @[] 59 | result.difficulty = diff 60 | 61 | for i, p in taskTotalProgs: 62 | var taskP = new(TaskProgress) 63 | taskP.index = i.uint 64 | taskP.current = 0 65 | taskP.total = p.uint64 66 | result.progresses.add(taskP) 67 | 68 | proc currentProgress*(t: QuestTask): float = 69 | var n = 0 70 | for p in t.progresses: 71 | if p.total != 0: 72 | result += p.current.int / p.total.int 73 | inc n 74 | if n != 0: 75 | result /= n.float 76 | 77 | proc setCurrentProgress*(t: QuestTask, p: uint64|int64|int, index: uint = 0) = 78 | for tp in t.progresses: 79 | if tp.index == index: 80 | if tp.current != p.uint64: 81 | tp.current = p.uint64 82 | t.progressState = qtpHasProgress 83 | 84 | if tp.current >= tp.total: 85 | tp.current = tp.total 86 | t.progressState = qtpCompleted 87 | break 88 | 89 | proc completeProgress*(t: QuestTask, index: uint = 0)= 90 | for tp in t.progresses: 91 | if tp.index == index: 92 | tp.current = tp.total 93 | t.progressState = qtpCompleted 94 | break 95 | 96 | proc incProgress*(t: QuestTask, amount: uint64|int64|int, index: uint = 0) = 97 | var taskP: TaskProgress 98 | for tp in t.progresses: 99 | if tp.index == index: 100 | taskP = tp 101 | break 102 | 103 | if not taskP.isNil: 104 | t.setCurrentProgress(taskP.current + amount.uint64, index) 105 | 106 | 107 | proc progress*(gconf: GameplayConfig, t: QuestTask, data: Bson): QuestTaskProgress= 108 | if t.currentProgress() >= 1.0 and (t.prevStage.len == 0 or t.prevStage == "Spin"): 109 | result = qtpCompleted 110 | else: 111 | let progressDbWorker = progressDBWorkers.getOrDefault(t.kind) 112 | if not progressDbWorker.isNil: 113 | gconf.progressDbWorker(t, data) 114 | 115 | result = t.progressState 116 | 117 | 118 | proc getCurrentStage*(t: QuestTask, data: JsonNode): string= 119 | let resp = data["res"] 120 | var stages = resp{"stages"} 121 | if not stages.isNil and stages.len > 0: 122 | result = stages[0]["stage"].getStr() 123 | var fc = -1 124 | 125 | if "fc" in resp: 126 | fc = resp["fc"].getInt() 127 | 128 | elif "freeSpinsCount" in resp: # balloon slot 129 | fc = resp["freeSpinsCount"].getInt() 130 | 131 | if result == "FreeSpin" or fc > 0: 132 | var lastFsC = 1 133 | if t.target in [candySlot, witchSlot]: 134 | lastFsC = 0 135 | 136 | if fc == lastFsC: 137 | result = "Spin" 138 | else: 139 | result = "FreeSpin" 140 | 141 | elif result == "Bonus": #hello balloon slot 142 | result = "Spin" 143 | else: 144 | result = "Spin" 145 | 146 | proc registerTask*(qtt: QuestTaskType, callback:proc(gconf: GameplayConfig, qt: QuestTask, data: Bson))= 147 | progressDBWorkers[qtt] = callback 148 | 149 | proc registerTask*(qtt: QuestTaskType, callback:proc(gconf: GameplayConfig, qt: QuestTask, data: JsonNode))= 150 | progressResponseWorkers[qtt] = callback 151 | -------------------------------------------------------------------------------- /integrational_tests.nim: -------------------------------------------------------------------------------- 1 | import asyncdispatch 2 | import json 3 | import httpclient 4 | import tables 5 | import strtabs 6 | import unittest 7 | import times 8 | 9 | const 10 | ProtocolVersion = 1 11 | BaseUrl = "http://localhost:5001" 12 | 13 | proc buildFalconClient(protocolVersion: int, devId: string = nil, sessionId: string = nil): AsyncHttpClient = 14 | result = newAsyncHttpClient() 15 | result.headers["Falcon-Proto-Version"] = $protocolVersion 16 | if not devId.isNil(): 17 | result.headers["Falcon-Device-Id"] = devId 18 | if not sessionId.isNil(): 19 | result.headers["Falcon-Session-Id"] = sessionId 20 | 21 | suite "Test Error Resilience": 22 | echo "\n General Resilience Behaviour\n" 23 | 24 | setup: 25 | var client = buildFalconClient(ProtocolVersion) 26 | 27 | test "Test non-existing URL": 28 | let r = waitFor(client.request(BaseUrl & "/bad_url", httpGet, "{}")) 29 | check: 30 | r.status == "404 Not Found" 31 | 32 | 33 | suite "Test Authentication": 34 | echo "\n Authentication\n" 35 | 36 | let devId = $getTime() 37 | 38 | setup: 39 | var client = buildFalconClient(ProtocolVersion, devId) 40 | 41 | test "Test Login With Device ID (new and existing cases)": 42 | var 43 | r: Response 44 | jr: JsonNode 45 | 46 | r = waitFor(client.request(BaseUrl & "/auth/login/device", httpPost, "{}")) 47 | jr = parseJson(r.body) 48 | check: 49 | r.status == "200 OK" 50 | jr.hasKey("sessionId") 51 | jr.hasKey("serverTime") 52 | 53 | suite "Test Map": 54 | echo "\n Map\n" 55 | 56 | let devId = $getTime() 57 | 58 | setup: 59 | var client = buildFalconClient(ProtocolVersion, devId) 60 | 61 | test "Get Map And Create (new user)": 62 | let sessionId = parseJson(waitFor(client.request(BaseUrl & "/auth/login/device", httpPost, "{}")).body)["sessionId"].getStr() 63 | 64 | client.headers["Falcon-Device-Id"] = "" 65 | client.headers["Falcon-Session-Id"] = sessionId 66 | let map = parseJson(waitFor(client.request(BaseUrl & "/map/get", httpGet, "{}")).body) 67 | 68 | check: 69 | map.hasKey("spots") 70 | map.hasKey("serverTime") 71 | 72 | test "Get Map And Load (existing user)": 73 | client.headers["Falcon-Session-Id"] = "" 74 | let sessionId = parseJson(waitFor(client.request(BaseUrl & "/auth/login/device", httpPost, "{}")).body)["sessionId"].getStr() 75 | 76 | client.headers["Falcon-Session-Id"] = sessionId 77 | let map = parseJson(waitFor(client.request(BaseUrl & "/map/get", httpGet, "{}")).body) 78 | 79 | check: 80 | map.hasKey("spots") 81 | map.hasKey("serverTime") 82 | 83 | 84 | suite "[Eiffel Slot] Spin Test": 85 | echo "\n Eiffel Slot\n" 86 | 87 | let devId = $getTime() 88 | 89 | setup: 90 | var client = buildFalconClient(ProtocolVersion, devId) 91 | 92 | test "[Eiffel Slot] Spinning With Bad JSON": 93 | let r = waitFor(client.request(BaseUrl & "/slot/spin/dreamTowerSlot", httpPost, "asdasdsa}'")) 94 | checkpoint($r) 95 | check r.status == "502 Bad Gateway" 96 | 97 | test "[Eiffel Slot] Spin Unauthorized": 98 | let r = waitFor(client.request(BaseUrl & "/slot/spin/dreamTowerSlot", httpPost, $(%*{"bet": 20, "lines": 20}))) 99 | check r.status == "502 Bad Gateway" 100 | 101 | test "[Eiffel Slot] Spin Success": 102 | let sessionId = parseJson(waitFor(client.request(BaseUrl & "/auth/login/device", httpPost, "{}")).body)["sessionId"].getStr() 103 | 104 | client.headers["Falcon-Session-Id"] = sessionId 105 | let r = waitFor(client.request(BaseUrl & "/slot/spin/dreamTowerSlot", httpPost, $(%*{"bet": 20, "lines": 20}))) 106 | check r.status == "200 OK" 107 | 108 | let jr = parseJson(r.body) 109 | checkpoint($jr) 110 | check: 111 | jr.hasKey("chips") 112 | jr.hasKey("stages") 113 | jr.hasKey("serverTime") 114 | 115 | 116 | suite "[Balloon Slot] Spin Test": 117 | echo "\n Balloon Slot\n" 118 | 119 | let devId = $getTime() 120 | 121 | setup: 122 | var client = buildFalconClient(ProtocolVersion, devId) 123 | 124 | test "[Balloon Slot] Spin Success": 125 | let sessionId = parseJson(waitFor(client.request(BaseUrl & "/auth/login/device", httpPost, "{}")).body)["sessionId"].getStr() 126 | 127 | client.headers["Falcon-Session-Id"] = sessionId 128 | let r = waitFor(client.request(BaseUrl & "/slot/spin/balloonSlot", httpPost, $(%*{"bet": 20, "lines": 15}))) 129 | check r.status == "200 OK" 130 | 131 | let jr = parseJson(r.body) 132 | checkpoint($jr) 133 | check: 134 | jr.hasKey("stages") 135 | jr.hasKey("serverTime") 136 | 137 | 138 | suite "[Candy Slot] Spin Test": 139 | echo "\n Candy Slot\n" 140 | 141 | let devId = $getTime() 142 | 143 | setup: 144 | var client = buildFalconClient(ProtocolVersion, devId) 145 | 146 | test "[Candy Slot] Spin Success": 147 | let sessionId = parseJson(waitFor(client.request(BaseUrl & "/auth/login/device", httpPost, "{}")).body)["sessionId"].getStr() 148 | 149 | client.headers["Falcon-Session-Id"] = sessionId 150 | let r = waitFor(client.request(BaseUrl & "/slot/spin/candySlot", httpPost, $(%*{"bet": 20, "lines": 15}))) 151 | check r.status == "200 OK" 152 | 153 | let jr = parseJson(r.body) 154 | checkpoint($jr) 155 | check: 156 | jr.hasKey("stages") 157 | jr.hasKey("serverTime") 158 | jr["stages"].len() >= 1 159 | 160 | echo "" 161 | -------------------------------------------------------------------------------- /falconserver/auth/session.nim: -------------------------------------------------------------------------------- 1 | import json, oids, times, sets, logging, asyncdispatch, asynchttpserver, strutils 2 | 3 | import nimongo / [ mongo, bson ] 4 | 5 | import falconserver / common / [ response, db, get_balance_config, config ] 6 | 7 | import profile, profile_types 8 | 9 | type 10 | Session* = ref object of RootObj 11 | profile*: Profile ## User's profile cache 12 | 13 | 14 | type ClientRequest* = ref object 15 | session*: Session 16 | body*: JsonNode 17 | status*: StatusCode 18 | reason*: string 19 | clientVersion*: string 20 | 21 | 22 | proc findProfile*(key: string, value: Bson): Future[Profile] {.async.} = 23 | # Should not be used directly 24 | let profileBson = await profilesDB().find(B(key, value)).oneOrNone() 25 | if not profileBson.isNil: 26 | result = newProfile(profilesDB(), profileBson) 27 | 28 | proc findProfileBySessionId(id: Oid): Future[Profile] {.inline.} = 29 | # Support for old protocol. Should be removed eventually. 30 | findProfile("session", id.toBson()) 31 | 32 | proc findProfileById*(id: Oid): Future[Profile] {.inline.} = 33 | findProfile("_id", id.toBson()) 34 | 35 | 36 | 37 | 38 | proc getSession*(r: Request): Future[Session] {.async.} = 39 | ## Returns profile for sessionId in request, or raises if not found 40 | let sessId = string(r.headers.getOrDefault("Falcon-Session-Id")) 41 | if sessId.len == 0: 42 | raise newException(KeyError, "No session id in request") 43 | 44 | let profileId = string(r.headers.getOrDefault("Falcon-Prof-Id")) 45 | var p: Profile 46 | if profileId.len > 0: 47 | p = await findProfileById(parseOid(profileId)) 48 | if not p.isNil: 49 | if p["session"].toOid != parseOid(sessId): 50 | info "wrong sessId ", sessId, " for profile ", profileId 51 | p = nil 52 | else: 53 | p = await findProfileBySessionId(parseOid(sessId)) 54 | 55 | if p.isNil: 56 | info "Profile for session id not found ", sessId 57 | return 58 | 59 | result.new() 60 | 61 | await p.loadGameBalance() 62 | 63 | result.profile = p 64 | result.profile.prevRequestTime = result.profile.statistics.lastRequestTime 65 | result.profile.statistics.lastRequestTime = epochTime() 66 | 67 | template clientVersion*(request: Request): string = 68 | request.headers.getOrDefault("Falcon-Client-Version", @["0"].HttpHeaderValues).toString() 69 | 70 | const OLD_CLIENT_VERSION = (status: StatusCode.IncorrectMinClientVersion, reason: "Unsupported client version") 71 | proc isClientVersionAllowable*(clientVersion: string, req: Request): bool = 72 | try: 73 | if clientVersion == NO_MAINTENANCE_CLIENT_VERSION: 74 | result = true 75 | if clientVersion.isDigit() and parseInt(clientVersion) >= sharedGameConfig().minClientVersion: 76 | result = true 77 | except: 78 | discard 79 | proc oldClientVersion*(res: ClientRequest) = 80 | res.status = OLD_CLIENT_VERSION.status 81 | res.reason = OLD_CLIENT_VERSION.reason 82 | proc oldClientVersion*(res: JsonNode) = 83 | res["status"] = %OLD_CLIENT_VERSION.status.int 84 | res["reason"] = %OLD_CLIENT_VERSION.reason 85 | proc oldClientVersion*(): JsonNode = 86 | result = newJObject() 87 | result.oldClientVersion() 88 | 89 | 90 | const MAINTENANCE_IN_PROGRESS = (status: StatusCode.MaintenanceInProgress, reason: "Maintenance in progress") 91 | proc isMaintenanceInProgress*(clientVersion: string, req: Request): bool = 92 | if clientVersion == NO_MAINTENANCE_CLIENT_VERSION: 93 | result = false 94 | elif sharedGameConfig().maintenanceTime != 0.0 and epochTime() >= sharedGameConfig().maintenanceTime: 95 | result = true 96 | proc maintenanceInProgress*(res: ClientRequest) = 97 | res.status = MAINTENANCE_IN_PROGRESS.status 98 | res.reason = MAINTENANCE_IN_PROGRESS.reason 99 | res.body = %{"maintenanceTime": %sharedGameConfig().maintenanceTime} 100 | proc maintenanceInProgress*(res: JsonNode) = 101 | res["status"] = %MAINTENANCE_IN_PROGRESS.status.int 102 | res["reason"] = %MAINTENANCE_IN_PROGRESS.reason 103 | res["maintenanceTime"] = %sharedGameConfig().maintenanceTime 104 | proc maintenanceInProgress*(): JsonNode = 105 | result = newJObject() 106 | result.maintenanceInProgress() 107 | 108 | proc checkRequest*(request: Request): Future[ClientRequest] {.async.}= 109 | result = ClientRequest.new() 110 | result.clientVersion = request.clientVersion() 111 | 112 | if isMaintenanceInProgress(result.clientVersion, request): 113 | result.maintenanceInProgress() 114 | return 115 | 116 | if not isClientVersionAllowable(result.clientVersion, request): 117 | result.oldClientVersion() 118 | return 119 | 120 | try: 121 | let ses = await getSession(request) 122 | if ses.isNil: 123 | result.status = StatusCode.IncorrectSession 124 | result.reason = "Session does't exist" 125 | return result 126 | 127 | else: 128 | result.status = StatusCode.OK 129 | result.session = ses 130 | 131 | except: 132 | let excMsg = getCurrentExceptionMsg() 133 | result.status = StatusCode.IncorrectSession 134 | result.reason = excMsg 135 | 136 | try: 137 | let jobj = try: parseJson(request.body) 138 | except: nil 139 | if jobj == nil: 140 | result.status = StatusCode.InvalidRequest 141 | result.reason = "Request body is nil" 142 | return result 143 | 144 | else: 145 | result.status = StatusCode.OK 146 | result.body = jobj 147 | 148 | except: 149 | echo "checkRequest exception ", getCurrentExceptionMsg() 150 | result.status = StatusCode.InvalidRequest 151 | result.reason = getCurrentExceptionMsg() 152 | -------------------------------------------------------------------------------- /falconserver/free_rounds/free_rounds.nim: -------------------------------------------------------------------------------- 1 | import oids, json 2 | import falconserver / auth / [ profile, profile_types ] 3 | import shafa / slot / slot_data_types 4 | import falconserver / common / [ db, orm ] 5 | import nimongo / [ bson, mongo ] 6 | 7 | 8 | proc freeRoundsDB*(): Collection[AsyncMongo] = 9 | result = sharedDB()["free_rounds"] 10 | 11 | 12 | type FreeRoundsFields = enum 13 | frfRounds = "r" 14 | frfRoundsCount = "rc" 15 | ftfSpin = "s" 16 | ftfReward = "re" 17 | 18 | 19 | TransparentObj FreeRounds: 20 | id: Oid($prfId, "id") 21 | slotsState: Bson($sdtSlotResult) 22 | rounds: Bson($frfRounds) 23 | 24 | 25 | proc newFreeRounds(b: Bson): FreeRounds = 26 | result.new 27 | result.init(freeRoundsDB(), b) 28 | 29 | 30 | proc newFreeRounds(id: Oid): FreeRounds = 31 | newFreeRounds(bson.`%*`({ 32 | $prfId: id, 33 | $sdtSlotResult: newBsonDocument(), 34 | $frfRounds: newBsonDocument() 35 | })) 36 | 37 | 38 | proc findFreeRounds*(id: Oid): Future[FreeRounds] {.async.} = 39 | try: 40 | let dbResult = await freeRoundsDB().find(bson.`%*`({$prfId: id})).one() 41 | result = newFreeRounds(dbResult) 42 | except: 43 | result = nil 44 | 45 | 46 | proc getOrCreateFreeRounds*(id: Oid): Future[FreeRounds] {.async.} = 47 | result = await findFreeRounds(id) 48 | if result.isNil: 49 | result = newFreeRounds(id) 50 | 51 | 52 | proc freeRoundsSpinIsValid*(profileId: Oid, gameSlotId: string, jData: JsonNode): Future[(JsonNode, FreeRounds)] {.async.} = 53 | let mode = jData{"mode"}{"kind"}.getInt() 54 | if mode != smkFreeRound.int: 55 | return 56 | 57 | let freeRounds = await profileId.findFreeRounds() 58 | if freeRounds.isNil: 59 | result[0] = %{"reason": %("Free rounds for user `" & $profileId & "` has not been found")} 60 | return 61 | 62 | let stats = freeRounds.rounds{gameSlotId} 63 | if stats.isNil: 64 | result[0] = %{"reason": %("Free rounds for user `" & $profileId & "` and slot `" & gameSlotId & "` has not been found")} 65 | return 66 | 67 | let limit = stats[$frfRoundsCount].toInt() 68 | let spin = stats[$ftfSpin].toInt() 69 | 70 | if limit == 0: 71 | result[0] = %{"reason": %("User `" & $profileId & "` on slot `" & gameSlotId & "` hasn't free rounds")} 72 | return 73 | 74 | result[1] = freeRounds 75 | 76 | 77 | proc addFreeRounds*(freeRounds: FreeRounds, gameSlotId: string, count: int) = 78 | let stats = freeRounds.rounds{gameSlotId} 79 | if stats.isNil: 80 | freeRounds.rounds{gameSlotId} = bson.`%*`({$frfRoundsCount: count, $ftfSpin: 0, $ftfReward: 0}) 81 | else: 82 | stats[$frfRoundsCount] = (stats[$frfRoundsCount].toInt() + count).toBson() 83 | 84 | 85 | proc incFreeRoundSpins*(freeRounds: FreeRounds, gameSlotId: string) = 86 | freeRounds.rounds{gameSlotId, $ftfSpin} = (freeRounds.rounds{gameSlotId, $ftfSpin}.toInt() + 1).toBson() 87 | 88 | 89 | proc incFreeRoundReward*(freeRounds: FreeRounds, gameSlotId: string, reward: int64) = 90 | freeRounds.rounds{gameSlotId, $ftfReward} = (freeRounds.rounds{gameSlotId, $ftfReward}.toInt64() + reward).toBson() 91 | 92 | 93 | proc rounds*(freeRounds: FreeRounds, gameSlotId: string): int = 94 | freeRounds.rounds{gameSlotId, $ftfSpin}.toInt() 95 | 96 | 97 | proc roundsCount*(freeRounds: FreeRounds, gameSlotId: string): int = 98 | freeRounds.rounds{gameSlotId, $frfRoundsCount}.toInt() 99 | 100 | 101 | const FREE_ROUNDS_JSON_KEY* = "freeRounds" 102 | proc toJson*(freeRounds: FreeRounds): JsonNode = 103 | result = newJObject() 104 | for key, value in freeRounds.rounds: 105 | let rounds = value[$frfRoundsCount].toInt() 106 | result[key] = json.`%*`({ 107 | "rounds": rounds, 108 | "passed": value[$ftfSpin].toInt(), 109 | "reward": value[$ftfReward].toInt64() 110 | }) 111 | 112 | 113 | proc getFreeRoundReward*(freeRounds: FreeRounds, gameSlotId: string): Future[tuple[err: JsonNode, reward: int64]] {.async.} = 114 | if freeRounds.isNil: 115 | result[0] = %{"reason": %("Rewards for user `" & $freeRounds.id & "` and slot `" & gameSlotId & "` have not been found")} 116 | return 117 | 118 | let stats = freeRounds.rounds{gameSlotId} 119 | if stats.isNil: 120 | result[0] = %{"reason": %("Rewards for user `" & $freeRounds.id & "` and slot `" & gameSlotId & "` have not been found")} 121 | return 122 | 123 | let rounds = stats{$frfRoundsCount}.toInt() 124 | if rounds <= 0: 125 | result[0] = %{"reason": %("Rewards for user `" & $freeRounds.id & "` and slot `" & gameSlotId & "` have not been found")} 126 | return 127 | 128 | if rounds > stats{$ftfSpin}.toInt(): 129 | result[0] = %{"reason": %("Free rounds for user `" & $freeRounds.id & "` and slot `" & gameSlotId & "` have not been completed jet")} 130 | return 131 | 132 | result[1] = stats{$ftfReward}.toInt64() 133 | 134 | stats{$frfRoundsCount} = 0.toBson() 135 | stats{$ftfSpin} = 0.toBson() 136 | stats{$ftfReward} = 0.toBson() 137 | 138 | let reqset = newBsonDocument() 139 | reqset[$frfRounds & "." & gameSlotId] = stats 140 | 141 | let resp = await freeRounds.collection.update(bson.`%*`({"_id": freeRounds.id}), bson.`%*`({"$set": reqset}), false, true) 142 | checkMongoReply(resp) 143 | 144 | 145 | proc save*(freeRounds: FreeRounds) {.async.} = 146 | let resp = await freeRounds.collection.update(bson.`%*`({"_id": freeRounds.id}), freeRounds.bson, false, true) 147 | checkMongoReply(resp) 148 | 149 | 150 | proc updateWithFreeRounds*(resp: JsonNode, freeRounds: FreeRounds) = 151 | let value = freeRounds.toJson() 152 | if value.len > 0: 153 | resp[FREE_ROUNDS_JSON_KEY] = value 154 | 155 | 156 | proc updateWithFreeRounds*(resp: JsonNode, id: Oid) {.async.} = 157 | let freeRounds = await findFreeRounds(id) 158 | if not freeRounds.isNil: 159 | resp.updateWithFreeRounds(freeRounds) -------------------------------------------------------------------------------- /falconserver/slot/machine_ufo_bonus.nim: -------------------------------------------------------------------------------- 1 | import sequtils, tables 2 | import slot_data_types 3 | import json, strutils 4 | import falconserver.auth.profile_random 5 | import machine_ufo_types 6 | 7 | const PIPES_ON_FIELD = PIPES_IN_ROW * PIPES_IN_COLUMN 8 | const CROSS_PIPE_POS = 3 9 | const DOUBLE_WIN_PIPE_POS = 5 10 | const TOTAL_PIPES = 7 11 | 12 | const KEY_WIN = "w" 13 | const KEY_DOUBLE = "d" 14 | const KEY_FILLORDER = "fo" 15 | 16 | proc newFillInfo(pos:int8,animDir:MilkFillDirection): FillInfo = 17 | result.new() 18 | result.pipePos = pos 19 | result.fillAnimDirection = animDir 20 | 21 | proc newBonusSpinResult*(): BonusSpinResult = 22 | result.new() 23 | # random field with crossPipe at left mid pos. 24 | result.field = newSeq[int8](PIPES_ON_FIELD) 25 | for i in 0.. 0: 112 | var nextFilledPipes = newSeq[FillInfo]() 113 | for i,s in bsr.stateOfFullness: 114 | if not s: 115 | let fi = bsr.willGetMilk(i.int8) 116 | if not fi.isNil: 117 | nextFilledPipes.add(fi) 118 | 119 | lastFilledPipes = nextFilledPipes.len 120 | if lastFilledPipes > 0: 121 | for fi in nextFilledPipes: 122 | bsr.stateOfFullness[fi.pipePos] = true 123 | bsr.fillOrder.add(nextFilledPipes) 124 | 125 | proc fillPipesWithMilk*(ubd:UfoBonusData) = 126 | for i,sr in ubd.spinResults: 127 | sr.createPipesFillOrder() 128 | for pipes in sr.fillOrder: 129 | sr.win += pipes.len * ubd.totalBet 130 | if sr.checkDoubleWin(): 131 | sr.win *= 2 132 | ubd.totalWin += sr.win 133 | 134 | 135 | proc toJson*(bsr:BonusSpinResult): JsonNode = 136 | result = newJObject() 137 | var field = newJArray() 138 | for p in bsr.field: 139 | field.add(%p) 140 | result["field"] = field 141 | var fillOrder = newJArray() 142 | for fillInfos in bsr.fillOrder: 143 | var nextPipes = newJArray() 144 | for fi in fillInfos: 145 | nextPipes.add(%fi) 146 | fillOrder.add(nextPipes) 147 | result[KEY_FILLORDER] = fillOrder 148 | result[KEY_WIN] = %bsr.win 149 | result[KEY_DOUBLE] = %bsr.hasDouble 150 | 151 | proc toJson*(ubd:UfoBonusData): JsonNode = 152 | result = newJObject() 153 | result[$srtPayout] = %ubd.totalWin 154 | var spinResults = newJArray() 155 | for bsr in ubd.spinResults: 156 | spinResults.add(bsr.toJson) 157 | result["sr"] = spinResults 158 | 159 | 160 | when isMainModule: 161 | echo "UfoBonus" 162 | #randomize() 163 | let ubd = newUfoBonusData(5000, 5) 164 | 165 | echo ubd.toJson 166 | 167 | 168 | 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /falconserver/quest/quest_types.nim: -------------------------------------------------------------------------------- 1 | # import falconserver.map.building.builditem 2 | import strutils 3 | 4 | #[ 5 | Story Quests represented in upper case, daily in lower case 6 | ]# 7 | type 8 | QuestTaskType* = enum 9 | qttFreeRounds = (-2, "zZZ") 10 | qttLevelUp = (-1, "zZ") 11 | 12 | qttBuild = "AA" 13 | qttUpgrade = "AB" 14 | 15 | # first tasks iteration 16 | qttSpinNTimes = "ac" 17 | qttWinBigWins = "aa" 18 | qttMakeWinSpins = "ab" 19 | qttSpinNTimesMaxBet = "ad" 20 | qttWinChipOnSpins = "ae" 21 | qttWinFreespinsCount = "ah" 22 | qttWinBonusTimes = "ai" 23 | qttWinChipOnFreespins = "aq" 24 | qttWinChipsOnBonus = "ar" 25 | 26 | # qttPlayRoulette = "ak" not used 27 | # qttPlayPvP = "am" not used 28 | # qttWinPvP = "ao" not used 29 | # qttCollectChips = "ap" not used 30 | 31 | # second tasks iteration 32 | qttWinNChips = "as" 33 | 34 | qttWinN5InRow = "at" 35 | qttWinN4InRow = "av" 36 | qttWinN3InRow = "aw" 37 | 38 | qttCollectScatters = "ax" 39 | qttCollectBonus = "aj" 40 | qttCollectWild = "al" 41 | 42 | qttBlowNBalloon = "ay" 43 | qttMakeNRespins = "az" 44 | # qttThrowTNTCandy = "ba" not used, implemented in qttCollectWild 45 | qttPolymorphNSymbolsIntoWild = "bb" 46 | 47 | qttGroovySevens = "bd" 48 | qttGroovyLemons = "bc" 49 | qttGroovyBars = "be" 50 | qttGroovyCherries = "bh" 51 | qttCollectHi0 = "h0" 52 | qttCollectHi1 = "h1" 53 | qttCollectHi2 = "h2" 54 | qttCollectHi3 = "h3" 55 | 56 | qttShuffle = "bs" 57 | qttCollectHidden = "bo" 58 | qttCollectMultipliers = "bm" 59 | qttWinNLines = "bl" 60 | 61 | QuestTaskFields* = enum 62 | qtfType = "t" ## task type from enum QuestTaskType 63 | qtfObject = "o" ## task BuildingId 64 | qtfTotalProgress = "tp" ## total task progress 65 | qtfCurrentProgress = "cp" ## current task progress, task completed when current reach's total 66 | qtfProgressIndex = "pi" 67 | qtfProgress = "p" 68 | qtfDifficulty = "m" ## Difficulty multiplayer 69 | qtfStage = "s" ## Last slot stage 70 | 71 | QuestTaskProgress* = enum 72 | qtpNone = "n" 73 | qtpHasProgress = "p" 74 | qtpCompleted = "c" 75 | 76 | QuestDataFields* = enum 77 | qdfSpins = "s" 78 | qdfSlotQuests = "q" 79 | qdfCompleteTime = "u" 80 | qdfAutoComplete = "i" 81 | qdfOldLvl = "o" 82 | qdfBet = "b" 83 | qdfLines = "l" 84 | qdfTotalWin = "w" 85 | qdfBonusWin = "g" 86 | qdfBonusPayout = "p" 87 | qdfBetLevel = "z" 88 | qdfStage = "t" 89 | qdfMidBet = "f" 90 | qdfTotalBet = "a" 91 | qdfCollectedChips = "c" 92 | qdfQuests = "d" 93 | qdfKind = "k" 94 | qdfExchangeParts = "ep" 95 | qdfFreespins = "e" 96 | qdfFreespinsCount = "fc" 97 | 98 | QuestFields* = enum 99 | qfId = "i" ## quest id 100 | qfReward = "r" ## reward earned on quest completed 101 | qfState = "d" ## quest data for tracking tasks progress, stored in db 102 | qfStatus = "s" ## quest status: {InProgress, GoalAchieved} 103 | qfTasks = "t" ## quest's task list 104 | qfKind = "k" ## kind of quest 105 | 106 | QuestType* {.pure.} = enum ## Type of quest 107 | ## Type of quest 108 | City 109 | Slot 110 | LevelUp 111 | 112 | QuestKind* {.pure.} = enum ## Kind of quest 113 | ## Kind of quest 114 | Daily = "d" 115 | Story = "s" ## Tutorial Quest and Story Quest 116 | Achievment = "a" 117 | LevelUp = "l" 118 | 119 | QuestProgress* {.pure.} = enum 120 | ## Current quest status 121 | None = (-1, "n") 122 | Ready = "r" 123 | InProgress = "p" 124 | GoalAchieved = "g" 125 | Completed = "c" 126 | 127 | QuestRewardKind* = enum 128 | qrChips = "cc" 129 | qrBucks = "cb" 130 | qrParts = "cp" 131 | qrExp = "x" 132 | qrIncomeChips = "ic" 133 | qrIncomeBucks = "ib" 134 | qrTourPoints = "tp" 135 | qrMaxBet = "mb" 136 | qrRespin 137 | qrFreespin 138 | qrBonusChips 139 | qrFreespinsCount 140 | qrBonusGame 141 | qrBigWin 142 | qrWinSpin 143 | qrSpin 144 | 145 | # ALL BOOSTERS GOES HERE BEFORE qrBoosterAll 146 | qrBoosterExp = "be" 147 | qrBoosterIncome = "bi" 148 | qrBoosterTourPoints = "bt" 149 | qrBoosterAll = "ba" 150 | 151 | # VIP bonuses 152 | qrBucksPurchaseBonus = "bpb" # == prfBucksPurchaseBonus 153 | qrChipsPurchaseBonus = "cpb" # == prfChipsPurchaseBonus 154 | qrGiftsBonus = "grb" # == prfGiftsBonus 155 | qrExchangeDiscount = "ed" # == prfExchangeDiscount 156 | qrFortuneWheelSpin = "fws" 157 | 158 | qrSlot 159 | 160 | 161 | QuestRewardFields* = enum 162 | qrfType = "t" ## type of reward - [ProfileFields] 163 | qrfCount = "c" ## count of reward 164 | qrfIsDef = "d" ## is reward deferred 165 | qrfTbet = "f" ## helper field, for calculate deferred reward 166 | 167 | DailyDifficultyType* = enum 168 | trivial, easy, intermediate, medium, hard 169 | 170 | proc parseQuestTaskType*(s: string, id: int): QuestTaskType = 171 | var compVal = s # for levelUp task with id -1 and other posible tasks with id less than 0 172 | if id >= 100_000: 173 | compVal = s.toLowerAscii() 174 | elif id >= 0: 175 | compVal = s.toUpperAscii() 176 | 177 | for t in low(QuestTaskType)..high(QuestTaskType): 178 | if $t == compVal: 179 | return t 180 | 181 | raise newException(ValueError, "invalid enum value: " & s) 182 | -------------------------------------------------------------------------------- /falconserver/cheats/cardSlot/cheats.json: -------------------------------------------------------------------------------- 1 | {"bigwin":[[6,6,6,3,4,6,0,6,6,5,6,5,3,6,8],[3,5,1,1,4,5,1,7,1,2,1,1,2,3,1],[1,4,1,2,1,11,6,8,6,1,7,0,3,1,8],[1,1,7,5,7,7,7,3,7,7,7,5,0,7,5],[0,8,0,7,5,8,2,4,2,8,2,5,3,5,6],[6,6,0,4,8,6,6,6,7,6,3,6,5,6,11],[8,6,6,8,6,6,6,6,6,6,5,7,3,1,6],[8,5,1,0,6,6,4,6,7,6,5,0,6,6,6],[7,5,1,3,3,1,1,1,5,5,1,1,1,2,6],[7,2,7,2,8,8,5,2,4,3,2,0,1,6,2],[2,6,2,5,5,3,2,5,2,4,0,5,6,0,8],[4,4,0,2,7,8,5,5,5,3,5,8,6,6,5],[3,0,2,5,0,2,6,5,6,6,5,5,7,3,5],[1,3,3,3,1,3,5,3,3,3,3,1,3,8,7],[4,4,7,4,8,4,0,4,4,6,4,7,8,4,5],[1,1,1,6,1,1,1,1,7,1,5,2,1,2,1],[0,6,7,4,7,5,7,6,7,8,6,1,5,6,6],[7,5,4,2,2,2,4,6,2,2,2,0,2,2,1],[0,6,2,7,3,8,2,6,2,5,2,8,4,6,2],[1,2,7,2,4,4,1,3,1,3,2,4,0,7,2],[5,1,2,7,8,8,0,3,4,6,0,8,0,2,4],[7,7,7,7,6,7,7,8,7,7,7,7,4,5,11],[4,6,6,5,11,6,6,6,6,7,6,2,6,6,3],[2,0,6,6,11,2,7,2,4,8,2,8,5,0,2],[7,8,7,6,7,7,7,7,5,7,3,7,7,7,1],[8,6,6,6,8,4,7,6,8,2,6,8,6,4,6],[5,7,5,0,4,4,5,4,5,8,0,4,3,6,2],[8,3,6,6,8,2,5,0,5,6,5,2,5,4,5],[1,5,1,7,5,1,1,1,8,8,1,1,3,6,4],[6,0,5,2,8,4,7,6,8,3,0,5,2,6,2],[3,4,4,4,8,7,4,4,4,3,4,4,4,3,7],[7,7,0,3,6,6,5,6,5,4,5,6,5,8,5],[2,8,6,8,3,1,1,4,1,4,0,7,0,7,8],[4,8,8,8,8,8,8,8,6,6,8,8,8,7,5],[2,3,6,2,8,2,5,2,2,2,2,2,5,2,3],[6,6,6,6,7,6,6,6,6,2,8,5,6,8,6],[1,3,1,3,1,3,2,3,6,7,6,1,7,1,5],[5,5,5,5,3,5,5,4,5,8,5,5,8,8,5],[6,5,6,5,7,2,3,0,8,6,5,2,5,3,5],[5,6,5,4,5,5,5,2,0,5,8,5,3,7,5],[2,8,2,7,4,3,2,7,2,8,0,7,6,6,2],[1,1,5,2,8,8,1,1,7,2,1,1,1,8,1],[6,4,6,3,7,6,6,6,6,11,3,0,6,6,4]],"megawin":[[4,4,5,4,8,4,2,4,7,2,4,7,4,6,4],[5,2,5,4,5,8,5,5,0,6,6,7,5,7,7],[1,7,7,3,8,7,7,7,8,7,7,7,7,7,7],[1,1,0,1,1,1,2,6,3,1,8,7,5,6,1],[6,2,2,5,2,7,2,3,2,2,2,7,2,3,2],[2,2,6,4,4,2,2,2,2,2,2,7,8,6,2],[3,4,4,4,3,7,4,4,4,8,4,5,4,4,4],[7,2,2,2,3,2,2,2,2,6,2,3,2,2,11],[2,3,0,0,8,3,2,8,7,3,0,8,2,6,7],[3,3,7,6,4,3,3,3,3,11,3,3,0,5,8],[1,5,1,1,1,1,1,1,1,1,1,1,1,1,5],[4,2,4,1,4,6,4,0,4,8,2,6,7,8,6],[1,1,1,6,11,1,1,1,5,7,1,1,7,1,3],[2,2,2,2,2,2,4,2,8,2,2,6,2,4,3],[1,4,8,4,8,4,4,11,4,4,4,4,4,4,1],[8,3,3,4,8,5,3,3,0,2,3,7,3,7,3],[8,8,8,8,2,8,8,8,8,8,8,2,8,3,6],[5,5,5,1,6,6,4,0,4,3,4,7,4,8,4],[3,5,0,5,5,2,5,6,5,5,5,7,5,5,5],[7,4,5,4,5,5,5,0,2,8,4,6,4,8,4],[5,0,2,3,5,6,5,5,5,6,3,6,0,6,4],[11,0,4,4,6,7,5,4,4,11,4,4,4,4,4],[3,5,3,4,6,3,3,3,0,7,3,3,3,7,11],[7,6,6,6,6,6,7,6,3,6,6,3,6,5,6],[6,6,6,6,6,4,6,6,4,7,6,6,6,7,11],[1,0,6,6,2,6,7,6,6,6,6,8,6,6,6],[1,6,1,1,6,8,1,7,1,7,1,1,8,3,1],[7,4,2,5,7,2,2,0,2,2,4,1,1,3,6],[5,3,1,3,1,3,3,3,3,3,2,7,3,5,7],[6,6,6,6,1,6,6,6,6,4,5,6,6,6,8],[6,6,6,0,6,6,6,6,7,11,3,6,3,6,4],[7,8,8,7,8,8,8,8,8,8,8,8,8,4,8]],"hugewin":[[1,8,6,6,1,6,6,6,6,4,6,6,6,6,8],[8,4,4,4,4,4,3,4,4,4,6,5,4,4,4],[3,8,3,3,8,3,3,3,3,4,3,3,7,5,1],[4,6,1,6,7,6,7,6,6,2,6,3,6,6,6],[0,1,8,4,1,8,0,6,1,7,2,8,0,3,8],[5,2,5,6,8,3,3,2,3,3,2,0,0,5,7],[6,6,3,6,3,4,6,0,1,5,6,6,6,3,6],[3,3,3,2,5,7,3,3,3,6,3,5,3,3,7],[6,4,0,2,2,6,6,6,6,6,3,3,5,1,11],[4,6,4,4,4,3,4,3,4,8,2,4,2,4,6],[0,0,8,0,4,8,6,3,1,8,3,5,2,7,3],[5,5,0,5,11,5,5,6,5,4,5,1,5,3,6],[6,5,6,5,6,4,1,5,7,4,0,0,3,6,5],[1,1,1,8,1,7,5,7,4,5,1,1,8,1,7],[7,1,8,2,4,2,0,6,6,5,6,8,0,4,6],[1,4,7,7,5,7,0,7,7,4,7,7,1,3,7],[1,2,8,3,4,3,5,0,5,3,5,3,6,4,5],[3,2,4,4,4,7,4,4,4,4,4,6,3,8,4],[5,1,6,3,3,1,0,0,7,2,0,8,7,8,7],[11,5,6,4,7,4,4,4,0,4,8,0,5,7,2],[7,1,1,5,7,4,4,6,4,4,4,2,4,8,4],[8,8,8,3,5,8,8,8,8,6,8,8,8,7,2],[8,6,0,4,4,1,0,6,0,1,7,5,5,7,3],[0,5,6,5,7,6,6,5,6,6,5,2,8,2,5],[1,1,1,6,11,1,5,1,7,2,1,1,1,2,5],[4,4,4,4,7,4,4,4,5,3,4,3,4,3,5],[3,5,5,2,6,5,2,1,3,7,1,1,1,1,1],[5,4,0,6,5,8,5,6,5,4,6,6,5,5,5],[8,6,8,4,8,3,8,8,8,6,7,8,8,2,7],[0,3,6,4,7,7,7,0,7,4,8,4,7,3,2],[7,6,1,3,7,7,7,7,7,7,7,7,2,7,7],[2,8,4,3,3,7,7,7,7,7,6,7,7,7,7]],"freespins":[[7,8,11,1,11,5,7,4,3,4,11,7,7,6,6],[11,3,11,7,11,4,5,1,4,4,8,6,8,3,5],[11,7,11,6,6,3,8,4,1,11,2,4,7,3,7],[11,3,3,1,6,4,5,2,7,11,8,6,11,4,4],[3,2,4,6,11,2,7,11,7,8,11,6,6,2,2],[2,7,11,1,7,6,8,5,8,11,11,4,4,1,4],[3,5,11,3,2,2,7,7,3,6,11,2,4,3,11],[11,2,7,6,7,4,4,8,4,11,8,6,11,0,8],[11,0,8,0,3,7,7,3,7,6,2,8,11,6,11],[2,5,1,7,6,6,4,4,4,11,11,3,11,3,2],[7,1,11,5,11,5,8,4,4,8,11,2,5,7,6],[11,5,6,8,11,3,6,2,7,7,2,8,11,7,3],[7,3,8,4,6,5,5,11,4,11,11,6,4,4,7],[11,2,3,8,6,7,6,8,7,11,2,7,11,2,2],[7,8,8,1,4,5,8,3,7,11,11,8,11,4,8],[3,6,8,5,6,2,4,11,3,7,11,4,4,8,11],[11,3,7,8,6,5,5,8,8,11,8,8,11,8,4],[1,0,2,0,6,11,5,11,7,7,7,8,1,6,11],[2,7,4,1,11,11,6,8,8,8,8,2,11,2,6],[11,2,4,6,6,5,5,11,4,5,8,7,6,7,11],[11,2,11,3,6,7,7,6,5,7,2,6,2,2,11],[7,8,8,6,7,5,6,11,8,11,11,6,4,4,4],[5,7,11,3,2,6,7,1,8,6,11,5,8,7,11],[5,2,6,3,6,11,1,2,5,11,4,4,11,2,7],[3,4,11,3,11,2,2,4,6,2,11,6,7,4,5],[2,7,11,6,7,11,2,4,4,11,8,7,7,7,4],[6,7,3,0,11,11,5,2,7,2,7,4,11,6,5],[2,7,8,4,11,6,6,11,7,4,11,2,7,8,5],[11,3,3,0,6,7,4,8,7,11,4,8,11,6,7],[5,8,7,5,11,6,6,8,3,4,11,6,11,8,5],[6,4,7,5,11,11,1,8,5,4,4,8,11,6,5],[11,5,3,2,6,3,5,8,7,11,2,1,11,8,4],[1,5,1,8,6,11,2,4,4,11,7,1,11,5,2],[11,8,3,7,6,4,8,2,8,7,8,8,11,4,11],[4,0,11,6,11,11,7,6,3,4,5,8,2,5,6],[11,3,8,7,6,3,5,11,8,7,2,8,7,6,11],[2,8,11,4,5,6,1,5,5,4,11,3,4,3,11],[11,4,4,6,11,7,0,8,4,2,4,7,11,7,5],[11,2,4,4,7,5,1,11,7,11,8,4,6,6,4],[3,7,2,2,6,2,8,11,6,11,11,6,5,1,4],[11,3,2,6,11,7,5,11,5,2,2,6,1,8,5],[3,2,8,7,11,2,5,11,7,4,11,7,4,5,5],[2,7,2,3,6,11,8,11,5,5,8,4,1,4,11],[11,8,8,5,7,7,6,3,5,11,2,5,11,5,4],[2,3,3,6,6,11,8,8,5,11,8,7,11,5,2],[7,6,8,1,5,5,7,11,3,11,11,3,4,6,4],[8,7,11,3,6,1,2,5,8,11,11,4,4,7,2],[8,7,11,2,5,1,5,6,6,11,11,1,2,1,4],[11,3,3,8,6,7,5,11,7,7,2,6,6,2,11],[11,5,11,7,11,8,6,6,4,7,1,6,2,3,3],[11,3,11,8,5,4,5,4,7,4,8,2,5,2,11],[1,7,2,4,4,11,8,11,4,11,7,6,5,4,8],[11,6,4,3,8,3,2,8,8,6,2,2,11,7,11],[11,5,11,2,11,5,8,4,6,7,8,6,5,1,3],[8,4,6,5,11,11,5,2,4,4,3,6,11,7,5],[6,8,2,2,7,8,6,11,5,11,11,5,1,4,4],[3,8,11,7,6,2,6,7,4,5,11,2,4,3,11],[2,8,3,7,11,11,5,11,2,4,8,1,6,1,6],[11,8,2,7,5,7,2,11,6,11,4,7,5,5,4],[11,7,8,5,6,4,8,11,6,7,8,6,7,3,11],[11,4,8,5,6,3,0,3,4,11,2,7,11,7,4],[11,0,2,4,11,5,7,11,4,8,8,8,5,4,6],[2,6,7,2,3,6,7,8,6,6,11,3,11,1,11],[8,8,11,7,11,11,8,6,8,8,3,8,2,6,2],[7,3,2,3,11,5,3,11,3,8,11,3,1,8,2],[11,2,3,2,6,5,4,2,1,11,8,6,11,7,7],[8,8,2,5,11,1,2,11,6,8,11,4,5,3,6],[6,6,3,5,5,8,8,8,2,11,11,8,11,1,4],[11,4,11,7,11,8,4,1,8,7,1,4,8,6,3],[5,2,6,2,6,6,4,2,5,5,11,6,11,4,11],[1,5,7,7,11,11,5,8,2,4,7,5,11,1,5],[11,5,11,7,2,4,6,4,2,6,8,3,7,1,11],[11,8,11,3,11,5,8,6,5,8,8,7,4,2,2],[6,3,3,4,2,11,5,2,8,6,4,2,11,2,11],[6,3,4,6,7,11,4,8,6,11,4,8,11,7,8],[8,7,8,7,11,11,5,11,8,4,3,4,4,6,5],[5,7,11,3,5,11,3,4,5,4,4,5,5,6,11],[11,8,2,3,3,4,6,11,6,6,8,3,1,7,11],[6,6,11,5,7,8,7,6,4,11,11,3,4,8,8],[11,3,6,6,5,8,8,2,4,4,1,1,11,0,11],[5,1,8,3,11,6,4,11,5,2,11,2,4,2,5],[6,5,11,3,6,11,1,4,3,11,7,7,5,3,4],[8,5,3,8,7,1,5,11,4,11,11,5,6,2,8],[3,6,4,8,5,2,0,11,5,4,11,5,6,6,11],[11,5,11,6,4,7,4,6,4,11,2,4,4,7,8],[5,7,8,1,6,6,5,11,7,11,11,4,4,4,7],[11,1,4,2,6,5,4,8,3,11,8,2,11,7,4],[3,4,11,3,5,2,5,5,5,11,11,6,4,2,4],[5,6,6,5,4,6,2,2,4,11,11,5,11,8,8],[11,4,6,8,11,8,2,2,6,4,1,6,11,4,5],[11,5,8,4,6,4,6,3,4,7,8,3,11,4,11],[4,1,6,5,11,11,3,2,3,4,5,8,11,8,5],[11,7,4,6,6,4,2,11,3,11,8,4,6,5,2],[6,8,8,3,11,11,7,11,3,4,7,5,4,5,5],[7,5,11,5,6,5,5,4,5,11,11,5,5,5,2],[6,7,11,5,6,11,5,1,2,11,7,4,8,3,2],[5,2,7,5,11,11,4,8,5,8,4,6,11,5,2],[8,1,8,1,6,1,4,11,8,5,11,2,7,8,11],[6,6,3,7,11,8,7,8,4,4,11,3,11,3,6],[11,7,11,4,5,7,5,7,4,4,2,4,4,5,11]]} -------------------------------------------------------------------------------- /falconserver/auth/profile_random.nim: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # Nim's Runtime Library 4 | # (c) Copyright 2017 Andreas Rumpf 5 | # 6 | # See the file "copying.txt", included in this 7 | # distribution, for details about the copyright. 8 | # 9 | 10 | ## Modified nim's standard random number generator. Based on the ``xoroshiro128+`` (xor/rotate/shift/rotate) library. 11 | ## * More information: http://xoroshiro.di.unimi.it/ 12 | ## * C implementation: http://xoroshiro.di.unimi.it/xoroshiro128plus.c 13 | ## 14 | ## Do not use this module for cryptographic use! 15 | 16 | include "system/inclrtl" 17 | {.push debugger:off.} 18 | 19 | import profile 20 | export profile 21 | 22 | import mersenne 23 | import times 24 | 25 | # XXX Expose RandomGenState 26 | when defined(JS): 27 | type ui = uint32 28 | 29 | const randMax = 4_294_967_295u32 30 | else: 31 | type ui = uint64 32 | 33 | const randMax = 18_446_744_073_709_551_615u64 34 | 35 | type 36 | RandomGenState = object 37 | a0, a1: ui 38 | 39 | 40 | proc rotl(x, k: ui): ui = 41 | result = (x shl k) or (x shr (ui(64) - k)) 42 | 43 | 44 | proc next(s: var RandomGenState): uint64 = 45 | let s0 = s.a0 46 | var s1 = s.a1 47 | result = s0 + s1 48 | s1 = s1 xor s0 49 | s.a0 = rotl(s0, 55) xor s1 xor (s1 shl 14) # a, b 50 | s.a1 = rotl(s1, 36) # c 51 | 52 | 53 | proc nextRandomState(p: Profile): uint64 = 54 | var state = RandomGenState(a0: cast[uint64](p.randomState0), a1: cast[uint64](p.randomState1)) 55 | result = next(state) 56 | p.randomState0 = cast[int64](state.a0) 57 | p.randomState1 = cast[int64](state.a1) 58 | 59 | 60 | # proc skipRandomNumbers(s: var RandomGenState) = 61 | # ## This is the jump function for the generator. It is equivalent 62 | # ## to 2^64 calls to next(); it can be used to generate 2^64 63 | # ## non-overlapping subsequences for parallel computations. 64 | # when defined(JS): 65 | # const helper = [0xbeac0467u32, 0xd86b048bu32] 66 | # else: 67 | # const helper = [0xbeac0467eba5facbu64, 0xd86b048b86aa9922u64] 68 | # var 69 | # s0 = ui 0 70 | # s1 = ui 0 71 | # for i in 0..high(helper): 72 | # for b in 0..< 64: 73 | # if (helper[i] and (ui(1) shl ui(b))) != 0: 74 | # s0 = s0 xor s.a0 75 | # s1 = s1 xor s.a1 76 | # discard next(s) 77 | # s.a0 = s0 78 | # s.a1 = s1 79 | 80 | var mSeed = uint32(times.epochTime() * 1_000_000_000) 81 | #echo "seed = ", mSeed 82 | var gMTwister = newMersenneTwister(mSeed) 83 | 84 | 85 | proc usePrivateRandom(p: Profile): bool = 86 | result = not p.isNil and (p.randomState0 != 0 or p.randomState1 != 0) 87 | 88 | 89 | #proc random*(max: int): int = 90 | # result = (gMTwister.getNum() mod max.uint32).int 91 | 92 | proc random*(max: int): int = 93 | result = ((gMTwister.getNum().int64 shl 31 or gMTwister.getNum().int64 shr 1) mod max.int64).int 94 | 95 | 96 | # proc random*(max: uint32): uint32 = 97 | # result = gMTwister.getNum() mod max 98 | 99 | 100 | # proc random*(max: int32): int32 = 101 | # result = cast[int32](gMTwister.getNum()) mod max 102 | 103 | 104 | # proc random*(max: int64): int64 = 105 | # result = (gMTwister.getNum().int64 shl 32 or gMTwister.getNum().int64) mod max 106 | 107 | 108 | proc random*(max: float): float = 109 | result = (float(gMTwister.getNum()) / float(high(uint32))) * max 110 | 111 | 112 | proc random*[T](x: Slice[T]): T = 113 | ## For a slice `a .. b` returns a value in the range `a .. b-1`. 114 | result = T(random(x.b - x.a)) + x.a 115 | 116 | 117 | proc random*[T](a: openArray[T]): T = 118 | ## returns a random element from the openarray `a`. 119 | result = a[random(a.low..a.len)] 120 | 121 | 122 | proc random*(p: Profile, max: int): int {.benign.} = 123 | ## Returns a random number in the range 0..max-1. The sequence of 124 | ## random number is always the same, unless `randomize` is called 125 | ## which initializes the random number generator with a "random" 126 | ## number, i.e. a tickcount. 127 | if not p.usePrivateRandom(): 128 | return random(max).int 129 | while true: 130 | let x = p.nextRandomState() 131 | if x < randMax - (randMax mod ui(max)): 132 | return int(x mod uint64(max)) 133 | 134 | 135 | proc random*(p: Profile, max: float): float {.benign.} = 136 | ## Returns a random number in the range 0.. 1200: 204 | echo "too many (" & $oc & ") occurrences of " & $i 205 | failures.inc 206 | assert failures == 0 207 | 208 | main() 209 | -------------------------------------------------------------------------------- /falconserver/map/collect.nim: -------------------------------------------------------------------------------- 1 | import falconserver / common / [ currency, game_balance, checks, get_balance_config, notifications ] 2 | import falconserver / quest / [ quests_config, quest_types, quest ] 3 | import falconserver / auth / profile 4 | import shafa / game / reward_types 5 | import falconserver.boosters.boosters 6 | import strutils, json, times, tables, math 7 | 8 | import boolseq 9 | 10 | import building.builditem 11 | export BuildingId 12 | export Currency 13 | 14 | proc incomeFromQuest(rews: seq[Reward], c: Currency): int= 15 | for r in rews: 16 | if r.kind == RewardKind.incomeChips and c == Currency.Chips: 17 | result += r.amount.int 18 | elif r.kind == RewardKind.incomeBucks and c == Currency.Bucks: 19 | result += r.amount.int 20 | 21 | proc questIncomeCurrency*(r: seq[Reward]): Currency= 22 | if r.incomeFromQuest(Currency.Chips) > 0: 23 | result = Currency.Chips 24 | elif r.incomeFromQuest(Currency.Bucks) > 0: 25 | result = Currency.Bucks 26 | 27 | proc resourcePerHour(conf: seq[QuestConfig], complQuests: BoolSeq, currency: Currency): int= 28 | result = 0 29 | if complQuests.len == 0: return 30 | for qi in 0 ..< complQuests.len: 31 | if qi < conf.len and complQuests[qi]: 32 | result += incomeFromQuest(conf[qi].rewards, currency) 33 | 34 | proc resourcePerHour*(p: Profile, currency: Currency): int = 35 | var storyConfig = p.getStoryConfig() 36 | var binStr = "" 37 | 38 | if not p.statistics.questsCompleted.isNil: 39 | binStr = p.statistics.questsCompleted.binstr() 40 | 41 | var bs = newBoolSeq(binStr) 42 | result = resourcePerHour(storyConfig, bs, currency) 43 | 44 | proc resourceIncomeFullTimeHours*(p: Profile, currency: Currency): float = 45 | let gb = p.getGameBalance() 46 | result = if currency == Currency.Chips: gb.fullChipsIncomeTime else: gb.fullBucksIncomeTime 47 | 48 | proc resourceIncomeFullTime*(p: Profile, currency: Currency): float = 49 | result = p.resourceIncomeFullTimeHours(currency) * 60.0 * 60.0 50 | 51 | 52 | template collectTimeKey*(c: Currency): string = "lct_" & $c 53 | template calculatedGainKey*(c: Currency): string = "cg_" & $c 54 | template calculatedGainDurationKey*(c: Currency): string = "cgd_" & $c 55 | 56 | 57 | proc lastCollectionTime*(p: Profile, c: Currency): float = 58 | let clientStates = p[$prfState] 59 | if not clientStates.isNil: 60 | let k = collectTimeKey(c) 61 | if k in clientStates: 62 | result = clientStates[k].toFloat64() 63 | else: 64 | result = 0.0 65 | 66 | proc calculatedGain*(p: Profile, c: Currency): float = 67 | let clientStates = p[$prfState] 68 | if not clientStates.isNil: 69 | let k = calculatedGainKey(c) 70 | if k in clientStates: 71 | result = clientStates[k].toFloat64() 72 | else: 73 | result = 0 74 | 75 | proc calculatedGainDuration*(p: Profile, c: Currency): float = 76 | let clientStates = p[$prfState] 77 | if not clientStates.isNil: 78 | let k = calculatedGainDurationKey(c) 79 | if k in clientStates: 80 | result = clientStates[k].toFloat64() 81 | else: 82 | result = 0.0 83 | 84 | 85 | proc availableResourcesF*(p: Profile, lastCollectTime: float, currency: Currency): float = 86 | let fullFillDur = p.resourceIncomeFullTimeHours(currency) * 60.0 * 60.0 87 | let actualFillDur = clamp(epochTime() - lastCollectTime, 0, fullFillDur) 88 | let calculatedDur = clamp(p.calculatedGainDuration(currency), 0, actualFillDur) 89 | let remainingFillDur = actualFillDur - calculatedDur 90 | let boosteredDur = clamp(p.boosters.activeUntilT($btIncome) - (lastCollectTime + calculatedDur), 0, remainingFillDur) 91 | let nonBoosteredDur = remainingFillDur - boosteredDur 92 | 93 | result = p.calculatedGain(currency) + (p.resourcePerHour(currency).float / 60.0 / 60.0 * (boosteredDur * p.getGameBalance().boosters.incomeRate + nonBoosteredDur)) 94 | 95 | proc availableResources*(p: Profile, lastCollectTime: float, currency: Currency): int = 96 | result = round(p.availableResourcesF(lastCollectTime, currency)).int 97 | 98 | 99 | proc sheduleCollectNotification*(p: Profile, currency: Currency, time: float)= 100 | var biKind = if currency == Currency.Chips: "restaurant" else: "gasStation" 101 | p.forkNotifyResourcesAreFull(biKind, time) 102 | 103 | 104 | proc collectResource*(p: Profile, currency: Currency) = 105 | let lastCollectionTime = p.lastCollectionTime(currency) 106 | # We mustn't raise an exception here as the server gets crashed when only chips income is available. 107 | if lastCollectionTime <= 1.0: 108 | return 109 | 110 | let ar = p.availableResources(lastCollectionTime, currency) 111 | if ar > 0: 112 | if currency == Currency.Chips: 113 | p.chips = p.chips + ar 114 | elif currency == Currency.Bucks: 115 | p.bucks = p.bucks + ar 116 | 117 | let collectionTime = epochTime() 118 | p.setClientState(collectTimeKey(currency), collectionTime.toBson()) 119 | p.deleteClientState(calculatedGainKey(currency)) 120 | p.deleteClientState(calculatedGainDurationKey(currency)) 121 | p.sheduleCollectNotification(currency, collectionTime + p.resourceIncomeFullTime(currency)) 122 | 123 | 124 | proc saveReadyIncomeGain*(p: Profile, currency: Currency) = 125 | let key = collectTimeKey(currency) 126 | let lastCollectionTime = p.lastCollectionTime(currency) 127 | # We mustn't raise an exception here as the server gets crashed when only chips income is available. 128 | if lastCollectionTime <= 1.0: 129 | return 130 | 131 | let readyIncomeGain = p.availableResourcesF(lastCollectionTime, currency) 132 | let readyIncomeGainDur = epochTime() - lastCollectionTime 133 | p.setClientState(calculatedGainKey(currency), readyIncomeGain.toBson()) 134 | p.setClientState(calculatedGainDurationKey(currency), readyIncomeGainDur.toBson()) 135 | 136 | 137 | # for old clients 138 | proc collectResources*(p: Profile, fromRes: string) {.deprecated.} = 139 | var currency: Currency 140 | if "restaurant" in fromRes: 141 | currency = Currency.Chips 142 | elif "gasStation" in fromRes: 143 | currency = Currency.Bucks 144 | 145 | p.collectResource(currency) 146 | 147 | 148 | proc collectConfig*(p: Profile): JsonNode= 149 | result = newJArray() 150 | 151 | for curr in [Currency.Chips, Currency.Bucks]: 152 | var conf = newJObject() 153 | conf["kind"] = %curr 154 | conf["lct"] = %p.lastCollectionTime(curr) 155 | conf["rph"] = %p.resourcePerHour(curr) 156 | conf["ful"] = %p.resourceIncomeFullTimeHours(curr) 157 | if p.calculatedGainDuration(curr) > 0: 158 | conf["cgd"] = %p.calculatedGainDuration(curr) 159 | conf["cg"] = %p.calculatedGain(curr) 160 | result.add(conf) 161 | -------------------------------------------------------------------------------- /falconserver/slot/machine_balloon_server.nim: -------------------------------------------------------------------------------- 1 | import json 2 | import tables 3 | import sequtils 4 | 5 | import nimongo.bson 6 | 7 | import falconserver.auth.profile_types 8 | import falconserver.auth.profile 9 | import falconserver.common.bson_helper 10 | import falconserver.map.building.builditem 11 | import machine_balloon 12 | import machine_base_server 13 | import slot_data_types 14 | 15 | export machine_balloon 16 | 17 | method getResponseAndState*(sm: SlotMachineBalloon, profile: Profile, prevState: Bson, jData: JsonNode, resp: var JsonNode, slotState: var Bson) {.gcsafe.} = 18 | var 19 | bet = jData[$srtBet].getBiggestInt().int64 20 | lines = jData[$srtLines].getInt() 21 | totalFreespinWin: int64 22 | spinPayout: int64 23 | cheatSpin: seq[int8] 24 | cheatName: string 25 | 26 | sm.lastWin = @[] 27 | sm.lastField = @[] 28 | 29 | # load from mongo 30 | if not prevState.isNil: 31 | if "md" in prevState: 32 | sm.destructions = prevState["md"] 33 | if "lf" in prevState: 34 | prevState["lf"].toSeqInt8(sm.lastField) 35 | if "lw" in prevState: 36 | prevState["lw"].toSeqIntTuple(sm.lastWin) 37 | if "fs" in prevState: 38 | sm.freespinCount = prevState["fs"] 39 | if "ls" in prevState: 40 | if sm.freespinCount > 0: 41 | prevState["ls"].toSeqInt8(sm.reelsFreespin.lastSpin) 42 | else: 43 | prevState["ls"].toSeqInt8(sm.reels.lastSpin) 44 | if "bt" in prevState: 45 | if sm.freespinCount > 0: 46 | bet = prevState["bt"] 47 | if "bg" in prevState: 48 | sm.bonusgame = prevState["bg"] 49 | if $sdtFreespinTotalWin in prevState: 50 | totalFreespinWin = prevState[$sdtFreespinTotalWin] 51 | if not prevState[$sdtCheatSpin].isNil and prevState[$sdtCheatSpin].kind != BsonKindNull: 52 | cheatSpin = @[] 53 | for v in prevState[$sdtCheatSpin]: 54 | cheatSpin.add(v.toInt().int8) 55 | if not prevState[$sdtCheatName].isNil and prevState[$sdtCheatName].kind != BsonKindNull: 56 | cheatName = prevState[$sdtCheatName].toString() 57 | 58 | var chips = profile.chips 59 | var jSymbols: seq[JsonNode] = @[] 60 | 61 | let prevFreespins = sm.freespinCount 62 | let prevDestructions = sm.destructions 63 | 64 | let spinResult = sm.spin(profile, bet, lines, cheatSpin) 65 | 66 | if prevFreespins == 0 and prevDestructions == 0: 67 | chips -= lines * bet 68 | 69 | # hotfix bonus cheat 70 | if cheatSpin.len != 0 and sm.lastField.len > 15: 71 | sm.lastField.delete(20, 24) 72 | sm.lastField.delete(0, 4) 73 | 74 | if sm.canStartBonusGame(sm.lastField) and sm.destructions == 0: 75 | 76 | inc sm.destructions 77 | 78 | let bonusResults = sm.runBonusGame(profile) 79 | for i in bonusResults.field: jSymbols.add(newJInt(i)) 80 | var jRockets = newJArray() 81 | var bonusGamePayout: int64 82 | var targets = initTable[int, int]() 83 | for rocket, aims in bonusResults.rockets: 84 | var jRocket = newJObject() 85 | var jAims = newJObject() 86 | 87 | var indx = 0 88 | for k, v in aims: 89 | var jMultiplierPayout = newJArray() 90 | jMultiplierPayout.add(newJInt(k[0])) # target index 91 | jMultiplierPayout.add(newJInt(k[1])) # symbol count in line 92 | jMultiplierPayout.add(newJInt(v*bet)) # payout 93 | jAims.add($(indx), jMultiplierPayout) 94 | inc indx 95 | 96 | bonusGamePayout += (v*bet).int64 97 | 98 | jRocket[rocket] = jAims 99 | jRockets.add(jRocket) 100 | 101 | chips += bonusGamePayout 102 | 103 | var stages = newJArray() 104 | var stageResult = newJObject() 105 | stageResult[$srtStage] = %("Bonus") 106 | stageResult[$srtField] = %(jSymbols) 107 | stageResult["rockets"] = jRockets 108 | stageResult[$srtPayout] = %(bonusGamePayout) 109 | stages.add(stageResult) 110 | resp = json.`%*`({ 111 | "freeSpinsCount": sm.freespinCount, 112 | $srtChips: chips, 113 | $srtStages: stages 114 | }) 115 | 116 | template markDestroyedItems()= 117 | const MARKER: int8 = -1 118 | for i in bonusResults.destrIds: 119 | if (i-5) < sm.lastField.len and (i-5) >= 0: 120 | sm.lastField[i-5] = MARKER 121 | for i in 0.. 0: 148 | stage = "FreeSpin" 149 | else: 150 | if prevDestructions > 0: stage = "Respin" 151 | else: stage = "Spin" 152 | 153 | var stages = newJArray() 154 | var stageResult = newJObject() 155 | stageResult[$srtStage] = %(stage) 156 | stageResult[$srtField] = %(jSymbols) 157 | stageResult[$srtLines] = winToJson(spinResult.lines, bet) 158 | stageResult[$srtFreespinTotalWin] = %(totalFreespinWin) 159 | 160 | if spinResult.destruction.len != 0: 161 | stageResult["destructions"] = %spinResult.destruction 162 | 163 | stages.add(stageResult) 164 | resp = json.`%*`({ 165 | "freeSpinsCount": sm.freespinCount, 166 | $srtChips: chips, 167 | $srtStages: stages 168 | }) 169 | 170 | sm.bonusgame = false 171 | 172 | # save state to mongo 173 | let lastSpin = if sm.freespinCount > 0: sm.reelsFreespin.lastSpin.toBson() else: sm.reels.lastSpin.toBson() 174 | let lastField = sm.lastField.toBson() 175 | let lastWin = sm.lastWin.toBson() 176 | slotState = bson.`%*`({ 177 | "md": sm.destructions.toBson(), 178 | "ls": lastSpin, 179 | "lf": lastField, 180 | "lw": lastWin, 181 | "fs": sm.freespinCount.toBson(), 182 | "bt": bet.toBson(), 183 | "bg": sm.bonusgame.toBson(), 184 | $sdtFreespinTotalWin: totalFreespinWin.toBson(), 185 | $sdtCheatSpin: null(), 186 | $sdtCheatName: null() 187 | }) 188 | 189 | const ballonsDefaultZsm* = staticRead("../resources/slot_003_balloons.zsm") 190 | registerSlotMachine($balloonSlot, newSlotMachineBalloon, ballonsDefaultZsm) 191 | -------------------------------------------------------------------------------- /falconserver/common/get_balance_config.nim: -------------------------------------------------------------------------------- 1 | import asyncdispatch, tables, strutils, json, logging 2 | 3 | import falconserver.slot.machine_base 4 | import falconserver.slot.near_misses 5 | import falconserver.common.bson_helper 6 | import falconserver.common.db 7 | import falconserver.common.ab_constants 8 | import falconserver.common.game_balance 9 | import falconserver.quest.quests_config 10 | import falconserver.auth.profile 11 | 12 | import nimongo / [ bson, mongo ] 13 | 14 | proc create(zsm: Bson, slotName: string): SlotMachine = 15 | let initializer = slotMachineInitializers.getOrDefault(slotName) 16 | if not initializer.isNil: 17 | result = initializer(zsm.toJson()) 18 | else: 19 | raise newException(Exception, "Slot machine " & slotName & " not registered") 20 | 21 | proc loadZsm(db: Database[AsyncMongo], slotName, zsmName: string): Future[SlotMachine] {.async.} = 22 | var zsm = await db["zsm"].find(bson.`%*`({"name": zsmName})).oneOrNone() 23 | if not zsm.isNil and "gb" in zsm: 24 | result = zsm["gb"].create(slotName) 25 | 26 | var slotMachineCache = newTable[string, SlotMachine]() 27 | 28 | var rootConfigs: TableRef[string, JsonNode] 29 | var gameBalances: TableRef[string, GameBalance] 30 | var dailyConfigs: TableRef[string, DailyGeneratorConfig] 31 | var storyConfigs: TableRef[string, StoryQuestConfig] 32 | var clientConfigs: TableRef[string, JsonNode] 33 | var predefinedSpins: TableRef[string, JsonNode] 34 | var nearMissConfigs: TableRef[string, NearMissConfig] 35 | var offersConfigs: TableRef[string, JsonNode] 36 | 37 | proc init[T](t: var TableRef[string, T]) = 38 | t = newTable[string, T]() 39 | 40 | proc initAllTables() = 41 | rootConfigs.init() 42 | gameBalances.init() 43 | dailyConfigs.init() 44 | storyConfigs.init() 45 | predefinedSpins.init() 46 | nearMissConfigs.init() 47 | clientConfigs.init() 48 | offersConfigs.init() 49 | slotMachineCache.init() 50 | warn "Reset game_balance cache!" 51 | 52 | initAllTables() 53 | 54 | proc loadBallance*(db: Database[AsyncMongo], collection, gbName: string): Future[JsonNode] {.async.} = 55 | var gb = await db[collection].find(bson.`%*`({"name": gbName})).oneOrNone() 56 | if not gb.isNil and "gb" in gb: 57 | result = gb["gb"].toJson() 58 | 59 | proc getBalanceConfig[T](cache: TableRef[string, T], key, dbkey: string, deserializer: proc(j:JsonNode): T, defaultVal: T): Future[T] {.async.} = 60 | result = cache.getOrDefault(key) 61 | if result.isNil: 62 | if key.len != 0: 63 | let data = await sharedDb().loadBallance(dbkey, key) 64 | if not data.isNil: result = deserializer(data) 65 | if result.isNil: result = defaultVal 66 | cache[key] = result 67 | 68 | proc getBalanceConfig[T](cache: TableRef[string, T], key, dbkey: string, gb: GameBalance, deserializer: proc(gb: GameBalance, j:JsonNode): T, defaultVal: T): Future[T] {.async.} = 69 | result = cache.getOrDefault(key) 70 | if result.isNil: 71 | if key.len != 0: 72 | let data = await sharedDb().loadBallance(dbkey, key) 73 | if not data.isNil: result = deserializer(gb, data) 74 | if result.isNil: result = defaultVal 75 | cache[key] = result 76 | 77 | proc getRootConfig(prf: Profile): Future[JsonNode] {.async.} = 78 | proc deser(x: JsonNode): JsonNode = x 79 | result = await getBalanceConfig(rootConfigs, prf.abTestConfigName, "gb_config", deser, nil) 80 | 81 | proc getSlotMachineByGameID*(prf: Profile, slotName: string): Future[SlotMachine] {.async.} = 82 | let rootConfig = await prf.getRootConfig() 83 | 84 | let zsms = rootConfig{"zsm"} 85 | let zsmName = zsms{slotName}.getStr() 86 | let cacheKey = slotName & ":" & zsmName 87 | result = slotMachineCache.getOrDefault(cacheKey) 88 | 89 | if result.isNil: 90 | if zsmName.len == 0 or zsmName == slotName.abDefaultSlotZsmName: 91 | result = getSlotMachineByGameID(slotName) 92 | else: 93 | result = await sharedDB().loadZsm(slotName, zsmName) 94 | if result.isNil: 95 | warn "ZSM Not found ", zsmName 96 | result = getSlotMachineByGameID(slotName) 97 | if result.isNil: 98 | raise newException(Exception, "Slot machine not registered: " & slotName) 99 | 100 | slotMachineCache[cacheKey] = result 101 | 102 | proc getMachineIdByGameId*(prf: Profile, gameID: string): Future[string] {.async.} = 103 | let m = await prf.getSlotMachineByGameID(gameId) 104 | result = m.getSlotID() 105 | 106 | proc getGameBalance*(prf: Profile): GameBalance = 107 | prf.gconf.getGameBalance() 108 | 109 | proc getStoryConfig*(prf: Profile): seq[QuestConfig] = 110 | prf.gconf.getStoryConfig() 111 | 112 | proc getDailyConfig*(prf: Profile): DailyGeneratorConfig = 113 | prf.gconf.getDailyConfig() 114 | 115 | proc getClientConfig*(prf: Profile): JsonNode = 116 | prf.gconf.getClientConfig() 117 | 118 | proc predefinedSpin*(prf: Profile, gameSlotID: string, step: int): Bson = 119 | prf.gconf.predefinedSpin(gameSlotID, step) 120 | 121 | proc nearMissConfig*(prf: Profile): NearMissConfig = 122 | prf.gconf.nearMissConfig() 123 | 124 | proc loadGameBalance*(prf: Profile) {.async.} = 125 | let deser = proc(j: JsonNode): JsonNode = j 126 | 127 | let rootConfig = await prf.getRootConfig() 128 | prf.gconf = GameplayConfig.new 129 | prf.gconf.gameBalance = await getBalanceConfig(gameBalances, rootConfig{"balance"}.getStr("default"), "gb_common", parseBalance, sharedGameBalance()) 130 | proc parseStory(conf: JsonNode): StoryQuestConfig = newStoryQuestConfig(parseStoryConfig(conf)) 131 | prf.gconf.storyConfig = await getBalanceConfig(storyConfigs, rootConfig{"storyQuest"}.getStr("default"), "gb_story", parseStory, getDefaultQuestConfig) 132 | prf.gconf.dailyConfig = await getBalanceConfig(dailyConfigs, rootConfig{"dailyQuest"}.getStr("default"), "gb_daily", prf.gconf.gameBalance, parseDailyConfig, sharedDailyGeneratorConfig()) 133 | prf.gconf.predefinedSpinsData = await getBalanceConfig(predefinedSpins, rootConfig{"predefinedSpins"}.getStr("default"), "gb_spins", deser, sharedPredefinedSpinsData()) 134 | prf.gconf.nearMissesConfig = await getBalanceConfig(nearMissConfigs, rootConfig{"nearMisses"}.getStr("default"), "gb_nearmisses", parseNearMisses, sharedNearMissData()) 135 | prf.gconf.clientConfig = await getBalanceConfig(clientConfigs, rootConfig{"clientConfig"}.getStr("default"), "gb_client", deser, nil) 136 | 137 | proc isBalanceConfigUpToDate(): Future[bool] {.async.} = 138 | var lastUpdEpoch {.global.} = 0.0 139 | var lastUpd = await sharedDB()["gb_lastUpdate"].find(bson.`%*`({"gb": "gb_check"})).oneOrNone() 140 | if not lastUpd.isNil and "last_update" in lastUpd: 141 | var dbLastEpoch = lastUpd["last_update"].toFloat64() 142 | if abs(dbLastEpoch - lastUpdEpoch) > 0.01: 143 | lastUpdEpoch = dbLastEpoch 144 | return false 145 | return true 146 | 147 | proc watchBalanceConfigUpdates*() {.async.} = 148 | while true: 149 | let uptodate = await isBalanceConfigUpToDate() 150 | if not uptodate: 151 | initAllTables() 152 | await sleepAsync(20 * 1000) # 20 seconds 153 | --------------------------------------------------------------------------------