├── .gitignore ├── README.md ├── SPACESHIFTER.png ├── SPACESHIFTER.sh ├── art sources ├── attack1.aseprite ├── banshee.psd ├── map_slot.psd ├── oldHero.aseprite ├── shield.aseprite ├── slot_select.psd ├── ui.psd └── ui2.psd ├── assets ├── fonts │ ├── cn.ttf │ ├── en.ttf │ └── num.ttf ├── i18n │ ├── cn.lua │ └── en.lua ├── images │ ├── action_effect │ │ ├── a1d1.png │ │ ├── a1drop1.png │ │ ├── a1p1.png │ │ ├── a2d1.png │ │ ├── a2p1.png │ │ ├── attack1.png │ │ ├── attack2.png │ │ ├── attack3.png │ │ ├── defence1.png │ │ ├── drop1.png │ │ ├── drop2.png │ │ ├── graveWorld.png │ │ ├── h1p2.png │ │ ├── heal1.png │ │ ├── heal2.png │ │ ├── jump.png │ │ ├── move.png │ │ ├── pick2.png │ │ ├── roundAttack.png │ │ ├── spaceRecover.png │ │ └── universeRecover.png │ ├── action_icon │ │ ├── a1d1.png │ │ ├── a1drop1.png │ │ ├── a1p1.png │ │ ├── a2d1.png │ │ ├── a2p1.png │ │ ├── attack1.png │ │ ├── attack2.png │ │ ├── attack3.png │ │ ├── defence1.png │ │ ├── drop1.png │ │ ├── drop2.png │ │ ├── graveWorld.png │ │ ├── h1p2.png │ │ ├── heal1.png │ │ ├── heal2.png │ │ ├── jump.png │ │ ├── move.png │ │ ├── pick2.png │ │ ├── roundAttack.png │ │ ├── spaceRecover.png │ │ └── universeRecover.png │ ├── background.png │ ├── button_y.png │ ├── card_action.png │ ├── card_space.png │ ├── connection_enemy_frame.png │ ├── cursor.png │ ├── damage_tip.png │ ├── deck_bg.png │ ├── deck_count_bg.png │ ├── discard_select.png │ ├── enemy_frame.png │ ├── hand_count_bg.png │ ├── heal_tip.png │ ├── hp.png │ ├── map_slot.png │ ├── name.png │ ├── player_frame.png │ ├── portrait │ │ ├── banshee.png │ │ ├── ghost.png │ │ ├── player.png │ │ └── troll.png │ ├── reward_select.png │ ├── sfx │ │ ├── fireball.png │ │ └── round_attack.png │ ├── slot_select.png │ ├── space_chess │ │ ├── advCircle.png │ │ ├── castle.png │ │ ├── circle.png │ │ ├── circleFence.png │ │ ├── desert.png │ │ ├── fence.png │ │ ├── graveyard.png │ │ └── well.png │ ├── space_effect │ │ ├── advCircle.png │ │ ├── castle.png │ │ ├── circle.png │ │ ├── circleFence.png │ │ ├── desert.png │ │ ├── fence.png │ │ ├── graveyard.png │ │ └── well.png │ ├── space_icon │ │ ├── advCircle.png │ │ ├── castle.png │ │ ├── circle.png │ │ ├── circleFence.png │ │ ├── desert.png │ │ ├── fence.png │ │ ├── graveyard.png │ │ └── well.png │ ├── special_card.png │ ├── sprite │ │ ├── banshee.png │ │ ├── ghost.png │ │ ├── player.png │ │ └── troll.png │ ├── tip_cursor.png │ ├── tutorial_1_cn.png │ ├── tutorial_1_en.png │ ├── tutorial_2_cn.png │ └── tutorial_2_en.png ├── sounds │ ├── bgm.mp3 │ ├── fireball_hit.wav │ ├── fireball_start.wav │ ├── heal.wav │ ├── hit02.ogg │ ├── pick_card.wav │ ├── play_card.wav │ └── round_attack.ogg └── sprites │ ├── attack1.json │ ├── attack1.png │ ├── shield.json │ └── shield.png ├── conf.lua ├── devlog ├── title.png ├── title@15.png ├── title@2.png ├── trello.png ├── ui.png ├── 真机.jpg ├── 纸面原型0.jpg ├── 纸面原型1.jpg └── 纸面原型2.jpg ├── lib ├── ScreenManager.lua ├── compatibility.lua ├── cron.lua ├── flux.lua ├── hump │ ├── camera.lua │ ├── class.lua │ ├── gamestate.lua │ ├── signal.lua │ ├── timer.lua │ ├── vector-light.lua │ └── vector.lua ├── json.lua ├── keys.lua ├── peachy.lua └── slam.lua ├── main.lua ├── src ├── assets.lua ├── const.lua ├── entities │ ├── Actions.lua │ ├── Card.lua │ ├── Decks.lua │ ├── Enemies.lua │ ├── Map.lua │ ├── Player.lua │ └── Spaces.lua ├── screens │ ├── GameScreen.lua │ └── TitleScreen.lua ├── states │ ├── DiscardCardState.lua │ ├── EnemyActionState.lua │ ├── EnemyShowState.lua │ ├── IdleState.lua │ ├── InitState.lua │ ├── LifeCheckState.lua │ ├── LoseState.lua │ ├── NextEnemyState.lua │ ├── PickCardState.lua │ ├── PlayerChooseSlotState.lua │ ├── PlayerPlayCardState.lua │ ├── ResolutionState.lua │ ├── RewardState.lua │ ├── SystemMenuState.lua │ ├── TutorialState.lua │ ├── UpkeepState.lua │ └── WinState.lua ├── ui │ ├── Button.lua │ ├── Cursor.lua │ ├── InfoBar.lua │ ├── SelectionGroup.lua │ └── Window.lua └── utils.lua └── test ├── luaunit.lua ├── mock ├── mockPeachy.lua ├── mocklove.lua └── mocktimer.lua ├── tests.lua ├── testsAIChooseCards.lua ├── testsActions.lua ├── testsAttackAndDefence.lua ├── testsBanshee.lua ├── testsDiscardCardState.lua ├── testsFillAllDecks.lua ├── testsGhost.lua ├── testsHumpClass.lua ├── testsMap.lua ├── testsOfUtils.lua ├── testsPickCards.lua ├── testsPlaySpace.lua ├── testsSpaces.lua └── testsUpkeepState.lua /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio 2015 cache directory 2 | /.vs/ 3 | /.idea/ 4 | 5 | # Autogenerated VS/MD/Consulo solution and project files 6 | ExportedObj/ 7 | .consulo/ 8 | *.csproj 9 | *.unityproj 10 | *.sln 11 | *.suo 12 | *.tmp 13 | *.user 14 | *.userprefs 15 | *.pidb 16 | *.booproj 17 | *.svd 18 | *.pdb 19 | 20 | /release/ 21 | *.log 22 | *.love -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Project Deot 2 | 3 | > 这是我参加机核BOOOM gamejam活动的项目~ -------------------------------------------------------------------------------- /SPACESHIFTER.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/SPACESHIFTER.png -------------------------------------------------------------------------------- /SPACESHIFTER.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | love SpaceShifter.love gameshell 4 | -------------------------------------------------------------------------------- /art sources/attack1.aseprite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/art sources/attack1.aseprite -------------------------------------------------------------------------------- /art sources/banshee.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/art sources/banshee.psd -------------------------------------------------------------------------------- /art sources/map_slot.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/art sources/map_slot.psd -------------------------------------------------------------------------------- /art sources/oldHero.aseprite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/art sources/oldHero.aseprite -------------------------------------------------------------------------------- /art sources/shield.aseprite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/art sources/shield.aseprite -------------------------------------------------------------------------------- /art sources/slot_select.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/art sources/slot_select.psd -------------------------------------------------------------------------------- /art sources/ui.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/art sources/ui.psd -------------------------------------------------------------------------------- /art sources/ui2.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/art sources/ui2.psd -------------------------------------------------------------------------------- /assets/fonts/cn.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/fonts/cn.ttf -------------------------------------------------------------------------------- /assets/fonts/en.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/fonts/en.ttf -------------------------------------------------------------------------------- /assets/fonts/num.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/fonts/num.ttf -------------------------------------------------------------------------------- /assets/i18n/cn.lua: -------------------------------------------------------------------------------- 1 | local I18N = { 2 | language = "中文", 3 | start = '开始', 4 | exit = '退出', 5 | back = '返回', 6 | retry = '重试', 7 | system = '系统', 8 | confirm = '确认', 9 | dropUntil12 = '弃牌直至你的牌库为12张', 10 | win = '胜利!', 11 | update = '血量+1', 12 | fail = '失败!', 13 | chooseReward = '选择奖励,最多3张', 14 | next = '继续', 15 | flipCard = '卡牌翻面', 16 | -- actions 17 | infoAttack1 = '对指定格上的单位攻击1', 18 | infoAttack2 = '对指定格上的单位攻击2', 19 | infoAttack3 = '对指定格上的单位攻击3', 20 | infoA1D1 = '防御1并攻击1', 21 | infoA2D1 = '防御1并攻击2', 22 | infoDefence1 = '防御1点伤害', 23 | infoHeal1 = '提升1点血量', 24 | infoHeal2 = '提升2点血量', 25 | infoPick2 = '抽牌2', 26 | infoA1P1 = '攻击1抽牌1', 27 | infoA2P1 = '攻击2抽牌1', 28 | infoH1P2 = '加血1抽牌2', 29 | infoDrop1 = '使指定格上的单位弃牌1张', 30 | infoDrop2 = '使指定格上的单位弃牌2张', 31 | infoA1Drop1 = '对指定格上的单位攻击1弃牌1', 32 | infoMove = '移动到另一个时空', 33 | infoSpaceRecover = '目标时空恢复初始', 34 | infoUniverseRecover = '全部时空恢复初始', 35 | infoRoundAttack = '每3回合对邻近所有格造成2伤害', 36 | infoGraveWorld = '不受墓地影响,每3回合将所有地面变为墓地', 37 | infoJump = '每3回合跳跃到玩家位置,造成3点伤害并击退', 38 | -- spaces 39 | infoFence = '提升1点防御', 40 | infoCastle = '提升2点防御', 41 | infoCircle = '提升1点攻击', 42 | infoAdvCircle = '提升2点攻击', 43 | infoCircleFence = '提升1防御1攻击', 44 | infoGraveyard = '每回合受伤1', 45 | infoWell = '受伤时抽牌1', 46 | infoDesert = '当攻击时额外使目标弃牌1', 47 | -- characters 48 | namePlayer = '唤地师', 49 | nameBanshee = '巨蛇', 50 | nameGhost = '怨煞灵', 51 | nameTroll = '巨魔', 52 | } 53 | return I18N -------------------------------------------------------------------------------- /assets/i18n/en.lua: -------------------------------------------------------------------------------- 1 | local I18N = { 2 | language = "English", 3 | start = 'Start', 4 | exit = 'Exit', 5 | back = 'Back', 6 | retry = 'Retry', 7 | system = 'System', 8 | confirm = 'Confirm', 9 | dropUntil12 = 'Abandon cards to 12', 10 | win = 'You Win!', 11 | update = 'Your HP+1', 12 | fail = 'You Lose!', 13 | chooseReward = 'Choose rewards up to 3', 14 | next = 'Next', 15 | flipCard = 'Flip Card', 16 | -- actions 17 | infoAttack1 = 'Attack: Deal 1 damage to target slot', 18 | infoAttack2 = 'Attack: Deal 2 damage to target slot', 19 | infoAttack3 = 'Attack: Deal 3 damage to target slot', 20 | infoA1D1 = 'Defence 1 and attack 1', 21 | infoA2D1 = 'Defence 1 and attack 2', 22 | infoDefence1 = 'Defence: Prevent 1 damage', 23 | infoHeal1 = 'Heal 1', 24 | infoHeal2 = 'Heal 2', 25 | infoPick2 = 'Draw 2 cards', 26 | infoA1P1 = 'Attack 1 and draw 1 card', 27 | infoA2P1 = 'Attack 2 and draw 1 card', 28 | infoH1P2 = 'Heal 1 and draw 2 cards', 29 | infoDrop1 = 'Cause target abandon 1 card', 30 | infoDrop2 = 'Cause target abandon 2 card', 31 | infoA1Drop1 = 'Attack 1 and cause target abandon 1 card', 32 | infoMove = 'Transfer to another space', 33 | infoSpaceRecover = 'Recover target space', 34 | infoUniverseRecover = 'Recover all spaces', 35 | infoRoundAttack = 'Deal 2 damage to all surround spaces every 3 round', 36 | infoGraveWorld = 'Immune to Graveyard, Change all spaces to graveyard every 3 round', 37 | infoJump = 'Jump to player and cause 3 damage and push away every 3 rounds', 38 | -- spaces 39 | infoFence = 'Add 1 defence', 40 | infoCastle = 'Add 2 defence', 41 | infoCircle = 'Add 1 attack', 42 | infoAdvCircle = 'Add 2 attack', 43 | infoCircleFence = 'Add 1 attack and 1 defence', 44 | infoGraveyard = 'Cause 1 damage every round', 45 | infoWell = 'Draw 1 card when got hurt', 46 | infoDesert = 'Cause target abandon card when attacking', 47 | -- characters 48 | namePlayer = 'Space Shifter', 49 | nameBanshee = 'Python', 50 | nameGhost = 'Ghost', 51 | nameTroll = 'Troll', 52 | } 53 | return I18N -------------------------------------------------------------------------------- /assets/images/action_effect/a1d1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_effect/a1d1.png -------------------------------------------------------------------------------- /assets/images/action_effect/a1drop1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_effect/a1drop1.png -------------------------------------------------------------------------------- /assets/images/action_effect/a1p1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_effect/a1p1.png -------------------------------------------------------------------------------- /assets/images/action_effect/a2d1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_effect/a2d1.png -------------------------------------------------------------------------------- /assets/images/action_effect/a2p1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_effect/a2p1.png -------------------------------------------------------------------------------- /assets/images/action_effect/attack1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_effect/attack1.png -------------------------------------------------------------------------------- /assets/images/action_effect/attack2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_effect/attack2.png -------------------------------------------------------------------------------- /assets/images/action_effect/attack3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_effect/attack3.png -------------------------------------------------------------------------------- /assets/images/action_effect/defence1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_effect/defence1.png -------------------------------------------------------------------------------- /assets/images/action_effect/drop1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_effect/drop1.png -------------------------------------------------------------------------------- /assets/images/action_effect/drop2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_effect/drop2.png -------------------------------------------------------------------------------- /assets/images/action_effect/graveWorld.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_effect/graveWorld.png -------------------------------------------------------------------------------- /assets/images/action_effect/h1p2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_effect/h1p2.png -------------------------------------------------------------------------------- /assets/images/action_effect/heal1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_effect/heal1.png -------------------------------------------------------------------------------- /assets/images/action_effect/heal2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_effect/heal2.png -------------------------------------------------------------------------------- /assets/images/action_effect/jump.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_effect/jump.png -------------------------------------------------------------------------------- /assets/images/action_effect/move.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_effect/move.png -------------------------------------------------------------------------------- /assets/images/action_effect/pick2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_effect/pick2.png -------------------------------------------------------------------------------- /assets/images/action_effect/roundAttack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_effect/roundAttack.png -------------------------------------------------------------------------------- /assets/images/action_effect/spaceRecover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_effect/spaceRecover.png -------------------------------------------------------------------------------- /assets/images/action_effect/universeRecover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_effect/universeRecover.png -------------------------------------------------------------------------------- /assets/images/action_icon/a1d1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_icon/a1d1.png -------------------------------------------------------------------------------- /assets/images/action_icon/a1drop1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_icon/a1drop1.png -------------------------------------------------------------------------------- /assets/images/action_icon/a1p1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_icon/a1p1.png -------------------------------------------------------------------------------- /assets/images/action_icon/a2d1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_icon/a2d1.png -------------------------------------------------------------------------------- /assets/images/action_icon/a2p1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_icon/a2p1.png -------------------------------------------------------------------------------- /assets/images/action_icon/attack1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_icon/attack1.png -------------------------------------------------------------------------------- /assets/images/action_icon/attack2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_icon/attack2.png -------------------------------------------------------------------------------- /assets/images/action_icon/attack3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_icon/attack3.png -------------------------------------------------------------------------------- /assets/images/action_icon/defence1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_icon/defence1.png -------------------------------------------------------------------------------- /assets/images/action_icon/drop1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_icon/drop1.png -------------------------------------------------------------------------------- /assets/images/action_icon/drop2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_icon/drop2.png -------------------------------------------------------------------------------- /assets/images/action_icon/graveWorld.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_icon/graveWorld.png -------------------------------------------------------------------------------- /assets/images/action_icon/h1p2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_icon/h1p2.png -------------------------------------------------------------------------------- /assets/images/action_icon/heal1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_icon/heal1.png -------------------------------------------------------------------------------- /assets/images/action_icon/heal2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_icon/heal2.png -------------------------------------------------------------------------------- /assets/images/action_icon/jump.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_icon/jump.png -------------------------------------------------------------------------------- /assets/images/action_icon/move.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_icon/move.png -------------------------------------------------------------------------------- /assets/images/action_icon/pick2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_icon/pick2.png -------------------------------------------------------------------------------- /assets/images/action_icon/roundAttack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_icon/roundAttack.png -------------------------------------------------------------------------------- /assets/images/action_icon/spaceRecover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_icon/spaceRecover.png -------------------------------------------------------------------------------- /assets/images/action_icon/universeRecover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/action_icon/universeRecover.png -------------------------------------------------------------------------------- /assets/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/background.png -------------------------------------------------------------------------------- /assets/images/button_y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/button_y.png -------------------------------------------------------------------------------- /assets/images/card_action.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/card_action.png -------------------------------------------------------------------------------- /assets/images/card_space.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/card_space.png -------------------------------------------------------------------------------- /assets/images/connection_enemy_frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/connection_enemy_frame.png -------------------------------------------------------------------------------- /assets/images/cursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/cursor.png -------------------------------------------------------------------------------- /assets/images/damage_tip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/damage_tip.png -------------------------------------------------------------------------------- /assets/images/deck_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/deck_bg.png -------------------------------------------------------------------------------- /assets/images/deck_count_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/deck_count_bg.png -------------------------------------------------------------------------------- /assets/images/discard_select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/discard_select.png -------------------------------------------------------------------------------- /assets/images/enemy_frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/enemy_frame.png -------------------------------------------------------------------------------- /assets/images/hand_count_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/hand_count_bg.png -------------------------------------------------------------------------------- /assets/images/heal_tip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/heal_tip.png -------------------------------------------------------------------------------- /assets/images/hp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/hp.png -------------------------------------------------------------------------------- /assets/images/map_slot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/map_slot.png -------------------------------------------------------------------------------- /assets/images/name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/name.png -------------------------------------------------------------------------------- /assets/images/player_frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/player_frame.png -------------------------------------------------------------------------------- /assets/images/portrait/banshee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/portrait/banshee.png -------------------------------------------------------------------------------- /assets/images/portrait/ghost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/portrait/ghost.png -------------------------------------------------------------------------------- /assets/images/portrait/player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/portrait/player.png -------------------------------------------------------------------------------- /assets/images/portrait/troll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/portrait/troll.png -------------------------------------------------------------------------------- /assets/images/reward_select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/reward_select.png -------------------------------------------------------------------------------- /assets/images/sfx/fireball.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/sfx/fireball.png -------------------------------------------------------------------------------- /assets/images/sfx/round_attack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/sfx/round_attack.png -------------------------------------------------------------------------------- /assets/images/slot_select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/slot_select.png -------------------------------------------------------------------------------- /assets/images/space_chess/advCircle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/space_chess/advCircle.png -------------------------------------------------------------------------------- /assets/images/space_chess/castle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/space_chess/castle.png -------------------------------------------------------------------------------- /assets/images/space_chess/circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/space_chess/circle.png -------------------------------------------------------------------------------- /assets/images/space_chess/circleFence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/space_chess/circleFence.png -------------------------------------------------------------------------------- /assets/images/space_chess/desert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/space_chess/desert.png -------------------------------------------------------------------------------- /assets/images/space_chess/fence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/space_chess/fence.png -------------------------------------------------------------------------------- /assets/images/space_chess/graveyard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/space_chess/graveyard.png -------------------------------------------------------------------------------- /assets/images/space_chess/well.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/space_chess/well.png -------------------------------------------------------------------------------- /assets/images/space_effect/advCircle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/space_effect/advCircle.png -------------------------------------------------------------------------------- /assets/images/space_effect/castle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/space_effect/castle.png -------------------------------------------------------------------------------- /assets/images/space_effect/circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/space_effect/circle.png -------------------------------------------------------------------------------- /assets/images/space_effect/circleFence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/space_effect/circleFence.png -------------------------------------------------------------------------------- /assets/images/space_effect/desert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/space_effect/desert.png -------------------------------------------------------------------------------- /assets/images/space_effect/fence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/space_effect/fence.png -------------------------------------------------------------------------------- /assets/images/space_effect/graveyard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/space_effect/graveyard.png -------------------------------------------------------------------------------- /assets/images/space_effect/well.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/space_effect/well.png -------------------------------------------------------------------------------- /assets/images/space_icon/advCircle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/space_icon/advCircle.png -------------------------------------------------------------------------------- /assets/images/space_icon/castle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/space_icon/castle.png -------------------------------------------------------------------------------- /assets/images/space_icon/circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/space_icon/circle.png -------------------------------------------------------------------------------- /assets/images/space_icon/circleFence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/space_icon/circleFence.png -------------------------------------------------------------------------------- /assets/images/space_icon/desert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/space_icon/desert.png -------------------------------------------------------------------------------- /assets/images/space_icon/fence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/space_icon/fence.png -------------------------------------------------------------------------------- /assets/images/space_icon/graveyard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/space_icon/graveyard.png -------------------------------------------------------------------------------- /assets/images/space_icon/well.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/space_icon/well.png -------------------------------------------------------------------------------- /assets/images/special_card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/special_card.png -------------------------------------------------------------------------------- /assets/images/sprite/banshee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/sprite/banshee.png -------------------------------------------------------------------------------- /assets/images/sprite/ghost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/sprite/ghost.png -------------------------------------------------------------------------------- /assets/images/sprite/player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/sprite/player.png -------------------------------------------------------------------------------- /assets/images/sprite/troll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/sprite/troll.png -------------------------------------------------------------------------------- /assets/images/tip_cursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/tip_cursor.png -------------------------------------------------------------------------------- /assets/images/tutorial_1_cn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/tutorial_1_cn.png -------------------------------------------------------------------------------- /assets/images/tutorial_1_en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/tutorial_1_en.png -------------------------------------------------------------------------------- /assets/images/tutorial_2_cn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/tutorial_2_cn.png -------------------------------------------------------------------------------- /assets/images/tutorial_2_en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/images/tutorial_2_en.png -------------------------------------------------------------------------------- /assets/sounds/bgm.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/sounds/bgm.mp3 -------------------------------------------------------------------------------- /assets/sounds/fireball_hit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/sounds/fireball_hit.wav -------------------------------------------------------------------------------- /assets/sounds/fireball_start.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/sounds/fireball_start.wav -------------------------------------------------------------------------------- /assets/sounds/heal.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/sounds/heal.wav -------------------------------------------------------------------------------- /assets/sounds/hit02.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/sounds/hit02.ogg -------------------------------------------------------------------------------- /assets/sounds/pick_card.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/sounds/pick_card.wav -------------------------------------------------------------------------------- /assets/sounds/play_card.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/sounds/play_card.wav -------------------------------------------------------------------------------- /assets/sounds/round_attack.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/sounds/round_attack.ogg -------------------------------------------------------------------------------- /assets/sprites/attack1.json: -------------------------------------------------------------------------------- 1 | { "frames": [ 2 | { 3 | "filename": "attack1 0.aseprite", 4 | "frame": { "x": 0, "y": 0, "w": 48, "h": 24 }, 5 | "rotated": false, 6 | "trimmed": false, 7 | "spriteSourceSize": { "x": 0, "y": 0, "w": 48, "h": 24 }, 8 | "sourceSize": { "w": 48, "h": 24 }, 9 | "duration": 100 10 | }, 11 | { 12 | "filename": "attack1 1.aseprite", 13 | "frame": { "x": 48, "y": 0, "w": 48, "h": 24 }, 14 | "rotated": false, 15 | "trimmed": false, 16 | "spriteSourceSize": { "x": 0, "y": 0, "w": 48, "h": 24 }, 17 | "sourceSize": { "w": 48, "h": 24 }, 18 | "duration": 100 19 | }, 20 | { 21 | "filename": "attack1 2.aseprite", 22 | "frame": { "x": 96, "y": 0, "w": 48, "h": 24 }, 23 | "rotated": false, 24 | "trimmed": false, 25 | "spriteSourceSize": { "x": 0, "y": 0, "w": 48, "h": 24 }, 26 | "sourceSize": { "w": 48, "h": 24 }, 27 | "duration": 100 28 | }, 29 | { 30 | "filename": "attack1 3.aseprite", 31 | "frame": { "x": 144, "y": 0, "w": 48, "h": 24 }, 32 | "rotated": false, 33 | "trimmed": false, 34 | "spriteSourceSize": { "x": 0, "y": 0, "w": 48, "h": 24 }, 35 | "sourceSize": { "w": 48, "h": 24 }, 36 | "duration": 100 37 | } 38 | ], 39 | "meta": { 40 | "app": "http://www.aseprite.org/", 41 | "version": "1.2.15-x64", 42 | "image": "attack1.png", 43 | "format": "RGBA8888", 44 | "size": { "w": 192, "h": 24 }, 45 | "scale": "1", 46 | "frameTags": [ 47 | { "name": "effect", "from": 0, "to": 3, "direction": "forward" } 48 | ], 49 | "layers": [ 50 | { "name": "Layer 1", "opacity": 255, "blendMode": "normal" } 51 | ], 52 | "slices": [ 53 | ] 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /assets/sprites/attack1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/sprites/attack1.png -------------------------------------------------------------------------------- /assets/sprites/shield.json: -------------------------------------------------------------------------------- 1 | { "frames": [ 2 | { 3 | "filename": "shield 0.aseprite", 4 | "frame": { "x": 0, "y": 0, "w": 18, "h": 32 }, 5 | "rotated": false, 6 | "trimmed": false, 7 | "spriteSourceSize": { "x": 0, "y": 0, "w": 18, "h": 32 }, 8 | "sourceSize": { "w": 18, "h": 32 }, 9 | "duration": 100 10 | }, 11 | { 12 | "filename": "shield 1.aseprite", 13 | "frame": { "x": 18, "y": 0, "w": 18, "h": 32 }, 14 | "rotated": false, 15 | "trimmed": false, 16 | "spriteSourceSize": { "x": 0, "y": 0, "w": 18, "h": 32 }, 17 | "sourceSize": { "w": 18, "h": 32 }, 18 | "duration": 100 19 | }, 20 | { 21 | "filename": "shield 2.aseprite", 22 | "frame": { "x": 36, "y": 0, "w": 18, "h": 32 }, 23 | "rotated": false, 24 | "trimmed": false, 25 | "spriteSourceSize": { "x": 0, "y": 0, "w": 18, "h": 32 }, 26 | "sourceSize": { "w": 18, "h": 32 }, 27 | "duration": 100 28 | }, 29 | { 30 | "filename": "shield 3.aseprite", 31 | "frame": { "x": 54, "y": 0, "w": 18, "h": 32 }, 32 | "rotated": false, 33 | "trimmed": false, 34 | "spriteSourceSize": { "x": 0, "y": 0, "w": 18, "h": 32 }, 35 | "sourceSize": { "w": 18, "h": 32 }, 36 | "duration": 100 37 | }, 38 | { 39 | "filename": "shield 4.aseprite", 40 | "frame": { "x": 72, "y": 0, "w": 18, "h": 32 }, 41 | "rotated": false, 42 | "trimmed": false, 43 | "spriteSourceSize": { "x": 0, "y": 0, "w": 18, "h": 32 }, 44 | "sourceSize": { "w": 18, "h": 32 }, 45 | "duration": 100 46 | }, 47 | { 48 | "filename": "shield 5.aseprite", 49 | "frame": { "x": 90, "y": 0, "w": 18, "h": 32 }, 50 | "rotated": false, 51 | "trimmed": false, 52 | "spriteSourceSize": { "x": 0, "y": 0, "w": 18, "h": 32 }, 53 | "sourceSize": { "w": 18, "h": 32 }, 54 | "duration": 100 55 | } 56 | ], 57 | "meta": { 58 | "app": "http://www.aseprite.org/", 59 | "version": "1.2.15-x64", 60 | "image": "shield.png", 61 | "format": "RGBA8888", 62 | "size": { "w": 108, "h": 32 }, 63 | "scale": "1", 64 | "frameTags": [ 65 | { "name": "effect", "from": 0, "to": 5, "direction": "forward" } 66 | ], 67 | "layers": [ 68 | { "name": "Layer 1", "opacity": 255, "blendMode": "normal" } 69 | ], 70 | "slices": [ 71 | ] 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /assets/sprites/shield.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/assets/sprites/shield.png -------------------------------------------------------------------------------- /conf.lua: -------------------------------------------------------------------------------- 1 | function love.conf(t) 2 | t.window.title = "Space Shifter" -- The window title 3 | t.window.width = 320 -- Gameshell Screen Width 4 | t.window.height = 240 -- Gameshell Screen Height 5 | t.console = false -- Enable this while developing the game 6 | t.window.vsync = 1 7 | 8 | t.modules.audio = true -- Enable the audio module (boolean) 9 | t.modules.data = true -- Enable the data module (boolean) 10 | t.modules.event = true -- Enable the event module (boolean) 11 | t.modules.font = true -- Enable the font module (boolean) 12 | t.modules.graphics = true -- Enable the graphics module (boolean) 13 | t.modules.image = true -- Enable the image module (boolean) 14 | t.modules.joystick = false -- Enable the joystick module (boolean) 15 | t.modules.keyboard = true -- Enable the keyboard module (boolean) 16 | t.modules.math = true -- Enable the math module (boolean) 17 | t.modules.mouse = true -- Enable the mouse module (boolean) 18 | t.modules.physics = true -- Enable the physics module (boolean) 19 | t.modules.sound = true -- Enable the sound module (boolean) 20 | t.modules.system = true -- Enable the system module (boolean) 21 | t.modules.thread = true -- Enable the thread module (boolean) 22 | t.modules.timer = true -- Enable the timer module (boolean), Disabling it will result 0 delta time in love.update 23 | t.modules.touch = false -- Enable the touch module (boolean) 24 | t.modules.video = true -- Enable the video module (boolean) 25 | t.modules.window = true -- Enable the window module (boolean) 26 | 27 | t.window.borderless = true -- Make window borderless so when running the game on desktop while developing, it feels like you are on the gameshell 28 | t.accelerometerjoystick = false -- false, because Gameshell doesn't have a accelerometer 29 | end -------------------------------------------------------------------------------- /devlog/title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/devlog/title.png -------------------------------------------------------------------------------- /devlog/title@15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/devlog/title@15.png -------------------------------------------------------------------------------- /devlog/title@2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/devlog/title@2.png -------------------------------------------------------------------------------- /devlog/trello.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/devlog/trello.png -------------------------------------------------------------------------------- /devlog/ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/devlog/ui.png -------------------------------------------------------------------------------- /devlog/真机.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/devlog/真机.jpg -------------------------------------------------------------------------------- /devlog/纸面原型0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/devlog/纸面原型0.jpg -------------------------------------------------------------------------------- /devlog/纸面原型1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/devlog/纸面原型1.jpg -------------------------------------------------------------------------------- /devlog/纸面原型2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zephyr1125/SpaceShifter/050d7c86fc6f317952ff0b2ed614cc7bb8ec0316/devlog/纸面原型2.jpg -------------------------------------------------------------------------------- /lib/ScreenManager.lua: -------------------------------------------------------------------------------- 1 | local class = require('lib.hump.class') 2 | 3 | local ScreenManager = class {} 4 | 5 | function ScreenManager:init() 6 | self.routes = {} 7 | self.currentScreen = nil 8 | self.currentPath = '' 9 | 10 | self:registerEvents() 11 | end 12 | 13 | function ScreenManager:event(event, arguments) 14 | if arguments == nil then 15 | arguments = {} 16 | elseif type(arguments) ~= 'table' then 17 | arguments = {arguments} 18 | end 19 | 20 | if self.activeScreen and self.activeScreen[event] then 21 | self.activeScreen[event](--[[self=]] self.activeScreen, unpack(arguments)) 22 | end 23 | end 24 | 25 | function ScreenManager:register(path, screenClass) 26 | local newRoute = { 27 | path = path, 28 | instance = screenClass(self) 29 | } 30 | 31 | if path == '/' then -- if screen is the root path then register it by default 32 | newRoute.instance:activate() 33 | self.activeScreen = newRoute.instance 34 | self.currentPath = '/' 35 | end 36 | 37 | return table.insert(self.routes, newRoute) 38 | end 39 | 40 | function ScreenManager:view(path, ...) 41 | for _, route in pairs(self.routes) do 42 | if path == route.path then 43 | route.instance:activate(...) 44 | self.activeScreen = route.instance 45 | self.currentPath = path 46 | end 47 | end 48 | end 49 | 50 | -- Register Löve2D events 51 | function ScreenManager:registerEvents() 52 | local _self = self 53 | 54 | -- Only the ones that could be used on a gameshell are not commented out! 55 | -- The events that are commented out with four '-', cause trouble when not being used 56 | 57 | --function love.directorydropped(...) _self:event('directorydropped', ...) end 58 | function love.draw(...) _self:event('draw', ...) end 59 | ----function love.errhand(...) _self:event('errhand', ...) end 60 | ----function love.errorhandler(...) _self:event('errorhandler', ...) end 61 | --function love.filedropped(...) _self:event('filedropped', ...) end 62 | function love.focus(...) _self:event('focus', ...) end 63 | function love.keypressed(...) 64 | _self:event('keypressed', ...) 65 | end 66 | function love.keyreleased(...) _self:event('keyreleased', ...) end 67 | function love.lowmemory(...) _self:event('lowmemory', ...) end 68 | --function love.mousefocus(...) _self:event('mousefocus', ...) end 69 | --function love.mousemoved(...) _self:event('mousemoved', ...) end 70 | --function love.mousepressed(...) _self:event('mousepressed', ...) end 71 | --function love.mousereleased(...) _self:event('mousereleased', ...) end 72 | function love.quit(...) _self:event('quit', ...) end 73 | function love.resize(...) _self:event('resize', ...) end 74 | ----function love.run(...) _self:event('run', ...) end 75 | function love.textedited(...) _self:event('textedited', ...) end 76 | function love.textinput(...) _self:event('textinput', ...) end 77 | function love.threaderror(...) _self:event('threaderror', ...) end 78 | --function love.touchmoved(...) _self:event('touchmoved', ...) end 79 | --function love.touchpressed(...) _self:event('touchpressed', ...) end 80 | --function love.touchreleased(...) _self:event('touchreleased', ...) end 81 | function love.update(...) _self:event('update', ...) end 82 | function love.visible(...) _self:event('visible', ...) end 83 | --function love.wheelmoved(...) _self:event('wheelmoved', ...) end 84 | end 85 | 86 | return ScreenManager -------------------------------------------------------------------------------- /lib/compatibility.lua: -------------------------------------------------------------------------------- 1 | --if love._version_major >= 11 then 2 | -- local _setColor = love.graphics.setColor 3 | -- 4 | -- love.graphics.setColor = function (r, g, b, a) 5 | -- _setColor(r / 255, g / 255, b / 255, (a or 255) / 255) 6 | -- end 7 | --end -------------------------------------------------------------------------------- /lib/cron.lua: -------------------------------------------------------------------------------- 1 | local cron = { 2 | __VERSION = 'cron.lua 2.0.0', 3 | __DESCRIPTION = 'Time-related functions for lua', 4 | __URL = 'https://github.com/kikito/cron.lua', 5 | __LICENSE = [[ 6 | Copyright (c) 2011 Enrique García Cota 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a 9 | copy of this software and associated documentation files (the 10 | "Software"), to deal in the Software without restriction, including 11 | without limitation the rights to use, copy, modify, merge, publish, 12 | distribute, sublicense, and/or sell copies of the Software, and to 13 | permit persons to whom the Software is furnished to do so, subject to 14 | the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included 17 | in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 20 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | ]] 27 | } 28 | 29 | -- Private functions 30 | 31 | local function isCallable(callback) 32 | local tc = type(callback) 33 | if tc == 'function' then return true end 34 | if tc == 'table' then 35 | local mt = getmetatable(callback) 36 | return type(mt) == 'table' and type(mt.__call) == 'function' 37 | end 38 | return false 39 | end 40 | 41 | local function checkPositiveInteger(name, value) 42 | if type(value) ~= "number" or value < 0 then 43 | error(name .. " must be a positive number") 44 | end 45 | end 46 | 47 | local Clock = {} 48 | local Clock_mt = {__index = Clock} 49 | 50 | local function newClock(time, callback, update, ...) 51 | checkPositiveInteger('time', time) 52 | assert(isCallable(callback), "callback must be a function") 53 | 54 | return setmetatable({ 55 | time = time, 56 | callback = callback, 57 | args = {...}, 58 | running = 0, 59 | update = update 60 | }, Clock_mt) 61 | end 62 | 63 | local function updateAfterClock(self, dt) -- returns true if expired 64 | checkPositiveInteger('dt', dt) 65 | 66 | if self.running >= self.time then return true end 67 | 68 | self.running = self.running + dt 69 | 70 | if self.running >= self.time then 71 | self.callback(unpack(self.args)) 72 | return true 73 | end 74 | return false 75 | end 76 | 77 | local function updateEveryClock(self, dt) 78 | checkPositiveInteger('dt', dt) 79 | 80 | self.running = self.running + dt 81 | 82 | while self.running >= self.time do 83 | self.callback(unpack(self.args)) 84 | self.running = self.running - self.time 85 | end 86 | return false 87 | end 88 | 89 | function Clock:reset(running) 90 | running = running or 0 91 | checkPositiveInteger('running', running) 92 | 93 | self.running = running 94 | end 95 | 96 | 97 | function cron.after(time, callback, ...) 98 | return newClock(time, callback, updateAfterClock, ...) 99 | end 100 | 101 | function cron.every(time, callback, ...) 102 | return newClock(time, callback, updateEveryClock, ...) 103 | end 104 | 105 | return cron 106 | 107 | -------------------------------------------------------------------------------- /lib/flux.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- flux 3 | -- 4 | -- Copyright (c) 2016 rxi 5 | -- 6 | -- This library is free software; you can redistribute it and/or modify it 7 | -- under the terms of the MIT license. See LICENSE for details. 8 | -- 9 | 10 | local flux = { _version = "0.1.5" } 11 | flux.__index = flux 12 | 13 | flux.tweens = {} 14 | flux.easing = { linear = function(p) return p end } 15 | 16 | local easing = { 17 | quad = "p * p", 18 | cubic = "p * p * p", 19 | quart = "p * p * p * p", 20 | quint = "p * p * p * p * p", 21 | expo = "2 ^ (10 * (p - 1))", 22 | sine = "-math.cos(p * (math.pi * .5)) + 1", 23 | circ = "-(math.sqrt(1 - (p * p)) - 1)", 24 | back = "p * p * (2.7 * p - 1.7)", 25 | elastic = "-(2^(10 * (p - 1)) * math.sin((p - 1.075) * (math.pi * 2) / .3))" 26 | } 27 | 28 | local makefunc = function(str, expr) 29 | local load = loadstring or load 30 | return load("return function(p) " .. str:gsub("%$e", expr) .. " end")() 31 | end 32 | 33 | for k, v in pairs(easing) do 34 | flux.easing[k .. "in"] = makefunc("return $e", v) 35 | flux.easing[k .. "out"] = makefunc([[ 36 | p = 1 - p 37 | return 1 - ($e) 38 | ]], v) 39 | flux.easing[k .. "inout"] = makefunc([[ 40 | p = p * 2 41 | if p < 1 then 42 | return .5 * ($e) 43 | else 44 | p = 2 - p 45 | return .5 * (1 - ($e)) + .5 46 | end 47 | ]], v) 48 | end 49 | 50 | 51 | 52 | local tween = {} 53 | tween.__index = tween 54 | 55 | local function makefsetter(field) 56 | return function(self, x) 57 | local mt = getmetatable(x) 58 | if type(x) ~= "function" and not (mt and mt.__call) then 59 | error("expected function or callable", 2) 60 | end 61 | local old = self[field] 62 | self[field] = old and function() old() x() end or x 63 | return self 64 | end 65 | end 66 | 67 | local function makesetter(field, checkfn, errmsg) 68 | return function(self, x) 69 | if checkfn and not checkfn(x) then 70 | error(errmsg:gsub("%$x", tostring(x)), 2) 71 | end 72 | self[field] = x 73 | return self 74 | end 75 | end 76 | 77 | tween.ease = makesetter("_ease", 78 | function(x) return flux.easing[x] end, 79 | "bad easing type '$x'") 80 | tween.delay = makesetter("_delay", 81 | function(x) return type(x) == "number" end, 82 | "bad delay time; expected number") 83 | tween.onstart = makefsetter("_onstart") 84 | tween.onupdate = makefsetter("_onupdate") 85 | tween.oncomplete = makefsetter("_oncomplete") 86 | 87 | 88 | function tween.new(obj, time, vars) 89 | local self = setmetatable({}, tween) 90 | self.obj = obj 91 | self.rate = time > 0 and 1 / time or 0 92 | self.progress = time > 0 and 0 or 1 93 | self._delay = 0 94 | self._ease = "quadout" 95 | self.vars = {} 96 | for k, v in pairs(vars) do 97 | if type(v) ~= "number" then 98 | error("bad value for key '" .. k .. "'; expected number") 99 | end 100 | self.vars[k] = v 101 | end 102 | return self 103 | end 104 | 105 | 106 | function tween:init() 107 | for k, v in pairs(self.vars) do 108 | local x = self.obj[k] 109 | if type(x) ~= "number" then 110 | error("bad value on object key '" .. k .. "'; expected number") 111 | end 112 | self.vars[k] = { start = x, diff = v - x } 113 | end 114 | self.inited = true 115 | end 116 | 117 | 118 | function tween:after(...) 119 | local t 120 | if select("#", ...) == 2 then 121 | t = tween.new(self.obj, ...) 122 | else 123 | t = tween.new(...) 124 | end 125 | t.parent = self.parent 126 | self:oncomplete(function() flux.add(self.parent, t) end) 127 | return t 128 | end 129 | 130 | 131 | function tween:stop() 132 | flux.remove(self.parent, self) 133 | end 134 | 135 | 136 | 137 | function flux.group() 138 | return setmetatable({}, flux) 139 | end 140 | 141 | 142 | function flux:to(obj, time, vars) 143 | return flux.add(self, tween.new(obj, time, vars)) 144 | end 145 | 146 | 147 | function flux:update(deltatime) 148 | for i = #self, 1, -1 do 149 | local t = self[i] 150 | if t._delay > 0 then 151 | t._delay = t._delay - deltatime 152 | else 153 | if not t.inited then 154 | flux.clear(self, t.obj, t.vars) 155 | t:init() 156 | end 157 | if t._onstart then 158 | t._onstart() 159 | t._onstart = nil 160 | end 161 | t.progress = t.progress + t.rate * deltatime 162 | local p = t.progress 163 | local x = p >= 1 and 1 or flux.easing[t._ease](p) 164 | for k, v in pairs(t.vars) do 165 | t.obj[k] = v.start + x * v.diff 166 | end 167 | if t._onupdate then t._onupdate() end 168 | if p >= 1 then 169 | flux.remove(self, i) 170 | if t._oncomplete then t._oncomplete() end 171 | end 172 | end 173 | end 174 | end 175 | 176 | 177 | function flux:clear(obj, vars) 178 | for t in pairs(self[obj]) do 179 | if t.inited then 180 | for k in pairs(vars) do t.vars[k] = nil end 181 | end 182 | end 183 | end 184 | 185 | 186 | function flux:add(tween) 187 | -- Add to object table, create table if it does not exist 188 | local obj = tween.obj 189 | self[obj] = self[obj] or {} 190 | self[obj][tween] = true 191 | -- Add to array 192 | table.insert(self, tween) 193 | tween.parent = self 194 | return tween 195 | end 196 | 197 | 198 | function flux:remove(x) 199 | if type(x) == "number" then 200 | -- Remove from object table, destroy table if it is empty 201 | local obj = self[x].obj 202 | self[obj][self[x]] = nil 203 | if not next(self[obj]) then self[obj] = nil end 204 | -- Remove from array 205 | self[x] = self[#self] 206 | return table.remove(self) 207 | end 208 | for i, v in ipairs(self) do 209 | if v == x then 210 | return flux.remove(self, i) 211 | end 212 | end 213 | end 214 | 215 | 216 | 217 | local bound = { 218 | to = function(...) return flux.to(flux.tweens, ...) end, 219 | update = function(...) return flux.update(flux.tweens, ...) end, 220 | remove = function(...) return flux.remove(flux.tweens, ...) end, 221 | } 222 | setmetatable(bound, flux) 223 | 224 | return bound 225 | -------------------------------------------------------------------------------- /lib/hump/camera.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2010-2015 Matthias Richter 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | Except as contained in this notice, the name(s) of the above copyright holders 15 | shall not be used in advertising or otherwise to promote the sale, use or 16 | other dealings in this Software without prior written authorization. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | ]]-- 26 | 27 | local _PATH = (...):match('^(.*[%./])[^%.%/]+$') or '' 28 | local cos, sin = math.cos, math.sin 29 | 30 | local camera = {} 31 | camera.__index = camera 32 | 33 | -- Movement interpolators (for camera locking/windowing) 34 | camera.smooth = {} 35 | 36 | function camera.smooth.none() 37 | return function(dx,dy) return dx,dy end 38 | end 39 | 40 | function camera.smooth.linear(speed) 41 | assert(type(speed) == "number", "Invalid parameter: speed = "..tostring(speed)) 42 | return function(dx,dy, s) 43 | -- normalize direction 44 | local d = math.sqrt(dx*dx+dy*dy) 45 | local dts = math.min((s or speed) * love.timer.getDelta(), d) -- prevent overshooting the goal 46 | if d > 0 then 47 | dx,dy = dx/d, dy/d 48 | end 49 | 50 | return dx*dts, dy*dts 51 | end 52 | end 53 | 54 | function camera.smooth.damped(stiffness) 55 | assert(type(stiffness) == "number", "Invalid parameter: stiffness = "..tostring(stiffness)) 56 | return function(dx,dy, s) 57 | local dts = love.timer.getDelta() * (s or stiffness) 58 | return dx*dts, dy*dts 59 | end 60 | end 61 | 62 | 63 | local function new(x,y, zoom, rot, smoother) 64 | x,y = x or love.graphics.getWidth()/2, y or love.graphics.getHeight()/2 65 | zoom = zoom or 1 66 | rot = rot or 0 67 | smoother = smoother or camera.smooth.none() -- for locking, see below 68 | return setmetatable({x = x, y = y, scale = zoom, rot = rot, smoother = smoother}, camera) 69 | end 70 | 71 | function camera:lookAt(x,y) 72 | self.x, self.y = x, y 73 | return self 74 | end 75 | 76 | function camera:move(dx,dy) 77 | self.x, self.y = self.x + dx, self.y + dy 78 | return self 79 | end 80 | 81 | function camera:position() 82 | return self.x, self.y 83 | end 84 | 85 | function camera:rotate(phi) 86 | self.rot = self.rot + phi 87 | return self 88 | end 89 | 90 | function camera:rotateTo(phi) 91 | self.rot = phi 92 | return self 93 | end 94 | 95 | function camera:zoom(mul) 96 | self.scale = self.scale * mul 97 | return self 98 | end 99 | 100 | function camera:zoomTo(zoom) 101 | self.scale = zoom 102 | return self 103 | end 104 | 105 | function camera:attach(x,y,w,h, noclip) 106 | x,y = x or 0, y or 0 107 | w,h = w or love.graphics.getWidth(), h or love.graphics.getHeight() 108 | 109 | self._sx,self._sy,self._sw,self._sh = love.graphics.getScissor() 110 | if not noclip then 111 | love.graphics.setScissor(x,y,w,h) 112 | end 113 | 114 | local cx,cy = x+w/2, y+h/2 115 | love.graphics.push() 116 | love.graphics.translate(cx, cy) 117 | love.graphics.scale(self.scale) 118 | love.graphics.rotate(self.rot) 119 | love.graphics.translate(-self.x, -self.y) 120 | end 121 | 122 | function camera:detach() 123 | love.graphics.pop() 124 | love.graphics.setScissor(self._sx,self._sy,self._sw,self._sh) 125 | end 126 | 127 | function camera:draw(...) 128 | local x,y,w,h,noclip,func 129 | local nargs = select("#", ...) 130 | if nargs == 1 then 131 | func = ... 132 | elseif nargs == 5 then 133 | x,y,w,h,func = ... 134 | elseif nargs == 6 then 135 | x,y,w,h,noclip,func = ... 136 | else 137 | error("Invalid arguments to camera:draw()") 138 | end 139 | 140 | self:attach(x,y,w,h,noclip) 141 | func() 142 | self:detach() 143 | end 144 | 145 | -- world coordinates to camera coordinates 146 | function camera:cameraCoords(x,y, ox,oy,w,h) 147 | ox, oy = ox or 0, oy or 0 148 | w,h = w or love.graphics.getWidth(), h or love.graphics.getHeight() 149 | 150 | -- x,y = ((x,y) - (self.x, self.y)):rotated(self.rot) * self.scale + center 151 | local c,s = cos(self.rot), sin(self.rot) 152 | x,y = x - self.x, y - self.y 153 | x,y = c*x - s*y, s*x + c*y 154 | return x*self.scale + w/2 + ox, y*self.scale + h/2 + oy 155 | end 156 | 157 | -- camera coordinates to world coordinates 158 | function camera:worldCoords(x,y, ox,oy,w,h) 159 | ox, oy = ox or 0, oy or 0 160 | w,h = w or love.graphics.getWidth(), h or love.graphics.getHeight() 161 | 162 | -- x,y = (((x,y) - center) / self.scale):rotated(-self.rot) + (self.x,self.y) 163 | local c,s = cos(-self.rot), sin(-self.rot) 164 | x,y = (x - w/2 - ox) / self.scale, (y - h/2 - oy) / self.scale 165 | x,y = c*x - s*y, s*x + c*y 166 | return x+self.x, y+self.y 167 | end 168 | 169 | function camera:mousePosition(ox,oy,w,h) 170 | local mx,my = love.mouse.getPosition() 171 | return self:worldCoords(mx,my, ox,oy,w,h) 172 | end 173 | 174 | -- camera scrolling utilities 175 | function camera:lockX(x, smoother, ...) 176 | local dx, dy = (smoother or self.smoother)(x - self.x, self.y, ...) 177 | self.x = self.x + dx 178 | return self 179 | end 180 | 181 | function camera:lockY(y, smoother, ...) 182 | local dx, dy = (smoother or self.smoother)(self.x, y - self.y, ...) 183 | self.y = self.y + dy 184 | return self 185 | end 186 | 187 | function camera:lockPosition(x,y, smoother, ...) 188 | return self:move((smoother or self.smoother)(x - self.x, y - self.y, ...)) 189 | end 190 | 191 | function camera:lockWindow(x, y, x_min, x_max, y_min, y_max, smoother, ...) 192 | -- figure out displacement in camera coordinates 193 | x,y = self:cameraCoords(x,y) 194 | local dx, dy = 0,0 195 | if x < x_min then 196 | dx = x - x_min 197 | elseif x > x_max then 198 | dx = x - x_max 199 | end 200 | if y < y_min then 201 | dy = y - y_min 202 | elseif y > y_max then 203 | dy = y - y_max 204 | end 205 | 206 | -- transform displacement to movement in world coordinates 207 | local c,s = cos(-self.rot), sin(-self.rot) 208 | dx,dy = (c*dx - s*dy) / self.scale, (s*dx + c*dy) / self.scale 209 | 210 | -- move 211 | self:move((smoother or self.smoother)(dx,dy,...)) 212 | end 213 | 214 | -- the module 215 | return setmetatable({new = new, smooth = camera.smooth}, 216 | {__call = function(_, ...) return new(...) end}) 217 | -------------------------------------------------------------------------------- /lib/hump/class.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2010-2013 Matthias Richter 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | Except as contained in this notice, the name(s) of the above copyright holders 15 | shall not be used in advertising or otherwise to promote the sale, use or 16 | other dealings in this Software without prior written authorization. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | ]]-- 26 | 27 | local function include_helper(to, from, seen) 28 | if from == nil then 29 | return to 30 | elseif type(from) ~= 'table' then 31 | return from 32 | elseif seen[from] then 33 | return seen[from] 34 | end 35 | 36 | seen[from] = to 37 | for k,v in pairs(from) do 38 | k = include_helper({}, k, seen) -- keys might also be tables 39 | if to[k] == nil then 40 | to[k] = include_helper({}, v, seen) 41 | end 42 | end 43 | return to 44 | end 45 | 46 | -- deeply copies `other' into `class'. keys in `other' that are already 47 | -- defined in `class' are omitted 48 | local function include(class, other) 49 | return include_helper(class, other, {}) 50 | end 51 | 52 | -- returns a deep copy of `other' 53 | local function clone(other) 54 | return setmetatable(include({}, other), getmetatable(other)) 55 | end 56 | 57 | local function new(class) 58 | -- mixins 59 | class = class or {} -- class can be nil 60 | local inc = class.__includes or {} 61 | if getmetatable(inc) then inc = {inc} end 62 | 63 | for _, other in ipairs(inc) do 64 | if type(other) == "string" then 65 | other = _G[other] 66 | end 67 | include(class, other) 68 | end 69 | 70 | -- class implementation 71 | class.__index = class 72 | class.init = class.init or class[1] or function() end 73 | class.include = class.include or include 74 | class.clone = class.clone or clone 75 | 76 | -- constructor call 77 | return setmetatable(class, {__call = function(c, ...) 78 | local o = setmetatable({}, c) 79 | o:init(...) 80 | return o 81 | end}) 82 | end 83 | 84 | -- interface for cross class-system compatibility (see https://github.com/bartbes/Class-Commons). 85 | if class_commons ~= false and not common then 86 | common = {} 87 | function common.class(name, prototype, parent) 88 | return new{__includes = {prototype, parent}} 89 | end 90 | function common.instance(class, ...) 91 | return class(...) 92 | end 93 | end 94 | 95 | 96 | -- the module 97 | return setmetatable({new = new, include = include, clone = clone}, 98 | {__call = function(_,...) return new(...) end}) 99 | -------------------------------------------------------------------------------- /lib/hump/gamestate.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2010-2013 Matthias Richter 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | Except as contained in this notice, the name(s) of the above copyright holders 15 | shall not be used in advertising or otherwise to promote the sale, use or 16 | other dealings in this Software without prior written authorization. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | ]]-- 26 | 27 | local function __NULL__() end 28 | 29 | -- default gamestate produces error on every callback 30 | local state_init = setmetatable({leave = __NULL__}, 31 | {__index = function() error("Gamestate not initialized. Use Gamestate.switch()") end}) 32 | local stack = {state_init} 33 | local initialized_states = setmetatable({}, {__mode = "k"}) 34 | local state_is_dirty = true 35 | 36 | local GS = {} 37 | function GS.new(t) return t or {} end -- constructor - deprecated! 38 | 39 | local function change_state(stack_offset, to, ...) 40 | local pre = stack[#stack] 41 | 42 | -- initialize only on first call 43 | ;(initialized_states[to] or to.init or __NULL__)(to) 44 | initialized_states[to] = __NULL__ 45 | 46 | stack[#stack+stack_offset] = to 47 | state_is_dirty = true 48 | return (to.enter or __NULL__)(to, pre, ...) 49 | end 50 | 51 | function GS.switch(to, ...) 52 | assert(to, "Missing argument: Gamestate to switch to") 53 | assert(to ~= GS, "Can't call switch with colon operator") 54 | ;(stack[#stack].leave or __NULL__)(stack[#stack]) 55 | return change_state(0, to, ...) 56 | end 57 | 58 | function GS.push(to, ...) 59 | assert(to, "Missing argument: Gamestate to switch to") 60 | assert(to ~= GS, "Can't call push with colon operator") 61 | return change_state(1, to, ...) 62 | end 63 | 64 | function GS.pop(...) 65 | assert(#stack > 1, "No more states to pop!") 66 | local pre, to = stack[#stack], stack[#stack-1] 67 | stack[#stack] = nil 68 | ;(pre.leave or __NULL__)(pre) 69 | state_is_dirty = true 70 | return (to.resume or __NULL__)(to, pre, ...) 71 | end 72 | 73 | function GS.current() 74 | return stack[#stack] 75 | end 76 | 77 | -- fetch event callbacks from love.handlers 78 | local all_callbacks = { 'draw', 'errhand', 'update' } 79 | for k in pairs(love.handlers) do 80 | all_callbacks[#all_callbacks+1] = k 81 | end 82 | 83 | function GS.registerEvents(callbacks) 84 | local registry = {} 85 | callbacks = callbacks or all_callbacks 86 | for _, f in ipairs(callbacks) do 87 | registry[f] = love[f] or __NULL__ 88 | love[f] = function(...) 89 | registry[f](...) 90 | return GS[f](...) 91 | end 92 | end 93 | end 94 | 95 | -- forward any undefined functions 96 | setmetatable(GS, {__index = function(_, func) 97 | -- call function only if at least one 'update' was called beforehand 98 | -- (see issue #46) 99 | if not state_is_dirty or func == 'update' then 100 | state_is_dirty = false 101 | return function(...) 102 | return (stack[#stack][func] or __NULL__)(stack[#stack], ...) 103 | end 104 | end 105 | return __NULL__ 106 | end}) 107 | 108 | return GS 109 | -------------------------------------------------------------------------------- /lib/hump/signal.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2012-2013 Matthias Richter 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | Except as contained in this notice, the name(s) of the above copyright holders 15 | shall not be used in advertising or otherwise to promote the sale, use or 16 | other dealings in this Software without prior written authorization. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | ]]-- 26 | 27 | local Registry = {} 28 | Registry.__index = function(self, key) 29 | return Registry[key] or (function() 30 | local t = {} 31 | rawset(self, key, t) 32 | return t 33 | end)() 34 | end 35 | 36 | function Registry:register(s, f) 37 | self[s][f] = f 38 | return f 39 | end 40 | 41 | function Registry:emit(s, ...) 42 | for f in pairs(self[s]) do 43 | f(...) 44 | end 45 | end 46 | 47 | function Registry:remove(s, ...) 48 | local f = {...} 49 | for i = 1,select('#', ...) do 50 | self[s][f[i]] = nil 51 | end 52 | end 53 | 54 | function Registry:clear(...) 55 | local s = {...} 56 | for i = 1,select('#', ...) do 57 | self[s[i]] = {} 58 | end 59 | end 60 | 61 | function Registry:emitPattern(p, ...) 62 | for s in pairs(self) do 63 | if s:match(p) then self:emit(s, ...) end 64 | end 65 | end 66 | 67 | function Registry:registerPattern(p, f) 68 | for s in pairs(self) do 69 | if s:match(p) then self:register(s, f) end 70 | end 71 | return f 72 | end 73 | 74 | function Registry:removePattern(p, ...) 75 | for s in pairs(self) do 76 | if s:match(p) then self:remove(s, ...) end 77 | end 78 | end 79 | 80 | function Registry:clearPattern(p) 81 | for s in pairs(self) do 82 | if s:match(p) then self[s] = {} end 83 | end 84 | end 85 | 86 | -- instancing 87 | function Registry.new() 88 | return setmetatable({}, Registry) 89 | end 90 | 91 | -- default instance 92 | local default = Registry.new() 93 | 94 | -- module forwards calls to default instance 95 | local module = {} 96 | for k in pairs(Registry) do 97 | if k ~= "__index" then 98 | module[k] = function(...) return default[k](default, ...) end 99 | end 100 | end 101 | 102 | return setmetatable(module, {__call = Registry.new}) 103 | -------------------------------------------------------------------------------- /lib/hump/timer.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2010-2013 Matthias Richter 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | Except as contained in this notice, the name(s) of the above copyright holders 15 | shall not be used in advertising or otherwise to promote the sale, use or 16 | other dealings in this Software without prior written authorization. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | ]]-- 26 | 27 | local Timer = {} 28 | Timer.__index = Timer 29 | 30 | local function _nothing_() end 31 | 32 | function Timer:update(dt) 33 | local to_remove = {} 34 | 35 | for handle in pairs(self.functions) do 36 | -- handle: { 37 | -- time = , 38 | -- after = , 39 | -- during = , 40 | -- limit = , 41 | -- count = , 42 | -- } 43 | 44 | handle.time = handle.time + dt 45 | handle.during(dt, math.max(handle.limit - handle.time, 0)) 46 | 47 | while handle.time >= handle.limit and handle.count > 0 do 48 | if handle.after(handle.after) == false then 49 | handle.count = 0 50 | break 51 | end 52 | handle.time = handle.time - handle.limit 53 | handle.count = handle.count - 1 54 | end 55 | 56 | if handle.count == 0 then 57 | table.insert(to_remove, handle) 58 | end 59 | end 60 | 61 | for i = 1, #to_remove do 62 | self.functions[to_remove[i]] = nil 63 | end 64 | end 65 | 66 | function Timer:during(delay, during, after) 67 | local handle = { time = 0, during = during, after = after or _nothing_, limit = delay, count = 1 } 68 | self.functions[handle] = true 69 | return handle 70 | end 71 | 72 | function Timer:after(delay, func) 73 | return self:during(delay, _nothing_, func) 74 | end 75 | 76 | function Timer:every(delay, after, count) 77 | local count = count or math.huge -- exploit below: math.huge - 1 = math.huge 78 | local handle = { time = 0, during = _nothing_, after = after, limit = delay, count = count } 79 | self.functions[handle] = true 80 | return handle 81 | end 82 | 83 | function Timer:cancel(handle) 84 | self.functions[handle] = nil 85 | end 86 | 87 | function Timer:clear() 88 | self.functions = {} 89 | end 90 | 91 | function Timer:script(f) 92 | local co = coroutine.wrap(f) 93 | co(function(t) 94 | self:after(t, co) 95 | coroutine.yield() 96 | end) 97 | end 98 | 99 | Timer.tween = setmetatable({ 100 | -- helper functions 101 | out = function(f) -- 'rotates' a function 102 | return function(s, ...) return 1 - f(1-s, ...) end 103 | end, 104 | chain = function(f1, f2) -- concatenates two functions 105 | return function(s, ...) return (s < .5 and f1(2*s, ...) or 1 + f2(2*s-1, ...)) * .5 end 106 | end, 107 | 108 | -- useful tweening functions 109 | linear = function(s) return s end, 110 | quad = function(s) return s*s end, 111 | cubic = function(s) return s*s*s end, 112 | quart = function(s) return s*s*s*s end, 113 | quint = function(s) return s*s*s*s*s end, 114 | sine = function(s) return 1-math.cos(s*math.pi/2) end, 115 | expo = function(s) return 2^(10*(s-1)) end, 116 | circ = function(s) return 1 - math.sqrt(1-s*s) end, 117 | 118 | back = function(s,bounciness) 119 | bounciness = bounciness or 1.70158 120 | return s*s*((bounciness+1)*s - bounciness) 121 | end, 122 | 123 | bounce = function(s) -- magic numbers ahead 124 | local a,b = 7.5625, 1/2.75 125 | return math.min(a*s^2, a*(s-1.5*b)^2 + .75, a*(s-2.25*b)^2 + .9375, a*(s-2.625*b)^2 + .984375) 126 | end, 127 | 128 | elastic = function(s, amp, period) 129 | amp, period = amp and math.max(1, amp) or 1, period or .3 130 | return (-amp * math.sin(2*math.pi/period * (s-1) - math.asin(1/amp))) * 2^(10*(s-1)) 131 | end, 132 | }, { 133 | 134 | -- register new tween 135 | __call = function(tween, self, len, subject, target, method, after, ...) 136 | -- recursively collects fields that are defined in both subject and target into a flat list 137 | local function tween_collect_payload(subject, target, out) 138 | for k,v in pairs(target) do 139 | local ref = subject[k] 140 | assert(type(v) == type(ref), 'Type mismatch in field "'..k..'".') 141 | if type(v) == 'table' then 142 | tween_collect_payload(ref, v, out) 143 | else 144 | local ok, delta = pcall(function() return (v-ref)*1 end) 145 | assert(ok, 'Field "'..k..'" does not support arithmetic operations') 146 | out[#out+1] = {subject, k, delta} 147 | end 148 | end 149 | return out 150 | end 151 | 152 | method = tween[method or 'linear'] -- see __index 153 | local payload, t, args = tween_collect_payload(subject, target, {}), 0, {...} 154 | 155 | local last_s = 0 156 | return self:during(len, function(dt) 157 | t = t + dt 158 | local s = method(math.min(1, t/len), unpack(args)) 159 | local ds = s - last_s 160 | last_s = s 161 | for _, info in ipairs(payload) do 162 | local ref, key, delta = unpack(info) 163 | ref[key] = ref[key] + delta * ds 164 | end 165 | end, after) 166 | end, 167 | 168 | -- fetches function and generated compositions for method `key` 169 | __index = function(tweens, key) 170 | if type(key) == 'function' then return key end 171 | 172 | assert(type(key) == 'string', 'Method must be function or string.') 173 | if rawget(tweens, key) then return rawget(tweens, key) end 174 | 175 | local function construct(pattern, f) 176 | local method = rawget(tweens, key:match(pattern)) 177 | if method then return f(method) end 178 | return nil 179 | end 180 | 181 | local out, chain = rawget(tweens,'out'), rawget(tweens,'chain') 182 | return construct('^in%-([^-]+)$', function(...) return ... end) 183 | or construct('^out%-([^-]+)$', out) 184 | or construct('^in%-out%-([^-]+)$', function(f) return chain(f, out(f)) end) 185 | or construct('^out%-in%-([^-]+)$', function(f) return chain(out(f), f) end) 186 | or error('Unknown interpolation method: ' .. key) 187 | end}) 188 | 189 | -- Timer instancing 190 | function Timer.new() 191 | return setmetatable({functions = {}, tween = Timer.tween}, Timer) 192 | end 193 | 194 | -- default instance 195 | local default = Timer.new() 196 | 197 | -- module forwards calls to default instance 198 | local module = {} 199 | for k in pairs(Timer) do 200 | if k ~= "__index" then 201 | module[k] = function(...) return default[k](default, ...) end 202 | end 203 | end 204 | module.tween = setmetatable({}, { 205 | __index = Timer.tween, 206 | __newindex = function(k,v) Timer.tween[k] = v end, 207 | __call = function(t, ...) return default:tween(...) end, 208 | }) 209 | 210 | return setmetatable(module, {__call = Timer.new}) 211 | -------------------------------------------------------------------------------- /lib/hump/vector-light.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2012-2013 Matthias Richter 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | Except as contained in this notice, the name(s) of the above copyright holders 15 | shall not be used in advertising or otherwise to promote the sale, use or 16 | other dealings in this Software without prior written authorization. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | ]]-- 26 | 27 | local sqrt, cos, sin, atan2 = math.sqrt, math.cos, math.sin, math.atan2 28 | 29 | local function str(x,y) 30 | return "("..tonumber(x)..","..tonumber(y)..")" 31 | end 32 | 33 | local function mul(s, x,y) 34 | return s*x, s*y 35 | end 36 | 37 | local function div(s, x,y) 38 | return x/s, y/s 39 | end 40 | 41 | local function add(x1,y1, x2,y2) 42 | return x1+x2, y1+y2 43 | end 44 | 45 | local function sub(x1,y1, x2,y2) 46 | return x1-x2, y1-y2 47 | end 48 | 49 | local function permul(x1,y1, x2,y2) 50 | return x1*x2, y1*y2 51 | end 52 | 53 | local function dot(x1,y1, x2,y2) 54 | return x1*x2 + y1*y2 55 | end 56 | 57 | local function det(x1,y1, x2,y2) 58 | return x1*y2 - y1*x2 59 | end 60 | 61 | local function eq(x1,y1, x2,y2) 62 | return x1 == x2 and y1 == y2 63 | end 64 | 65 | local function lt(x1,y1, x2,y2) 66 | return x1 < x2 or (x1 == x2 and y1 < y2) 67 | end 68 | 69 | local function le(x1,y1, x2,y2) 70 | return x1 <= x2 and y1 <= y2 71 | end 72 | 73 | local function len2(x,y) 74 | return x*x + y*y 75 | end 76 | 77 | local function len(x,y) 78 | return sqrt(x*x + y*y) 79 | end 80 | 81 | local function fromPolar(angle, radius) 82 | radius = radius or 1 83 | return cos(angle)*radius, sin(angle)*radius 84 | end 85 | 86 | local function randomDirection(len_min, len_max) 87 | len_min = len_min or 1 88 | len_max = len_max or len_min 89 | 90 | assert(len_max > 0, "len_max must be greater than zero") 91 | assert(len_max >= len_min, "len_max must be greater than or equal to len_min") 92 | 93 | return fromPolar(math.random()*2*math.pi, 94 | math.random() * (len_max-len_min) + len_min) 95 | end 96 | 97 | local function toPolar(x, y) 98 | return atan2(y,x), len(x,y) 99 | end 100 | 101 | local function dist2(x1,y1, x2,y2) 102 | return len2(x1-x2, y1-y2) 103 | end 104 | 105 | local function dist(x1,y1, x2,y2) 106 | return len(x1-x2, y1-y2) 107 | end 108 | 109 | local function normalize(x,y) 110 | local l = len(x,y) 111 | if l > 0 then 112 | return x/l, y/l 113 | end 114 | return x,y 115 | end 116 | 117 | local function rotate(phi, x,y) 118 | local c, s = cos(phi), sin(phi) 119 | return c*x - s*y, s*x + c*y 120 | end 121 | 122 | local function perpendicular(x,y) 123 | return -y, x 124 | end 125 | 126 | local function project(x,y, u,v) 127 | local s = (x*u + y*v) / (u*u + v*v) 128 | return s*u, s*v 129 | end 130 | 131 | local function mirror(x,y, u,v) 132 | local s = 2 * (x*u + y*v) / (u*u + v*v) 133 | return s*u - x, s*v - y 134 | end 135 | 136 | -- ref.: http://blog.signalsondisplay.com/?p=336 137 | local function trim(maxLen, x, y) 138 | local s = maxLen * maxLen / len2(x, y) 139 | s = s > 1 and 1 or math.sqrt(s) 140 | return x * s, y * s 141 | end 142 | 143 | local function angleTo(x,y, u,v) 144 | if u and v then 145 | return atan2(y, x) - atan2(v, u) 146 | end 147 | return atan2(y, x) 148 | end 149 | 150 | -- the module 151 | return { 152 | str = str, 153 | 154 | fromPolar = fromPolar, 155 | toPolar = toPolar, 156 | randomDirection = randomDirection, 157 | 158 | -- arithmetic 159 | mul = mul, 160 | div = div, 161 | add = add, 162 | sub = sub, 163 | permul = permul, 164 | dot = dot, 165 | det = det, 166 | cross = det, 167 | 168 | -- relation 169 | eq = eq, 170 | lt = lt, 171 | le = le, 172 | 173 | -- misc operations 174 | len2 = len2, 175 | len = len, 176 | dist2 = dist2, 177 | dist = dist, 178 | normalize = normalize, 179 | rotate = rotate, 180 | perpendicular = perpendicular, 181 | project = project, 182 | mirror = mirror, 183 | trim = trim, 184 | angleTo = angleTo, 185 | } 186 | -------------------------------------------------------------------------------- /lib/hump/vector.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2010-2013 Matthias Richter 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | Except as contained in this notice, the name(s) of the above copyright holders 15 | shall not be used in advertising or otherwise to promote the sale, use or 16 | other dealings in this Software without prior written authorization. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | ]]-- 26 | 27 | local assert = assert 28 | local sqrt, cos, sin, atan2 = math.sqrt, math.cos, math.sin, math.atan2 29 | 30 | local vector = {} 31 | vector.__index = vector 32 | 33 | local function new(x,y) 34 | return setmetatable({x = x or 0, y = y or 0}, vector) 35 | end 36 | local zero = new(0,0) 37 | 38 | local function fromPolar(angle, radius) 39 | radius = radius or 1 40 | return new(cos(angle) * radius, sin(angle) * radius) 41 | end 42 | 43 | local function randomDirection(len_min, len_max) 44 | len_min = len_min or 1 45 | len_max = len_max or len_min 46 | 47 | assert(len_max > 0, "len_max must be greater than zero") 48 | assert(len_max >= len_min, "len_max must be greater than or equal to len_min") 49 | 50 | return fromPolar(math.random() * 2*math.pi, 51 | math.random() * (len_max-len_min) + len_min) 52 | end 53 | 54 | local function isvector(v) 55 | return type(v) == 'table' and type(v.x) == 'number' and type(v.y) == 'number' 56 | end 57 | 58 | function vector:clone() 59 | return new(self.x, self.y) 60 | end 61 | 62 | function vector:unpack() 63 | return self.x, self.y 64 | end 65 | 66 | function vector:__tostring() 67 | return "("..tonumber(self.x)..","..tonumber(self.y)..")" 68 | end 69 | 70 | function vector.__unm(a) 71 | return new(-a.x, -a.y) 72 | end 73 | 74 | function vector.__add(a,b) 75 | assert(isvector(a) and isvector(b), "Add: wrong argument types ( expected)") 76 | return new(a.x+b.x, a.y+b.y) 77 | end 78 | 79 | function vector.__sub(a,b) 80 | assert(isvector(a) and isvector(b), "Sub: wrong argument types ( expected)") 81 | return new(a.x-b.x, a.y-b.y) 82 | end 83 | 84 | function vector.__mul(a,b) 85 | if type(a) == "number" then 86 | return new(a*b.x, a*b.y) 87 | elseif type(b) == "number" then 88 | return new(b*a.x, b*a.y) 89 | else 90 | assert(isvector(a) and isvector(b), "Mul: wrong argument types ( or expected)") 91 | return a.x*b.x + a.y*b.y 92 | end 93 | end 94 | 95 | function vector.__div(a,b) 96 | assert(isvector(a) and type(b) == "number", "wrong argument types (expected / )") 97 | return new(a.x / b, a.y / b) 98 | end 99 | 100 | function vector.__eq(a,b) 101 | return a.x == b.x and a.y == b.y 102 | end 103 | 104 | function vector.__lt(a,b) 105 | return a.x < b.x or (a.x == b.x and a.y < b.y) 106 | end 107 | 108 | function vector.__le(a,b) 109 | return a.x <= b.x and a.y <= b.y 110 | end 111 | 112 | function vector.permul(a,b) 113 | assert(isvector(a) and isvector(b), "permul: wrong argument types ( expected)") 114 | return new(a.x*b.x, a.y*b.y) 115 | end 116 | 117 | function vector:toPolar() 118 | return new(atan2(self.x, self.y), self:len()) 119 | end 120 | 121 | function vector:len2() 122 | return self.x * self.x + self.y * self.y 123 | end 124 | 125 | function vector:len() 126 | return sqrt(self.x * self.x + self.y * self.y) 127 | end 128 | 129 | function vector.dist(a, b) 130 | assert(isvector(a) and isvector(b), "dist: wrong argument types ( expected)") 131 | local dx = a.x - b.x 132 | local dy = a.y - b.y 133 | return sqrt(dx * dx + dy * dy) 134 | end 135 | 136 | function vector.dist2(a, b) 137 | assert(isvector(a) and isvector(b), "dist: wrong argument types ( expected)") 138 | local dx = a.x - b.x 139 | local dy = a.y - b.y 140 | return (dx * dx + dy * dy) 141 | end 142 | 143 | function vector:normalizeInplace() 144 | local l = self:len() 145 | if l > 0 then 146 | self.x, self.y = self.x / l, self.y / l 147 | end 148 | return self 149 | end 150 | 151 | function vector:normalized() 152 | return self:clone():normalizeInplace() 153 | end 154 | 155 | function vector:rotateInplace(phi) 156 | local c, s = cos(phi), sin(phi) 157 | self.x, self.y = c * self.x - s * self.y, s * self.x + c * self.y 158 | return self 159 | end 160 | 161 | function vector:rotated(phi) 162 | local c, s = cos(phi), sin(phi) 163 | return new(c * self.x - s * self.y, s * self.x + c * self.y) 164 | end 165 | 166 | function vector:perpendicular() 167 | return new(-self.y, self.x) 168 | end 169 | 170 | function vector:projectOn(v) 171 | assert(isvector(v), "invalid argument: cannot project vector on " .. type(v)) 172 | -- (self * v) * v / v:len2() 173 | local s = (self.x * v.x + self.y * v.y) / (v.x * v.x + v.y * v.y) 174 | return new(s * v.x, s * v.y) 175 | end 176 | 177 | function vector:mirrorOn(v) 178 | assert(isvector(v), "invalid argument: cannot mirror vector on " .. type(v)) 179 | -- 2 * self:projectOn(v) - self 180 | local s = 2 * (self.x * v.x + self.y * v.y) / (v.x * v.x + v.y * v.y) 181 | return new(s * v.x - self.x, s * v.y - self.y) 182 | end 183 | 184 | function vector:cross(v) 185 | assert(isvector(v), "cross: wrong argument types ( expected)") 186 | return self.x * v.y - self.y * v.x 187 | end 188 | 189 | -- ref.: http://blog.signalsondisplay.com/?p=336 190 | function vector:trimInplace(maxLen) 191 | local s = maxLen * maxLen / self:len2() 192 | s = (s > 1 and 1) or math.sqrt(s) 193 | self.x, self.y = self.x * s, self.y * s 194 | return self 195 | end 196 | 197 | function vector:angleTo(other) 198 | if other then 199 | return atan2(self.y, self.x) - atan2(other.y, other.x) 200 | end 201 | return atan2(self.y, self.x) 202 | end 203 | 204 | function vector:trimmed(maxLen) 205 | return self:clone():trimInplace(maxLen) 206 | end 207 | 208 | 209 | -- the module 210 | return setmetatable({ 211 | new = new, 212 | fromPolar = fromPolar, 213 | randomDirection = randomDirection, 214 | isvector = isvector, 215 | zero = zero 216 | }, { 217 | __call = function(_, ...) return new(...) end 218 | }) 219 | -------------------------------------------------------------------------------- /lib/keys.lua: -------------------------------------------------------------------------------- 1 | -- GAMESHELL KEYMAP -- 2 | -- See here: https://github.com/clockworkpi/Keypad/blob/master/keymaps.png 3 | 4 | keys = {} 5 | 6 | -- A, B, X, Y 7 | keys.Y = 'u' 8 | keys.X = 'i' 9 | keys.A = 'k' 10 | keys.B = 'j' 11 | 12 | -- D-Pad 13 | keys.DPad_up = 'up' 14 | keys.DPad_down = 'down' 15 | keys.DPad_right = 'right' 16 | keys.DPad_left = 'left' 17 | 18 | -- Special Keys 19 | keys.Start = 'return' 20 | keys.Select = 'space' 21 | keys.Volume_down = 'kp-' 22 | keys.Volume_up = 'kp+' 23 | 24 | -- Menu 25 | keys.Menu = 'escape' 26 | 27 | -- Lightkey 28 | keys.LK1 = 'h' 29 | keys.LK2 = 'y' 30 | --keys.LK3 = keys.lk3 = '' <-- Not usable, is shift 31 | keys.LK4 = 'o' 32 | keys.LK5 = 'l' 33 | keys.LK1_shift = 'home' 34 | keys.LK2_shift = 'pageup' 35 | --keys.LK3_shift = lk3_shift = '' <-- Still not usable... 36 | keys.LK4_shift = 'pagedown' 37 | keys.LK5_shift = 'end' 38 | 39 | return keys -------------------------------------------------------------------------------- /lib/slam.lua: -------------------------------------------------------------------------------- 1 | -- Simple LOVE Audio Manager 2 | -- 3 | -- Copyright (c) 2011 Matthias Richter 4 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 5 | -- of this software and associated documentation files (the "Software"), to deal 6 | -- in the Software without restriction, including without limitation the rights 7 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | -- copies of the Software, and to permit persons to whom the Software is 9 | -- furnished to do so, subject to the following conditions: 10 | -- 11 | -- The above copyright notice and this permission notice shall be included in 12 | -- all copies or substantial portions of the Software. 13 | -- 14 | -- Except as contained in this notice, the name(s) of the above copyright holders 15 | -- shall not be used in advertising or otherwise to promote the sale, use or 16 | -- other dealings in this Software without prior written authorization. 17 | -- 18 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | -- THE SOFTWARE. 25 | -- 26 | 27 | local newInstance = love.audio.newSource 28 | local stop = love.audio.stop 29 | 30 | ------------------ 31 | -- source class -- 32 | ------------------ 33 | local Source = {} 34 | Source.__index = Source 35 | Source.__newindex = function(_,k) error(('Cannot write key %s'):format(tostring(k))) end 36 | 37 | local function remove_stopped(sources) 38 | local remove = {} 39 | for s in pairs(sources) do 40 | remove[s] = true 41 | end 42 | for s in pairs(remove) do 43 | sources[s] = nil 44 | end 45 | end 46 | 47 | local function get_target(target) 48 | if type(target) == 'table' then 49 | return target[math.random(1,#target)] 50 | end 51 | return target 52 | end 53 | 54 | local play_instance, stop_instance 55 | function Source:play() 56 | remove_stopped(self.instances) 57 | if self._paused then self:stop() end 58 | local instance = newInstance(get_target(self.target), self.how) 59 | 60 | -- overwrite instance:stop() and instance:play() 61 | if not (play_instance and stop_instance) then 62 | play_instance = getmetatable(instance).play 63 | getmetatable(instance).play = error 64 | 65 | stop_instance = getmetatable(instance).stop 66 | getmetatable(instance).stop = function(this) 67 | stop_instance(this) 68 | self.instances[this] = nil 69 | end 70 | end 71 | 72 | instance:setLooping(self.looping) 73 | instance:setPitch(self.pitch) 74 | instance:setVolume(self.volume) 75 | 76 | self.instances[instance] = instance 77 | play_instance(instance) 78 | return instance 79 | end 80 | 81 | function Source:stop() 82 | for s in pairs(self.instances) do 83 | s:stop() 84 | end 85 | self._paused = false 86 | self.instances = {} 87 | end 88 | 89 | function Source:pause() 90 | if self._paused then return end 91 | for s in pairs(self.instances) do 92 | s:pause() 93 | end 94 | self._paused = true 95 | end 96 | 97 | function Source:resume() 98 | if not self._paused then return end 99 | for s in pairs(self.instances) do 100 | s:resume() 101 | end 102 | self._paused = false 103 | end 104 | 105 | function Source:addTags(tag, ...) 106 | if not tag then return end 107 | love.audio.tags[tag][self] = self 108 | return Source.addTags(self, ...) 109 | end 110 | 111 | function Source:removeTags(tag, ...) 112 | if not tag then return end 113 | love.audio.tags[tag][self] = nil 114 | return Source.removeTags(self, ...) 115 | end 116 | 117 | function Source:isStatic() 118 | return self.how ~= "stream" 119 | end 120 | 121 | -- getter/setter for looping, pitch and volume 122 | for _, property in ipairs{'looping', 'pitch', 'volume'} do 123 | local name = property:sub(1,1):upper() .. property:sub(2) 124 | Source['get' .. name] = function(self) 125 | return self[property] 126 | end 127 | 128 | Source['set' .. name] = function(self, val) 129 | self[property] = val 130 | for s in pairs(self.instances) do 131 | s['set' .. name](s, val) 132 | end 133 | end 134 | end 135 | Source.isLooping = Source.getLooping 136 | 137 | -------------------------- 138 | -- love.audio interface -- 139 | -------------------------- 140 | function love.audio.newSource(target, how) 141 | local s = { 142 | _paused = false, 143 | target = target, 144 | how = how, 145 | instances = {}, 146 | looping = false, 147 | pitch = 1, 148 | volume = 1, 149 | } 150 | if how == 'static' and type(target) == 'string' then 151 | s.target = love.sound.newSoundData(target) 152 | end 153 | love.audio.tags.all[s] = s 154 | return setmetatable(s, Source) 155 | end 156 | 157 | function love.audio.play(source) 158 | assert(source and source.instances, "Can only play source objects.") 159 | return source:play() 160 | end 161 | 162 | function love.audio.stop(source) 163 | if source and source.stop then return source:stop() end 164 | stop() 165 | end 166 | 167 | ---------- 168 | -- tags -- 169 | ---------- 170 | local Tag = { __mode = "kv" } 171 | function Tag:__index(func) 172 | -- calls a function on all tagged sources 173 | return function(...) 174 | for s in pairs(self) do 175 | assert(type(s[func]) == "function", ("`%s' does not name a function."):format(func)) 176 | s[func](s, ...) 177 | end 178 | end 179 | end 180 | 181 | love.audio.tags = setmetatable({}, { 182 | __newindex = error, 183 | __index = function(t,k) 184 | local tag = setmetatable({}, Tag) 185 | rawset(t, k, tag) 186 | return tag 187 | end, 188 | }) 189 | -------------------------------------------------------------------------------- /main.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | -- LOADING SCREEN 3 | -- Uncomment to activate 4 | -- Can be an image for example 5 | 6 | love.graphics.clear(255,255,255) 7 | local w, h = love.window.getMode() 8 | 9 | > love.graphics.draw(...) 10 | > love.graphics.print(...) 11 | > ... 12 | 13 | love.graphics.present() 14 | ]]-- 15 | 16 | --log to IDE 17 | io.stdout:setvbuf("no") 18 | 19 | logs = {} 20 | --local oldprint = print 21 | --print = function(...) 22 | -- setColor({255, 255, 255}) 23 | -- logs[#logs] = ... 24 | --end 25 | 26 | class = require('lib.hump.class') 27 | timer = require('lib.hump.timer') 28 | keys = require('lib.keys') 29 | flux = require('lib.flux') 30 | peachy = require('lib.peachy') 31 | require('lib.slam') 32 | 33 | require('src.const') 34 | require('src.utils') 35 | 36 | -- UI 37 | Button = require('src.ui.Button') 38 | require('src.ui.SelectionGroup') 39 | Window = require('src.ui.Window') 40 | InfoBar = require('src.ui.InfoBar') 41 | Cursor = require('src.ui.Cursor') 42 | cursor = Cursor() 43 | -- assets 44 | require('src.assets') 45 | 46 | 47 | 48 | -- Load Libraries 49 | local ScreenManager = require('lib.ScreenManager') 50 | -- 51 | 52 | -- Load Screens 53 | local TitleScreen = require('src.screens.TitleScreen') 54 | local GameScreen = require('src.screens.GameScreen') 55 | -- 56 | 57 | -- Game States 58 | GameState = require "lib.hump.gamestate" 59 | require('src.states.IdleState') 60 | require('src.states.InitState') 61 | require('src.states.UpkeepState') 62 | require('src.states.EnemyActionState') 63 | require('src.states.PlayerPlayCardState') 64 | require('src.states.PlayerChooseSlotState') 65 | require('src.states.ResolutionState') 66 | require('src.states.PickCardState') 67 | require('src.states.LifeCheckState') 68 | require('src.states.WinState') 69 | require('src.states.LoseState') 70 | require('src.states.RewardState') 71 | require('src.states.DiscardCardState') 72 | require('src.states.NextEnemyState') 73 | require('src.states.SystemMenuState') 74 | require('src.states.TutorialState') 75 | require('src.states.EnemyShowState') 76 | 77 | i18n = require('assets.i18n.cn') 78 | 79 | -- Load Game 80 | function love.load() 81 | InitAssets() 82 | 83 | map = require('src.entities.Map') 84 | require('src.entities.Card') 85 | 86 | love.graphics.setDefaultFilter( "nearest", "nearest", 1 ) 87 | fontCN = love.graphics.newFont("assets/fonts/cn.ttf", fontSize) 88 | fontCN:setFilter( "nearest", "nearest", 0 ) 89 | love.graphics.setFont(fontCN) 90 | fontNum = love.graphics.newFont("assets/fonts/num.ttf", 11) 91 | fontNum:setFilter( "nearest", "nearest", 0 ) 92 | 93 | infoBar = InfoBar(infoBarWidth) 94 | 95 | screenManager = ScreenManager() 96 | 97 | screenManager:register('/', TitleScreen) 98 | screenManager:register('game', GameScreen) 99 | 100 | end 101 | 102 | local cachetable = {} 103 | for i = 0, math.pi * 2, math.pi / 1000 do 104 | cachetable[i] = math.sin(i) 105 | end 106 | 107 | function love.lowmemory() 108 | print("low memory") 109 | cachetable = {} 110 | collectgarbage() 111 | end 112 | 113 | 114 | -------------------------------------------------------------------------------- /src/assets.lua: -------------------------------------------------------------------------------- 1 | function InitAssets() 2 | -- UI 3 | imgName = love.graphics.newImage('assets/images/name.png') 4 | imgBackground = love.graphics.newImage('assets/images/background.png') 5 | imgTutorial1 = love.graphics.newImage('assets/images/tutorial_1_cn.png') 6 | imgTutorial2 = love.graphics.newImage('assets/images/tutorial_2_cn.png') 7 | imgSlotSelect = love.graphics.newImage('assets/images/slot_select.png') 8 | imgRewardSelect = love.graphics.newImage('assets/images/reward_select.png') 9 | imgDiscardSelect = love.graphics.newImage('assets/images/discard_select.png') 10 | imgCursor = love.graphics.newImage('assets/images/cursor.png') 11 | imgTipCursor = love.graphics.newImage('assets/images/tip_cursor.png') 12 | imgButtonY = love.graphics.newImage('assets/images/button_y.png') 13 | imgDamageTip = love.graphics.newImage('assets/images/damage_tip.png') 14 | imgHealTip = love.graphics.newImage('assets/images/heal_tip.png') 15 | imgCardActionBg = love.graphics.newImage('assets/images/card_action.png') 16 | imgCardSpaceBg = love.graphics.newImage('assets/images/card_space.png') 17 | imgPlayerFrame = love.graphics.newImage('assets/images/player_frame.png') 18 | imgEnemyFrame = love.graphics.newImage('assets/images/enemy_frame.png') 19 | imgConnectionEnemyFrame = love.graphics.newImage('assets/images/connection_enemy_frame.png') 20 | imgSpecialCard = love.graphics.newImage('assets/images/special_card.png') 21 | imgHPBg= love.graphics.newImage('assets/images/hp.png') 22 | imgDeckBg = love.graphics.newImage('assets/images/deck_bg.png') 23 | imgDeckCountBg = love.graphics.newImage('assets/images/deck_count_bg.png') 24 | imgHandCountBg = love.graphics.newImage('assets/images/hand_count_bg.png') 25 | -- characters 26 | imgPlayerSprite = love.graphics.newImage('assets/images/sprite/player.png') 27 | imgBansheeSprite = love.graphics.newImage('assets/images/sprite/banshee.png') 28 | imgGhostSprite = love.graphics.newImage('assets/images/sprite/ghost.png') 29 | imgTrollSprite = love.graphics.newImage('assets/images/sprite/troll.png') 30 | imgPlayerPortrait = love.graphics.newImage('assets/images/portrait/player.png') 31 | imgBansheePortrait = love.graphics.newImage('assets/images/portrait/banshee.png') 32 | imgGhostPortrait = love.graphics.newImage('assets/images/portrait/ghost.png') 33 | imgTrollPortrait = love.graphics.newImage('assets/images/portrait/troll.png') 34 | -- sprite 35 | spriteDefence = {'assets/sprites/shield.json','assets/sprites/shield.png', 'effect'} 36 | --sfx 37 | imgSFXFireball = love.graphics.newImage('assets/images/sfx/fireball.png') 38 | imgSFXRoundAttack = love.graphics.newImage('assets/images/sfx/round_attack.png') 39 | -- sound 40 | soundFireballStart = love.audio.newSource('assets/sounds/fireball_start.wav', 'static') 41 | soundFireballHit = love.audio.newSource('assets/sounds/fireball_hit.wav', 'static') 42 | soundRoundAttack = love.audio.newSource('assets/sounds/round_attack.ogg', 'static') 43 | soundHit2 = love.audio.newSource('assets/sounds/hit02.ogg', 'static') 44 | soundPickCard = love.audio.newSource('assets/sounds/pick_card.wav', 'static') 45 | soundPlayCard = love.audio.newSource('assets/sounds/play_card.wav', 'static') 46 | musicBGM = love.audio.newSource('assets/sounds/bgm.mp3', 'stream') 47 | end -------------------------------------------------------------------------------- /src/const.lua: -------------------------------------------------------------------------------- 1 | screenWidth = 320 2 | screenHeight = 240 3 | 4 | cardWidth = 40 5 | cardHeight = 52 6 | 7 | buttonWidth = 128 8 | buttonHeight = 18 9 | 10 | mapX = 46 11 | mapY = 60 12 | 13 | mapSlotWidth = 97 14 | mapSlotHeight = 31 15 | 16 | enemyInfoPos = {x = screenWidth-54, y = 4} 17 | 18 | enemyCardX = 222 - cardWidth 19 | enemyCardY = 4 20 | 21 | specialCardX = 226 22 | specialCardY = 4 23 | 24 | infoBarX = 4 25 | infoBarY = 216 26 | infoBarWidth = 312 27 | 28 | playerHandX = 72 29 | playerHandY = 160 30 | playerHandHideY = 208 31 | playerHandWidth = 200 32 | 33 | rewardCardX = 32 34 | rewardCardY = 96 35 | rewardCardWidth = screenWidth-64 36 | rewardCardInterval = (rewardCardWidth - cardWidth)/6 37 | 38 | white = {255,255,255} 39 | black = {0,0,0} 40 | green = {0,255,0} 41 | red = {255,0,0} 42 | halfCoverColor = {0,0,0,0.7} 43 | coverColor = {0,0,0,1} 44 | buttonIdleColor = {32, 32, 32} 45 | buttonSelectColor = {59, 133, 10} 46 | cardActionColor = {227,168,102} 47 | cardSpaceColor = {103,141,62} 48 | playerInfoBgColor = {136,151,166} 49 | enemyInfoBgColor = {150,85,86} 50 | windowColor = {163,154,137} 51 | infoBarBgColor = {240,234,216} 52 | infoBarTextColor = {119,119,119} 53 | infoBarFlipInfoColor = {87,152,250} 54 | 55 | fontSize = 12 -------------------------------------------------------------------------------- /src/entities/Card.lua: -------------------------------------------------------------------------------- 1 | Card = class{} 2 | 3 | function Card:init(action, space, x, y, deck) 4 | self.action = action 5 | self.space = space 6 | 7 | self.tweenWidth = 0 8 | self.isShowAction = true 9 | 10 | self.deck = deck 11 | 12 | x = x or screenWidth/2-cardWidth/2 13 | y = y or -cardHeight 14 | self.x = x 15 | self.y = y 16 | end 17 | 18 | function Card:draw() 19 | if self.isShowAction then 20 | self:drawCardAsAction() 21 | else 22 | self:drawCardAsSpace() 23 | end 24 | end 25 | 26 | function Card:drawCardAsAction() 27 | setColor(white) 28 | -- bg 29 | local imgBg 30 | if table.contains(self.action.type, 'special') then 31 | imgBg = imgSpecialCard 32 | else 33 | imgBg = imgCardActionBg 34 | end 35 | love.graphics.draw(imgBg, self.x+self.tweenWidth/2, self.y, 0, 36 | 1-(self.tweenWidth/cardWidth), 1) 37 | 38 | -- icon 39 | love.graphics.draw(self.action.icon, self.x+self.tweenWidth/2+4, self.y+4, 0, 40 | 1-(self.tweenWidth/cardWidth), 1) 41 | 42 | -- effect 43 | love.graphics.draw(self.action.effectIcon, self.x+self.tweenWidth/2+4, self.y+40, 0, 44 | 1-(self.tweenWidth/cardWidth), 1) 45 | end 46 | 47 | function Card:drawCardAsSpace() 48 | setColor(white) 49 | -- bg 50 | love.graphics.draw(imgCardSpaceBg, self.x+self.tweenWidth/2, self.y, 0, 51 | 1-(self.tweenWidth/cardWidth), 1) 52 | 53 | -- icon 54 | love.graphics.draw(self.space.icon, self.x+self.tweenWidth/2+4, self.y+4, 0, 55 | 1-(self.tweenWidth/cardWidth), 1) 56 | 57 | -- effect 58 | love.graphics.draw(self.space.effectIcon, self.x+self.tweenWidth/2+4, self.y+40, 0, 59 | 1-(self.tweenWidth/cardWidth), 1) 60 | end 61 | 62 | function Card:flip(caller, onFliped) 63 | flux.to(self, 0.1, {tweenWidth = cardWidth}):oncomplete(function() 64 | self.isShowAction = not self.isShowAction 65 | flux.to(self, 0.1, {tweenWidth = 0}) 66 | if onFliped~= nil then onFliped(caller) end 67 | end) 68 | end 69 | 70 | function Card:moveTo(targetX, targetY, time, onComplete, method) 71 | time = time or 0.4 72 | method = method or 'linear' 73 | --print(self.action.name..'|'..tostring(self.deck)..' move to '..tostring(targetX)..','..tostring(targetY)) 74 | timer.tween(time, self, {x = targetX, y = targetY}, method, onComplete) 75 | end -------------------------------------------------------------------------------- /src/entities/Decks.lua: -------------------------------------------------------------------------------- 1 | local function drawDeck(cards, x, y, drawTopDeck) 2 | if #cards > 0 and drawTopDeck ~= nil then 3 | setColor(white) 4 | love.graphics.draw(imgDeckBg, x, y+cardHeight-3) 5 | drawTopDeck(cards[#cards]) 6 | end 7 | 8 | --card count 9 | setColor(white) 10 | love.graphics.draw(imgDeckCountBg, x+cardWidth-16, y-2) 11 | love.graphics.setFont(fontNum) 12 | love.graphics.printf(tostring(#cards), x+cardWidth-16, y+1, 19, 'center') 13 | love.graphics.setFont(fontCN) 14 | end 15 | 16 | local function refillDeck(self) 17 | shuffle(self.discardCards) 18 | for _, card in pairs(self.discardCards) do 19 | card.isShowAction = true 20 | table.insert(self.cards, random(2, #self.cards), card) 21 | end 22 | table.clean(self.discardCards) 23 | print('refilled deck') 24 | end 25 | 26 | local function discardCard(self, card) 27 | table.add(self.discardCards, card) 28 | end 29 | 30 | -- if amount == 1 return 1 card, else return table of cards 31 | local function pickCards(self, amount) 32 | if #self.cards == 0 then 33 | refillDeck(self) 34 | end 35 | local picked = {} 36 | for _ = 1, amount do 37 | local card = table.remove(self.cards, #self.cards) 38 | if amount == 1 then 39 | return card 40 | else 41 | table.add(picked, card) 42 | end 43 | end 44 | return picked 45 | end 46 | 47 | local Decks = class { 48 | PublicDeck = { 49 | size = 70, 50 | scoreRange = {3,10}, 51 | discardCards = {}, 52 | x = -8-cardWidth, 53 | y = 4, 54 | draw = function(self) 55 | local topCard = self.cards[#self.cards] 56 | if topCard~=nil then 57 | drawDeck(self.cards, self.x, self.y, topCard.drawCardAsSpace) 58 | end 59 | end, 60 | pickCards = function(self, amount) 61 | return pickCards(self, amount) 62 | end, 63 | discardCard = function(self, card) 64 | discardCard(self, card) 65 | end, 66 | cards={} 67 | }, 68 | PlayerDeck = { 69 | size = 12, 70 | scoreRange = {2,3}, 71 | discardCards = {}, 72 | x = 276, 73 | y = 154, 74 | draw = function(self) 75 | --draw the latest dicard card 76 | local card = self.discardCards[#self.discardCards] 77 | if card~=nil and not floatequal(card.x, self.x, 0.1) then 78 | card:draw() 79 | end 80 | local topCard = self.cards[#self.cards] 81 | if topCard~=nil then 82 | drawDeck(self.cards, self.x, self.y, topCard.drawCardAsAction) 83 | end 84 | end, 85 | pickCards = function(self, amount) 86 | soundPickCard:play() 87 | return pickCards(self, amount) 88 | end, 89 | discardCard = function(self, card) 90 | discardCard(self, card) 91 | -- start card tween 92 | card:moveTo(self.x, self.y, 0.3) 93 | end, 94 | cards={ 95 | Card(actions.container.attack1, spaces.container.fence), 96 | Card(actions.container.attack1, spaces.container.fence), 97 | Card(actions.container.attack1, spaces.container.circle), 98 | Card(actions.container.attack2, spaces.container.fence), 99 | Card(actions.container.attack2, spaces.container.circle), 100 | } 101 | }, 102 | BansheeDeck = { 103 | size = 18, 104 | scoreRange = {2,3}, 105 | discardCards = {}, 106 | x = (screenWidth - cardWidth)/2, 107 | y = - cardHeight, 108 | draw = function(self, x, y) 109 | drawDeck(self.cards, x, y) 110 | end, 111 | pickCards = function(self, amount) 112 | return pickCards(self, amount) 113 | end, 114 | discardCard = function(self, card) 115 | discardCard(self, card) 116 | end, 117 | cards = { 118 | Card(actions.container.spaceRecover),Card(actions.container.spaceRecover), 119 | Card(actions.container.spaceRecover),Card(actions.container.spaceRecover) 120 | } 121 | }, 122 | GhostDeck = { 123 | size = 20, 124 | scoreRange = {3,4}, 125 | discardCards = {}, 126 | x = (screenWidth - cardWidth)/2, 127 | y = - cardHeight, 128 | draw = function(self, x, y) 129 | drawDeck(self.cards, x, y) 130 | end, 131 | pickCards = function(self, amount) 132 | return pickCards(self, amount) 133 | end, 134 | discardCard = function(self, card) 135 | discardCard(self, card) 136 | end, 137 | cards = {} 138 | }, 139 | TrollDeck = { 140 | size = 22, 141 | scoreRange = {3,5}, 142 | discardCards = {}, 143 | x = (screenWidth - cardWidth)/2, 144 | y = - cardHeight, 145 | draw = function(self, x, y) 146 | drawDeck(self.cards, x, y) 147 | end, 148 | pickCards = function(self, amount) 149 | return pickCards(self, amount) 150 | end, 151 | discardCard = function(self, card) 152 | discardCard(self, card) 153 | end, 154 | cards = { 155 | Card(actions.container.spaceRecover),Card(actions.container.spaceRecover), 156 | Card(actions.container.spaceRecover),Card(actions.container.universeRecover) 157 | } 158 | } 159 | } 160 | 161 | return Decks -------------------------------------------------------------------------------- /src/entities/Map.lua: -------------------------------------------------------------------------------- 1 | local Map = class { 2 | slots = { 3 | { 4 | neighbours = {0, 2, 7, 6, 0, 0}, 5 | x = 0, 6 | y = 16 7 | }, 8 | { 9 | neighbours = {0, 0, 3, 7, 1, 0}, 10 | x = 66, 11 | y = 0 12 | }, 13 | { 14 | neighbours = {0, 0, 0, 4, 7, 2}, 15 | x = 132, 16 | y = 16 17 | }, 18 | { 19 | neighbours = {3, 0, 0, 0, 5, 7}, 20 | x = 132, 21 | y = 48 22 | }, 23 | { 24 | neighbours = {7, 4, 0, 0, 0, 6}, 25 | x = 66, 26 | y = 64 27 | }, 28 | { 29 | neighbours = {1, 7, 5, 0, 0, 0}, 30 | right = 7, 31 | x = 0, 32 | y = 48 33 | }, 34 | { 35 | neighbours = {2, 3, 4, 5, 6, 1}, 36 | x = 66, 37 | y = 32 38 | } 39 | }, 40 | init = function(self, onComplete) 41 | -- pick space cards to slots 42 | for i, slot in pairs(self.slots) do 43 | local card = decks.PublicDeck:pickCards(1) 44 | card.isShowAction = false 45 | self.slots[i].baseCard = card 46 | self.slots[i].card = card 47 | timer.after(i*0.3, function() 48 | soundPlayCard:play() 49 | local callback = (i == #self.slots) and onComplete or nil 50 | card:moveTo(mapX+slot.x+28, mapY+slot.y-12, 0.3, callback, 'out-cubic') 51 | end) 52 | end 53 | end, 54 | draw = function(self, x, y) 55 | local sequence = {2,1,3,7,6,4,5} 56 | for _, slotId in pairs(sequence) do 57 | local slot = map.slots[slotId] 58 | setColor(white) 59 | if floatequal(slot.card.y, mapY+slot.y-12, 0.1) then 60 | -- space chess 61 | love.graphics.draw(slot.card.space.chess, x + slot.x, y + slot.y-8) 62 | end 63 | end 64 | end, 65 | drawCards = function(self) 66 | for id = #map.slots, 1, -1 do 67 | local slot = map.slots[id] 68 | setColor(white) 69 | if not floatequal(slot.card.y, mapY+slot.y-12, 0.1) then 70 | --space card 71 | slot.card:draw() 72 | end 73 | if not floatequal(slot.baseCard.y, mapY+slot.y-12, 0.1) then 74 | --space base card 75 | slot.baseCard:draw() 76 | end 77 | end 78 | end, 79 | -- returns neighbour id starts with 1 at up, and counter clockwise 80 | -- returns 0 if is not neighbour 81 | isNeighbour = function (self, a, b) 82 | for i, neighbour in pairs(self.slots[a].neighbours) do 83 | if neighbour == b then return i end 84 | end 85 | return 0 86 | end, 87 | getNeighbours = function(self, slot) 88 | if slot < 1 or slot > #self.slots then return {} end 89 | return {unpack(self.slots[slot].neighbours)} 90 | end, 91 | getNoHoleNeighbours = function(self, slot) 92 | --warning remove the 0s of neighbours table but be aware will loss direction information 93 | local neighbours = self:getNeighbours(slot) 94 | for i=#neighbours,1,-1 do 95 | if neighbours[i] == 0 then 96 | table.remove(neighbours, i) 97 | end 98 | end 99 | return neighbours 100 | end, 101 | randomNeighbour = function(self, slot, except) 102 | local neighbours = self:getNoHoleNeighbours(slot) 103 | -- remove the except slot 104 | for i, neighbour in pairs(neighbours) do 105 | if neighbour == except then table.remove(neighbours, i) end 106 | end 107 | return neighbours[random(#neighbours)] 108 | end, 109 | getSlotOccupied = function(self, slot) 110 | local resident 111 | if player.slot == slot then resident = player end 112 | if currentEnemy~=nil and currentEnemy.slot == slot then resident = currentEnemy end 113 | return resident 114 | end, 115 | getBestBenefitNeighbour = function (self, slot, isExceptOccupied) 116 | isExceptOccupied = isExceptOccupied or true 117 | local mostBenefit = self.slots[slot].card.space.benefit 118 | local mostSlot = slot 119 | for _, neighbour in pairs(self:getNoHoleNeighbours(slot)) do 120 | local skip = isExceptOccupied and self:getSlotOccupied(neighbour)~=nil 121 | local benefit = self.slots[neighbour].card.space.benefit 122 | if benefit > mostBenefit and not skip then 123 | mostBenefit = benefit 124 | mostSlot = neighbour 125 | end 126 | end 127 | return mostSlot 128 | end, 129 | shiftSpace = function(self, slotId, newCard, onComplete) 130 | local slot = self.slots[slotId] 131 | newCard = newCard or slot.baseCard 132 | local oldCard = slot.card 133 | if oldCard ~= slot.baseCard then 134 | discardCard(oldCard) 135 | end 136 | slot.card = newCard 137 | newCard:moveTo(mapX+ slot.x+28, mapY+ slot.y-12, 0.3, onComplete, 'out-cubic') 138 | end 139 | } 140 | return Map -------------------------------------------------------------------------------- /src/entities/Player.lua: -------------------------------------------------------------------------------- 1 | local function cardFlyToHand(self, id, card, time) 2 | local xInterval = (playerHandWidth - cardWidth)/(#self.hand <= 1 and 1 or #self.hand-1) 3 | local targetX = playerHandX + (id-1)*(xInterval > cardWidth+1 and cardWidth+1 or xInterval) 4 | local targetY = playerHandY - (self.currentCardId == id and 4 or 0) 5 | card:moveTo(targetX, targetY, time) 6 | end 7 | 8 | local Player = { 9 | name = i18n.namePlayer, 10 | portrait = imgPlayerPortrait, 11 | initLife = 8, 12 | life = 8, 13 | deck = 'PlayerDeck', 14 | handSize = 3, 15 | rewardSize = 3, 16 | spriteWidth = 8, 17 | spriteHeight = 18, 18 | isShaking = false, 19 | isInited = false, 20 | x = 0, 21 | y = -120, 22 | damageTip = {x = -4, baseY = -6, y = -6, img = imgDamageTip, value = 0}, 23 | healTip = {x = 16, baseY = -6, y = -6, img = imgHealTip, value = 0}, 24 | init = function(self, onComplete) 25 | self.life = self.initLife 26 | self.slot = 1 27 | charMove(self, 1, 'arrive', function() 28 | self.isInited = true 29 | self.hand = decks.PlayerDeck:pickCards(self.handSize) 30 | self.currentCardId = 1 31 | onComplete() 32 | end) 33 | end, 34 | upgrade = function(self) 35 | self.initLife = self.initLife + 1 36 | end, 37 | drawInfo = function(self, x, y) 38 | setColor(white) 39 | love.graphics.draw(imgPlayerFrame, x, y) 40 | 41 | --portrait 42 | love.graphics.draw(self.portrait, x+6, y+4) 43 | 44 | --name 45 | love.graphics.printf(self.name, x, y+32, 50, 'center') 46 | 47 | --life-- 48 | love.graphics.draw(imgHPBg, x+41, y) 49 | love.graphics.setFont(fontNum) 50 | love.graphics.printf(tostring(self.life), x+41, y+1, 18, 'center') 51 | love.graphics.setFont(fontCN) 52 | end, 53 | drawHand = function(self) 54 | if self.hand == nil or #self.hand == 0 then return end 55 | 56 | for id, card in pairs(self.hand) do 57 | if id~=currentCardId then 58 | card:draw() 59 | end 60 | end 61 | if self.currentCardId~=0 and self.currentCardId<=#self.hand then 62 | self.hand[self.currentCardId]:draw() 63 | end 64 | end, 65 | drawSprite = function(self, mapX, mapY) 66 | local shakeX = 0 67 | if self.isShaking then 68 | shakeX = love.math.random(-2, 2) 69 | end 70 | setColor(white) 71 | love.graphics.draw(imgPlayerSprite, mapX + self.x + shakeX, mapY + self.y) 72 | --spritePlayer:draw(mapX + self.x + shakeX, mapY + self.y) 73 | 74 | -- draw tips 75 | drawTip(self, self.damageTip) 76 | drawTip(self, self.healTip) 77 | end, 78 | update = function(self, dt) 79 | end, 80 | selectNext = function(self) 81 | if self.hand == nil or #self.hand == 0 then return end 82 | 83 | local prevId = self.currentCardId 84 | local id = self.currentCardId + 1 85 | if id > #self.hand then 86 | id = 1 87 | end 88 | self.currentCardId = id 89 | 90 | cardFlyToHand(self, prevId, self.hand[prevId],0.1) 91 | cardFlyToHand(self, self.currentCardId, self.hand[self.currentCardId],0.1) 92 | end, 93 | selectPrev = function(self) 94 | if self.hand == nil or #self.hand == 0 then return end 95 | 96 | local prevId = self.currentCardId 97 | local id = self.currentCardId - 1 98 | if id < 1 then 99 | id = #self.hand 100 | end 101 | self.currentCardId = id 102 | 103 | cardFlyToHand(self, prevId, self.hand[prevId],0.1) 104 | cardFlyToHand(self, self.currentCardId, self.hand[self.currentCardId],0.1) 105 | end, 106 | playCard = function(me, opponent) 107 | me.playingCard = me.hand[me.currentCardId] 108 | me.playingCardAsAction = me.playingCard.isShowAction 109 | if not me.playingCardAsAction then 110 | -- play space card always need choose slot 111 | return true 112 | elseif me.playingCardAsAction and me.playingCard.action.needChooseSlot then 113 | return true 114 | end 115 | return false 116 | end, 117 | pickCard = function(self) 118 | local card = decks.PlayerDeck:pickCards(1) 119 | -- unify card direction 120 | if #self.hand > 0 then card.isShowAction = self.hand[1].isShowAction end 121 | table.add(self.hand, card) 122 | print('player pick: '..card.action.name..'|'..card.space.name) 123 | end, 124 | dropCard = function(self) 125 | dropFirstHandCard(self) 126 | end, 127 | changeLife = function(self, value) 128 | changeLife(self, value) 129 | end, 130 | updateHandCardPositions = function(self) 131 | for i, card in pairs(self.hand) do 132 | cardFlyToHand(self, i, card) 133 | end 134 | end, 135 | hideUnplayedHandCards = function(self, onComplete) 136 | if #self.hand <= 1 then 137 | onComplete() 138 | return 139 | end 140 | 141 | for i, card in pairs(self.hand) do 142 | if card ~= self.playingCard then 143 | card:moveTo(card.x, playerHandHideY, 0.1, onComplete) 144 | end 145 | end 146 | end 147 | } 148 | return Player -------------------------------------------------------------------------------- /src/entities/Spaces.lua: -------------------------------------------------------------------------------- 1 | local function defaultTargetSlot(me, opponent) 2 | -- default to myself 3 | return me.slot 4 | end 5 | 6 | local Spaces = class { 7 | container = { 8 | fence = { 9 | name = '栅栏', 10 | info = i18n.infoFence, 11 | score = 1, 12 | benefit = 1, 13 | defaultTargetSlot = function(me, opponent) 14 | return defaultTargetSlot(me, opponent) 15 | end, 16 | onCalcDefence = function(me, opponent) 17 | -- if I am using defence, add 1 defence 18 | me.defence = me.defence + 1 19 | end 20 | }, 21 | castle = { 22 | name = '城堡', 23 | info = i18n.infoCastle, 24 | score = 2, 25 | benefit = 2, 26 | defaultTargetSlot = function(me, opponent) 27 | return defaultTargetSlot(me, opponent) 28 | end, 29 | onCalcDefence = function(me, opponent) 30 | -- if I am using defence, add 2 defence 31 | me.defence = me.defence + 2 32 | end 33 | }, 34 | circle = { 35 | name = '法阵', 36 | info = i18n.infoCircle, 37 | score = 1, 38 | benefit = 1, 39 | defaultTargetSlot = function(me, opponent) 40 | return defaultTargetSlot(me, opponent) 41 | end, 42 | onCalcAttack = function(me, opponent) 43 | -- if I am using attack, add 1 attack 44 | me.attack = me.attack + 1 45 | end 46 | }, 47 | advCircle = { 48 | name = '上级法阵', 49 | info = i18n.infoAdvCircle, 50 | score = 2, 51 | benefit = 2, 52 | defaultTargetSlot = function(me, opponent) 53 | return defaultTargetSlot(me, opponent) 54 | end, 55 | onCalcAttack = function(me, opponent) 56 | -- if I am using attack, add 2 attack 57 | me.attack = me.attack + 2 58 | end 59 | }, 60 | circleFence = { 61 | name = '防护法阵', 62 | info = i18n.infoCircleFence, 63 | score = 2, 64 | benefit = 2, 65 | defaultTargetSlot = function(me, opponent) 66 | return defaultTargetSlot(me, opponent) 67 | end, 68 | onCalcAttack = function(me, opponent) 69 | -- if I am using attack, add 1 attack 70 | me.attack = me.attack + 1 71 | end, 72 | onCalcDefence = function(me, opponent) 73 | -- if I am using defence, add 1 defence 74 | me.defence = me.defence + 1 75 | end 76 | }, 77 | graveyard = { 78 | name = '墓地', 79 | info = i18n.infoGraveyard, 80 | score = 2, 81 | benefit = -1, 82 | onUpkeep = function(resident) 83 | if resident ~= nil and not resident.isImmuneGrave then 84 | resident:changeLife(-1) 85 | end 86 | end, 87 | defaultTargetSlot = function(me, opponent) 88 | return defaultTargetSlot(me, opponent) 89 | end, 90 | }, 91 | --['bog'] = { 92 | -- name = '沼泽', 93 | -- info = '无法转移时空', 94 | -- score = 2, 95 | -- benefit = -1, 96 | -- onActionCheck = function(action) 97 | -- if table.contains(action.type, 'move') then 98 | -- return false 99 | -- else 100 | -- return true 101 | -- end 102 | -- end, 103 | -- defaultTargetSlot = function(me, opponent) 104 | -- return defaultTargetSlot(me, opponent) 105 | -- end, 106 | --}, 107 | well = { 108 | name = '法力井', 109 | info = i18n.infoWell, 110 | score = 2, 111 | benefit = 1, 112 | onDamaged = function(me) 113 | me:pickCard() 114 | end, 115 | defaultTargetSlot = function(me, opponent) 116 | return defaultTargetSlot(me, opponent) 117 | end, 118 | }, 119 | desert = { 120 | name = '沙漠', 121 | info = i18n.infoDesert, 122 | score = 2, 123 | benefit = -1, 124 | onAttack = function(me, opponent) 125 | opponent:dropCard() 126 | end, 127 | defaultTargetSlot = function(me, opponent) 128 | return defaultTargetSlot(me, opponent) 129 | end, 130 | }, 131 | } 132 | } 133 | return Spaces -------------------------------------------------------------------------------- /src/screens/GameScreen.lua: -------------------------------------------------------------------------------- 1 | local GameScreen = class {} 2 | 3 | function GameScreen:init(ScreenManager) 4 | self.screen = ScreenManager 5 | 6 | GameState.registerEvents() 7 | GameState.switch(IdleState) 8 | end 9 | 10 | function GameScreen:activate() 11 | bgmInstance:setVolume(0.4) 12 | cursor:setVisible(false) 13 | GameState.switch(InitState) 14 | end 15 | 16 | function GameScreen:update(dt) 17 | player:update(dt) 18 | timer.update(dt) 19 | flux.update(dt) 20 | end 21 | 22 | function GameScreen:draw() 23 | local state = GameState.current() 24 | if state == RewardState or state == DiscardCardState then 25 | return 26 | end 27 | 28 | self:drawBackground() 29 | map:draw(mapX, mapY) 30 | decks.PublicDeck:draw() 31 | if currentEnemy~=nil then 32 | currentEnemy:drawSprite(mapX, mapY) 33 | if currentEnemy.isInited then 34 | currentEnemy:drawSelectSlot() 35 | currentEnemy:drawInfo(enemyInfoPos.x, enemyInfoPos.y) 36 | currentEnemy:drawPlayingCard(enemyCardX, enemyCardY) 37 | end 38 | end 39 | player:drawSprite(mapX, mapY) 40 | if player.isInited then 41 | decks.PlayerDeck:draw() 42 | player:drawHand(72, 160, 200) 43 | player:drawInfo(4, 160) 44 | infoBar:draw(infoBarX, infoBarY) 45 | end 46 | map:drawCards(mapX, mapY) 47 | 48 | cursor:draw() 49 | --drawFPS() 50 | --drawLogs() 51 | end 52 | 53 | function GameScreen:drawBackground() 54 | setColor(white) 55 | love.graphics.draw(imgBackground, 0, 0) 56 | end 57 | 58 | function GameScreen:keypressed(key) 59 | if key == keys.Menu and GameState.current() ~= SystemMenuState then 60 | GameState.push(SystemMenuState) 61 | end 62 | end 63 | 64 | return GameScreen -------------------------------------------------------------------------------- /src/screens/TitleScreen.lua: -------------------------------------------------------------------------------- 1 | local TitleScreen = class {} 2 | 3 | local buttonWidth, buttonHeight = 128, 18 4 | local buttonStartY = 194-18 5 | 6 | local language = 'cn' 7 | 8 | function TitleScreen:init(ScreenManager) 9 | musicBGM:setLooping(true) 10 | musicBGM:setVolume(1) 11 | bgmInstance = musicBGM:play() 12 | 13 | self.screen = ScreenManager 14 | 15 | self.buttons = SelectionGroup() 16 | self.startButton = Button(i18n.start, buttonWidth, buttonHeight, 17 | buttonIdleColor, buttonSelectColor, function() 18 | self.screen:view('game') 19 | end) 20 | self.i18nButton = Button(i18n.language, buttonWidth, buttonHeight, 21 | buttonIdleColor, buttonSelectColor, function () 22 | if language == 'cn' then 23 | language = 'en' 24 | else 25 | language = 'cn' 26 | end 27 | i18n = reload('assets.i18n.'..language) 28 | self:refreshLanguage() 29 | end) 30 | self.exitButton = Button(i18n.exit, buttonWidth, buttonHeight, 31 | buttonIdleColor, buttonSelectColor, function () 32 | love.event.quit() 33 | end) 34 | self.buttons:add(self.startButton) 35 | self.buttons:add(self.i18nButton) 36 | self.buttons:add(self.exitButton) 37 | end 38 | 39 | function TitleScreen:refreshLanguage() 40 | imgTutorial1 = love.graphics.newImage('assets/images/tutorial_1_'..language..'.png') 41 | imgTutorial2 = love.graphics.newImage('assets/images/tutorial_2_'..language..'.png') 42 | fontSize = (language == 'cn') and 12 or 8 43 | fontCN = love.graphics.newFont('assets/fonts/'..language..'.ttf', fontSize) 44 | self.startButton.text = i18n.start 45 | self.i18nButton.text = i18n.language 46 | self.exitButton.text = i18n.exit 47 | end 48 | 49 | function TitleScreen:activate() 50 | bgmInstance:setVolume(1) 51 | end 52 | 53 | function TitleScreen:activate() 54 | end 55 | 56 | function TitleScreen:draw() 57 | setColor(white) 58 | love.graphics.draw(imgBackground, 0, 0) 59 | love.graphics.draw(imgName, (screenWidth - 198)/2, (screenHeight-88)/2 - buttonHeight) 60 | --love.graphics.print('Space Shifter', 10, 10) 61 | love.graphics.setFont(fontCN) 62 | self.startButton:draw((screenWidth-buttonWidth)/2, buttonStartY) 63 | self.i18nButton:draw((screenWidth-buttonWidth)/2, buttonStartY+buttonHeight) 64 | self.exitButton:draw((screenWidth-buttonWidth)/2, buttonStartY+buttonHeight*2) 65 | setColor(white) 66 | cursor:draw() 67 | --drawFPS() 68 | --drawLogs() 69 | end 70 | 71 | function TitleScreen:update(dt) 72 | timer.update(dt) 73 | end 74 | 75 | function TitleScreen:keypressed(key) 76 | self.buttons:keypressed(key) 77 | 78 | if key == keys.DPad_up then 79 | self.buttons:Prev() 80 | elseif key == keys.DPad_down then 81 | self.buttons:Next() 82 | elseif key == keys.Menu then 83 | love.event.quit() 84 | end 85 | end 86 | 87 | return TitleScreen -------------------------------------------------------------------------------- /src/states/DiscardCardState.lua: -------------------------------------------------------------------------------- 1 | DiscardCardState = {} 2 | 3 | local function drawCard(id, card) 4 | card:draw() 5 | -- draw selection 6 | if card.isDiscardSelected then 7 | setColor(white) 8 | love.graphics.draw(imgDiscardSelect, card.x + cardWidth - 19, card.y + 2) 9 | end 10 | end 11 | 12 | local function isDiscardFull() 13 | local discardSum = #decks.PlayerDeck.cards - decks.PlayerDeck.size 14 | for _, card in pairs(decks.PlayerDeck.cards) do 15 | if card.isDiscardSelected then 16 | discardSum = discardSum - 1 17 | end 18 | end 19 | return discardSum <= 0 20 | end 21 | 22 | local function refreshInfoBar(self) 23 | infoBar:setCardInfo(decks.PlayerDeck.cards[self.currentCardId]) 24 | end 25 | 26 | local function RefreshSelectCard(prevId, newId) 27 | local prevCard = decks.PlayerDeck.cards[prevId] 28 | local newCard = decks.PlayerDeck.cards[newId] 29 | prevCard:moveTo(prevCard.x, prevCard.y+3, 0.1) 30 | newCard:moveTo(newCard.x, newCard.y-3, 0.1) 31 | end 32 | 33 | function DiscardCardState:init() 34 | self.confirmButton = Button(i18n.confirm, buttonWidth, buttonHeight, 35 | buttonIdleColor, buttonSelectColor, function() 36 | if not isDiscardFull() then return end 37 | self:confirmSelection() 38 | for i, card in pairs(decks.PlayerDeck.cards) do 39 | card:moveTo(decks.PlayerDeck.x, decks.PlayerDeck.y, 0.3, 40 | i == #decks.PlayerDeck.cards and function() 41 | GameState.switch(NextEnemyState) 42 | end or nil) 43 | end 44 | end) 45 | end 46 | 47 | function DiscardCardState:enter() 48 | self.currentCardId = 1 49 | self.isSelectingCard = true 50 | self.confirmButton:setSelect(false) 51 | 52 | local xInterval = 4 53 | local yInterval = 4 54 | local width = 5*cardWidth + 4*xInterval 55 | local startX = screenWidth/2- width/2 56 | local startY = 22 57 | 58 | for id, card in pairs(decks.PlayerDeck.cards) do 59 | card.isDiscardSelected = false 60 | card.isShowAction = true 61 | local selectionOffsetY 62 | if id == 1 then selectionOffsetY = -3 else selectionOffsetY = 0 end 63 | local x = startX + math.fmod(id-1, 5) * (cardWidth + xInterval) 64 | local y = startY + math.floor((id-1)/5) * (cardHeight + yInterval) + selectionOffsetY 65 | card:moveTo(x, y) 66 | end 67 | 68 | cursor:setVisible(true) 69 | 70 | infoBar:setShowFlipInfo(true) 71 | refreshInfoBar(self) 72 | end 73 | 74 | function DiscardCardState:draw() 75 | setColor(coverColor) 76 | love.graphics.rectangle('fill', 0, 0, screenWidth, screenHeight) 77 | 78 | setColor(white) 79 | love.graphics.printf(i18n.dropUntil12, 0, 4, screenWidth, 'center') 80 | 81 | setColor(white) 82 | self:drawPlayerDeck() 83 | 84 | self.confirmButton:draw((screenWidth-buttonWidth)/2, screenHeight - 28 - buttonHeight) 85 | 86 | infoBar:draw(infoBarX, infoBarY) 87 | 88 | cursor:draw() 89 | end 90 | 91 | function DiscardCardState:drawPlayerDeck() 92 | for id, card in pairs(decks.PlayerDeck.cards) do 93 | if id ~= self.currentCardId or not self.isSelectingCard then 94 | drawCard(id, card) 95 | end 96 | end 97 | if self.currentCardId ~= 0 and self.isSelectingCard then 98 | drawCard(self.currentCardId, decks.PlayerDeck.cards[self.currentCardId]) 99 | end 100 | end 101 | 102 | function DiscardCardState:confirmSelection() 103 | for i = #decks.PlayerDeck.cards, 1, -1 do 104 | local card = decks.PlayerDeck.cards[i] 105 | if card.isDiscardSelected then 106 | -- selected card removed 107 | table.remove(decks.PlayerDeck.cards, i) 108 | end 109 | card.isDiscardSelected = nil 110 | end 111 | end 112 | 113 | function DiscardCardState:selectLeft() 114 | self:setSelectingCard(true) 115 | local prevId = self.currentCardId 116 | local id = self.currentCardId - 1 117 | if id < 1 then 118 | id = #decks.PlayerDeck.cards 119 | end 120 | self.currentCardId = id 121 | RefreshSelectCard(prevId, id) 122 | end 123 | 124 | function DiscardCardState:selectRight() 125 | self:setSelectingCard(true) 126 | local prevId = self.currentCardId 127 | local id = self.currentCardId + 1 128 | if id > #decks.PlayerDeck.cards then 129 | id = 1 130 | end 131 | self.currentCardId = id 132 | RefreshSelectCard(prevId, id) 133 | end 134 | 135 | function DiscardCardState:selectUp() 136 | if not self.isSelectingCard then 137 | self:setSelectingCard(true) 138 | else 139 | local prevId = self.currentCardId 140 | local id = self.currentCardId - 5 141 | if id >= 1 then 142 | self.currentCardId = id 143 | RefreshSelectCard(prevId, id) 144 | end 145 | end 146 | end 147 | 148 | function DiscardCardState:selectDown() 149 | local id = self.currentCardId + 5 150 | if id <= #decks.PlayerDeck.cards then 151 | self:setSelectingCard(true) 152 | local prevId = self.currentCardId 153 | self.currentCardId = id 154 | RefreshSelectCard(prevId, id) 155 | else 156 | self:setSelectingCard(false) 157 | end 158 | end 159 | 160 | function DiscardCardState:update(dt) 161 | if self.isSelectingCard then 162 | cursor:moveToCard(decks.PlayerDeck.cards[self.currentCardId]) 163 | end 164 | end 165 | 166 | function DiscardCardState:keypressed(key) 167 | if key == keys.Y then 168 | for id, card in pairs(decks.PlayerDeck.cards) do 169 | if id == self.currentCardId then 170 | card:flip(self, refreshInfoBar) 171 | else 172 | card:flip() 173 | end 174 | end 175 | end 176 | 177 | if key == keys.DPad_left then 178 | self:selectLeft() 179 | end 180 | 181 | if key == keys.DPad_right then 182 | self:selectRight() 183 | end 184 | 185 | if key == keys.DPad_up then 186 | self:selectUp() 187 | end 188 | 189 | if key == keys.DPad_down then 190 | self:selectDown() 191 | end 192 | 193 | if key == keys.A and self.isSelectingCard then 194 | local currentCard = decks.PlayerDeck.cards[self.currentCardId] 195 | local selected = currentCard.isDiscardSelected 196 | if isDiscardFull() then 197 | currentCard.isDiscardSelected = false 198 | else 199 | currentCard.isDiscardSelected = not selected 200 | end 201 | end 202 | 203 | refreshInfoBar(self) 204 | 205 | self.confirmButton:keypressed(key) 206 | end 207 | 208 | function DiscardCardState:setSelectingCard(isSelectingCard) 209 | self.isSelectingCard = isSelectingCard 210 | self.confirmButton:setSelect(not isSelectingCard) 211 | end -------------------------------------------------------------------------------- /src/states/EnemyActionState.lua: -------------------------------------------------------------------------------- 1 | EnemyActionState = {} 2 | 3 | function EnemyActionState:enter() 4 | cursor:setVisible(false) 5 | currentEnemy:playCard(player) 6 | GameState.switch(PlayerPlayCardState) 7 | end -------------------------------------------------------------------------------- /src/states/EnemyShowState.lua: -------------------------------------------------------------------------------- 1 | EnemyShowState = {} 2 | 3 | function EnemyShowState:enter() 4 | self.isInfoInside = false 5 | isEnemyShowed = true 6 | enemySpecialCardMoveIn(currentEnemy, function() 7 | self.isInfoInside = true 8 | end) 9 | end 10 | 11 | function EnemyShowState:draw() 12 | setColor(halfCoverColor) 13 | love.graphics.rectangle('fill', 0, 0, screenWidth, screenHeight) 14 | if self.isInfoInside then 15 | setColor(black) 16 | love.graphics.rectangle('fill', 0, screenHeight/2-20, screenWidth, 50) 17 | setColor(white) 18 | love.graphics.printf(currentEnemy.specialCard.action.info, 0, screenHeight/2+10, 19 | screenWidth, 'center') 20 | end 21 | currentEnemy:drawInfo(enemyInfoPos.x, enemyInfoPos.y) 22 | end 23 | 24 | function EnemyShowState:keypressed(key) 25 | if self.isInfoInside then 26 | if key == keys.A then 27 | enemySpecialCardMoveOut(currentEnemy,function() 28 | GameState.pop() 29 | end) 30 | end 31 | end 32 | end -------------------------------------------------------------------------------- /src/states/IdleState.lua: -------------------------------------------------------------------------------- 1 | IdleState = {} -------------------------------------------------------------------------------- /src/states/InitState.lua: -------------------------------------------------------------------------------- 1 | InitState = {} 2 | 3 | function InitState:enter() 4 | isTutorialPlayed = false 5 | isEnemyShowed = false 6 | cursor:setVisible(false) 7 | -- Game Data 8 | actions = reload('src.entities.Actions') 9 | for key, action in pairs(actions.container) do 10 | action.effectIcon = love.graphics.newImage('assets/images/action_effect/'..key..'.png') 11 | action.icon = love.graphics.newImage('assets/images/action_icon/'..key..'.png') 12 | end 13 | 14 | spaces = reload('src.entities.Spaces') 15 | for key, space in pairs(spaces.container) do 16 | space.icon = love.graphics.newImage('assets/images/space_icon/'..key..'.png') 17 | space.effectIcon = love.graphics.newImage('assets/images/space_effect/'..key..'.png') 18 | space.chess = love.graphics.newImage('assets/images/space_chess/'..key..'.png') 19 | end 20 | 21 | decks = reload('src.entities.Decks') 22 | player = reload('src.entities.Player') 23 | enemies = reload('src.entities.Enemies') 24 | currentEnemy = nil 25 | 26 | fillAllDecks() 27 | sortDeck(decks.PublicDeck) 28 | map:init(function() 29 | player:init(function() 30 | self:initEnemy(function() 31 | GameState.switch(EnemyActionState) 32 | end) 33 | end) 34 | end) 35 | end 36 | 37 | function InitState:initEnemy(onComplete) 38 | currentEnemyId = 'banshee' 39 | currentEnemy = enemies.container[currentEnemyId] 40 | currentEnemy:init(onComplete) 41 | end 42 | 43 | function InitState:update(dt) 44 | 45 | end 46 | 47 | function InitState:draw() 48 | 49 | end -------------------------------------------------------------------------------- /src/states/LifeCheckState.lua: -------------------------------------------------------------------------------- 1 | LifeCheckState = {} 2 | 3 | local function returnAllPlayerCards() 4 | for _, card in pairs(player.hand) do 5 | table.add(decks.PlayerDeck.cards, card) 6 | end 7 | table.clean(player.hand) 8 | 9 | for _, card in pairs(decks.PlayerDeck.discardCards) do 10 | table.add(decks.PlayerDeck.cards, card) 11 | end 12 | table.clean(decks.PlayerDeck.discardCards) 13 | 14 | for _, slot in pairs(map.slots) do 15 | if slot.card.deck == decks.PlayerDeck then 16 | table.add(decks.PlayerDeck.cards, slot.card) 17 | slot.card = slot.baseCard 18 | end 19 | end 20 | end 21 | 22 | function LifeCheckState:enter() 23 | cursor:setVisible(false) 24 | if player.life <= 0 then 25 | returnAllPlayerCards() 26 | GameState.switch(LoseState) 27 | elseif currentEnemy.life <= 0 then 28 | returnAllPlayerCards() 29 | GameState.switch(WinState) 30 | else 31 | GameState.switch(PickCardState) 32 | end 33 | end -------------------------------------------------------------------------------- /src/states/LoseState.lua: -------------------------------------------------------------------------------- 1 | LoseState = {} 2 | 3 | local windowWidth = 144 4 | local windowHeight = 64 5 | local buttonWidth, buttonHeight = 128, 18 6 | 7 | function LoseState:init() 8 | self.window = Window() 9 | self.buttons = SelectionGroup() 10 | self.restartButton = Button(i18n.retry, buttonWidth, buttonHeight, 11 | buttonIdleColor, buttonSelectColor, function() 12 | GameState.switch(InitState) 13 | end) 14 | self.exitButton = Button(i18n.exit, buttonWidth, buttonHeight, 15 | buttonIdleColor, buttonSelectColor, function () 16 | GameState.switch(IdleState) 17 | screenManager:view('/') 18 | end) 19 | self.buttons:add(self.restartButton) 20 | self.buttons:add(self.exitButton) 21 | self.buttonStartY = (screenHeight-windowHeight)/2 + 24 22 | end 23 | 24 | function LoseState:enter() 25 | cursor:setVisible(true) 26 | end 27 | 28 | function LoseState:draw() 29 | local windowX = (screenWidth - windowWidth )/2 30 | local windowY = (screenHeight - windowHeight)/2 31 | self.window:draw(windowX, windowY, windowWidth, windowHeight) 32 | 33 | setColor(white) 34 | love.graphics.printf(i18n.fail, windowX, windowY + 2, windowWidth, 'center') 35 | 36 | self.restartButton:draw((screenWidth-buttonWidth)/2, self.buttonStartY) 37 | self.exitButton:draw((screenWidth-buttonWidth)/2, self.buttonStartY+buttonHeight) 38 | 39 | cursor:draw() 40 | end 41 | 42 | function LoseState:keypressed(key) 43 | self.buttons:keypressed(key) 44 | 45 | if key == keys.DPad_up then 46 | self.buttons:Prev() 47 | elseif key == keys.DPad_down then 48 | self.buttons:Next() 49 | end 50 | end -------------------------------------------------------------------------------- /src/states/NextEnemyState.lua: -------------------------------------------------------------------------------- 1 | NextEnemyState = {} 2 | 3 | function NextEnemyState:enter() 4 | isEnemyShowed = false 5 | cursor:setVisible(false) 6 | if currentEnemyId == 'banshee' then 7 | currentEnemyId = 'ghost' 8 | elseif currentEnemyId == 'ghost' then 9 | currentEnemyId = 'troll' 10 | elseif currentEnemyId == 'troll' then 11 | currentEnemyId = 'banshee' 12 | end 13 | currentEnemy = enemies.container[currentEnemyId] 14 | currentEnemy.isInited = false 15 | player.x = 0 16 | player.y = -120 17 | currentEnemy.x = 0 18 | currentEnemy.y = -120 19 | map:init(function() 20 | player:init(function() 21 | currentEnemy:init(function() 22 | GameState.switch(EnemyActionState) 23 | end) 24 | end) 25 | end) 26 | end -------------------------------------------------------------------------------- /src/states/PickCardState.lua: -------------------------------------------------------------------------------- 1 | PickCardState = {} 2 | 3 | function PickCardState:enter() 4 | cursor:setVisible(false) 5 | player:pickCard() 6 | currentEnemy:pickCard() 7 | GameState.switch(EnemyActionState) 8 | end -------------------------------------------------------------------------------- /src/states/PlayerChooseSlotState.lua: -------------------------------------------------------------------------------- 1 | local function checkTargetSlot(playerNeighbours, slot) 2 | local isNeighbour = slot ~= 0 and table.contains(playerNeighbours, slot) 3 | -- playing space only consider if is neighbour 4 | if not player.playingCardAsAction then return isNeighbour end 5 | -- playing action need to consider exception 6 | local action = player.playingCard.action 7 | return isNeighbour and slot ~= action.getExceptSlot(player, currentEnemy) 8 | end 9 | 10 | local function setTargetSlot(playerNeighbours, slot) 11 | if checkTargetSlot(playerNeighbours, slot) then 12 | player.targetSlot = slot 13 | end 14 | end 15 | 16 | PlayerChooseSlotState = {} 17 | 18 | function PlayerChooseSlotState:init() 19 | 20 | end 21 | 22 | function PlayerChooseSlotState:enter() 23 | cursor:setVisible(true) 24 | -- to defaultSlot 25 | if player.playingCardAsAction then 26 | player.targetSlot = player.playingCard.action.defaultTargetSlot(player, currentEnemy) 27 | else 28 | player.targetSlot = player.playingCard.space.defaultTargetSlot(player, currentEnemy) 29 | end 30 | infoBar:setCardInfo(map.slots[player.targetSlot].card) 31 | end 32 | 33 | function PlayerChooseSlotState:draw() 34 | local slot = map.slots[player.targetSlot] 35 | 36 | setColor(green) 37 | love.graphics.draw(imgSlotSelect, mapX + slot.x, mapY + slot.y) 38 | end 39 | 40 | function PlayerChooseSlotState:update(dt) 41 | cursor:moveToMapSlot(player.targetSlot) 42 | end 43 | 44 | function PlayerChooseSlotState:keypressed(key) 45 | if key == keys.B then 46 | GameState.switch(PlayerPlayCardState) 47 | return 48 | end 49 | 50 | local playerNeighbours = map:getNeighbours(player.slot) 51 | table.add(playerNeighbours, player.slot) 52 | local next 53 | if key == keys.DPad_up then 54 | next = map:getNeighbours(player.targetSlot)[1] 55 | if not checkTargetSlot(playerNeighbours, next) then 56 | next = map:getNeighbours(player.targetSlot)[6] 57 | end 58 | if not checkTargetSlot(playerNeighbours, next) then 59 | next = map:getNeighbours(player.targetSlot)[2] 60 | end 61 | setTargetSlot(playerNeighbours, next) 62 | elseif key == keys.DPad_down then 63 | next = map:getNeighbours(player.targetSlot)[4] 64 | if not checkTargetSlot(playerNeighbours, next) then 65 | next = map:getNeighbours(player.targetSlot)[5] 66 | end 67 | if not checkTargetSlot(playerNeighbours, next) then 68 | next = map:getNeighbours(player.targetSlot)[3] 69 | end 70 | setTargetSlot(playerNeighbours, next) 71 | elseif key == keys.DPad_left then 72 | next = map:getNeighbours(player.targetSlot)[6] 73 | if not checkTargetSlot(playerNeighbours, next) then 74 | next = map:getNeighbours(player.targetSlot)[5] 75 | end 76 | setTargetSlot(playerNeighbours, next) 77 | elseif key == keys.DPad_right then 78 | next = map:getNeighbours(player.targetSlot)[2] 79 | if not checkTargetSlot(playerNeighbours, next) then 80 | next = map:getNeighbours(player.targetSlot)[3] 81 | end 82 | setTargetSlot(playerNeighbours, next) 83 | end 84 | 85 | if key == keys.A then 86 | soundPlayCard:play() 87 | GameState.switch(ResolutionState) 88 | return 89 | end 90 | 91 | infoBar:setCardInfo(map.slots[player.targetSlot].card) 92 | end -------------------------------------------------------------------------------- /src/states/PlayerPlayCardState.lua: -------------------------------------------------------------------------------- 1 | local noTips, mapTips, enemyTips = 1,2,3 2 | 3 | local function refreshInfoBarAtNoTips(self) 4 | infoBar:setCardInfo(player.hand[player.currentCardId], player.cardAsAction) 5 | end 6 | 7 | local function enterNoTips(self) 8 | self.tipsStatus = noTips 9 | cursor:setTip(false) 10 | infoBar:setShowFlipInfo(true) 11 | refreshInfoBarAtNoTips(self) 12 | end 13 | 14 | local function enterMapTips(self, isFromBottom) 15 | self.tipsStatus = mapTips 16 | cursor:setTip(true) 17 | self.mapTipSlot = isFromBottom and 5 or 2 18 | infoBar:setShowFlipInfo(false) 19 | infoBar:setCardInfo(map.slots[self.mapTipSlot].card) 20 | end 21 | 22 | local function enterEnemyTips(self) 23 | self.tipsStatus = enemyTips 24 | cursor:setTip(true) 25 | self.enemyTipPos = enemyCardX 26 | infoBar:setShowFlipInfo(false) 27 | infoBar:setCardInfo(currentEnemy.playingCard) 28 | end 29 | 30 | local function noTipsKeyPressed(self, key) 31 | 32 | if key == keys.DPad_right then 33 | player:selectNext() 34 | end 35 | 36 | if key == keys.DPad_left then 37 | player:selectPrev() 38 | end 39 | 40 | if key == keys.DPad_up then 41 | enterMapTips(self, true) 42 | return 43 | end 44 | 45 | if key == keys.Y then 46 | self:flipHandCard(self) 47 | end 48 | 49 | if key == keys.A then 50 | local needChooseSlot = player:playCard(currentEnemy) 51 | if needChooseSlot then 52 | GameState.switch(PlayerChooseSlotState) 53 | return 54 | else 55 | soundPlayCard:play() 56 | GameState.switch(ResolutionState) 57 | return 58 | end 59 | end 60 | 61 | refreshInfoBarAtNoTips(self) 62 | end 63 | 64 | local function mapTipsKeyPressed(self, key) 65 | if key == keys.B or key == keys.A then 66 | enterNoTips(self) 67 | return 68 | end 69 | 70 | local next 71 | local neighbours = map:getNeighbours(self.mapTipSlot) 72 | if key == keys.DPad_up then 73 | next = neighbours[1] 74 | if next == 0 then 75 | enterEnemyTips(self) 76 | return 77 | else 78 | self.mapTipSlot = next 79 | end 80 | elseif key == keys.DPad_down then 81 | next = neighbours[4] 82 | if next == 0 then 83 | enterNoTips(self) 84 | return 85 | else 86 | self.mapTipSlot = next 87 | end 88 | elseif key == keys.DPad_left then 89 | next = neighbours[6] 90 | if next == 0 then 91 | next = neighbours[5] 92 | end 93 | if next ~= 0 then 94 | self.mapTipSlot = next 95 | end 96 | elseif key == keys.DPad_right then 97 | next = neighbours[2] 98 | if next == 0 then 99 | next = neighbours[3] 100 | end 101 | if next ~= 0 then 102 | self.mapTipSlot = next 103 | end 104 | end 105 | 106 | infoBar:setCardInfo(map.slots[self.mapTipSlot].card) 107 | end 108 | 109 | local function enemyTipsKeyPressed(self, key) 110 | if key == keys.B or key == keys.A then 111 | enterNoTips(self) 112 | return 113 | end 114 | 115 | if key == keys.DPad_left then 116 | self.enemyTipPos = enemyCardX 117 | infoBar:setCardInfo(currentEnemy.playingCard) 118 | elseif key == keys.DPad_right then 119 | self.enemyTipPos = specialCardX 120 | infoBar:setCardInfo(currentEnemy.specialCard) 121 | end 122 | 123 | if key == keys.DPad_down then 124 | enterMapTips(self, false) 125 | return 126 | end 127 | 128 | 129 | end 130 | 131 | PlayerPlayCardState = {} 132 | function PlayerPlayCardState:enter() 133 | cursor:setVisible(true) 134 | player.currentCardId = 1 135 | player:updateHandCardPositions() 136 | enterNoTips(self) 137 | if not isTutorialPlayed then 138 | GameState.push(TutorialState) 139 | elseif not isEnemyShowed then 140 | GameState.push(EnemyShowState) 141 | end 142 | end 143 | 144 | function PlayerPlayCardState:resume() 145 | cursor:setVisible(true) 146 | if not isEnemyShowed then 147 | GameState.push(EnemyShowState) 148 | end 149 | end 150 | 151 | function PlayerPlayCardState:update(dt) 152 | if self.tipsStatus == noTips then 153 | cursor:moveToCard(player.hand[player.currentCardId]) 154 | elseif self.tipsStatus == mapTips then 155 | cursor:moveToMapSlot(self.mapTipSlot) 156 | else 157 | cursor:moveTo(self.enemyTipPos + cardWidth/2, 158 | enemyCardY + cardHeight - 8) 159 | end 160 | end 161 | 162 | function PlayerPlayCardState:keypressed(key) 163 | if self.tipsStatus == noTips then 164 | noTipsKeyPressed(self, key) 165 | elseif self.tipsStatus == mapTips then 166 | mapTipsKeyPressed(self, key) 167 | else 168 | enemyTipsKeyPressed(self, key) 169 | end 170 | end 171 | 172 | function PlayerPlayCardState:flipHandCard() 173 | for id, card in pairs(player.hand) do 174 | if id == player.currentCardId then 175 | card:flip(self, refreshInfoBarAtNoTips) 176 | else 177 | card:flip() 178 | end 179 | end 180 | end -------------------------------------------------------------------------------- /src/states/RewardState.lua: -------------------------------------------------------------------------------- 1 | RewardState = {} 2 | 3 | local function drawCard(id, card) 4 | card:draw() 5 | -- draw selection 6 | if card.isRewardSelected then 7 | setColor(white) 8 | love.graphics.draw(imgRewardSelect, card.x + cardWidth - 19, card.y + 2) 9 | end 10 | end 11 | 12 | local function isRewardFull() 13 | local rewardCount = 0 14 | for _, slot in pairs(map.slots) do 15 | if slot.baseCard.isRewardSelected then 16 | rewardCount = rewardCount + 1 17 | end 18 | end 19 | return rewardCount >= player.rewardSize 20 | end 21 | 22 | local function refreshInfoBar(self) 23 | infoBar:setCardInfo(map.slots[self.currentCardId].baseCard) 24 | end 25 | 26 | function RewardState:init() 27 | self.confirmButton = Button(i18n.confirm, buttonWidth, buttonHeight, 28 | buttonIdleColor, buttonSelectColor, function() 29 | self:confirmSelection() 30 | if #decks.PlayerDeck.cards > decks.PlayerDeck.size then 31 | GameState.switch(DiscardCardState) 32 | else 33 | GameState.switch(NextEnemyState) 34 | end 35 | end) 36 | end 37 | 38 | function RewardState:enter() 39 | cursor:setVisible(true) 40 | self.currentCardId = 1 41 | self.isSelectingCard = true 42 | self.confirmButton:setSelect(false) 43 | 44 | for _, slot in pairs(map.slots) do 45 | slot.baseCard.isRewardSelected = false 46 | end 47 | 48 | infoBar:setShowFlipInfo(true) 49 | refreshInfoBar(self) 50 | end 51 | 52 | function RewardState:draw() 53 | setColor(coverColor) 54 | love.graphics.rectangle('fill', 0, 0, screenWidth, screenHeight) 55 | 56 | setColor(white) 57 | love.graphics.printf(i18n.chooseReward, 0, 64, screenWidth, 'center') 58 | 59 | setColor(white) 60 | self:drawRewards(32, 96, screenWidth-64) 61 | 62 | self.confirmButton:draw((screenWidth-buttonWidth)/2, 96 + 64 + 16) 63 | 64 | infoBar:draw(infoBarX, infoBarY) 65 | 66 | cursor:draw() 67 | end 68 | 69 | function RewardState:drawRewards(x, y, width) 70 | local xInterval = (width - cardWidth)/6 71 | for id, slot in pairs(map.slots) do 72 | if id ~= self.currentCardId then 73 | drawCard(id, slot.baseCard) 74 | end 75 | end 76 | if self.currentCardId ~= 0 then 77 | drawCard(self.currentCardId, map.slots[self.currentCardId].baseCard) 78 | end 79 | end 80 | 81 | function RewardState:update(dt) 82 | if self.isSelectingCard and self.currentCardId ~= 0 then 83 | cursor:moveToCard(map.slots[self.currentCardId].baseCard) 84 | end 85 | end 86 | 87 | function RewardState:confirmSelection() 88 | for _, slot in pairs(map.slots) do 89 | if slot.baseCard.isRewardSelected then 90 | -- selected card into player deck 91 | table.add(decks.PlayerDeck.cards, slot.baseCard) 92 | else 93 | -- others into public discard pile 94 | table.add(decks.PublicDeck.discardCards, slot.baseCard) 95 | end 96 | slot.baseCard.isRewardSelected = nil 97 | slot.baseCard = nil 98 | end 99 | end 100 | 101 | function RewardState:selectRight() 102 | local prevId = self.currentCardId 103 | local id = self.currentCardId + 1 104 | if id > #map.slots then 105 | id = 1 106 | end 107 | self.currentCardId = id 108 | 109 | map.slots[prevId].baseCard:moveTo(rewardCardX+(prevId-1)*rewardCardInterval, rewardCardY, 0.1) 110 | map.slots[self.currentCardId].baseCard:moveTo( 111 | rewardCardX+(id-1)*rewardCardInterval, rewardCardY-4, 0.1) 112 | end 113 | 114 | function RewardState:selectLeft() 115 | local prevId = self.currentCardId 116 | local id = self.currentCardId - 1 117 | if id <1 then 118 | id = #map.slots 119 | end 120 | self.currentCardId = id 121 | 122 | map.slots[prevId].baseCard:moveTo(rewardCardX+(prevId-1)*rewardCardInterval, rewardCardY, 0.1) 123 | map.slots[self.currentCardId].baseCard:moveTo( 124 | rewardCardX+(id-1)*rewardCardInterval, rewardCardY-4, 0.1) 125 | end 126 | 127 | function RewardState:keypressed(key) 128 | if key == keys.Y then 129 | for id, slot in pairs(map.slots) do 130 | if id == self.currentCardId then 131 | slot.baseCard:flip(self, refreshInfoBar) 132 | else 133 | slot.baseCard:flip() 134 | end 135 | end 136 | end 137 | 138 | if key == keys.DPad_left then 139 | self.isSelectingCard = true 140 | self.confirmButton:setSelect(false) 141 | self:selectLeft() 142 | end 143 | 144 | if key == keys.DPad_right then 145 | self.isSelectingCard = true 146 | self.confirmButton:setSelect(false) 147 | self:selectRight() 148 | end 149 | 150 | if key == keys.DPad_down then 151 | self.isSelectingCard = false 152 | self.confirmButton:setSelect(true) 153 | end 154 | 155 | if key == keys.DPad_up then 156 | self.isSelectingCard = true 157 | self.confirmButton:setSelect(false) 158 | end 159 | 160 | if key == keys.A and self.isSelectingCard then 161 | local currentCard = map.slots[self.currentCardId].baseCard 162 | local selected = currentCard.isRewardSelected 163 | if isRewardFull() then 164 | currentCard.isRewardSelected = false 165 | else 166 | currentCard.isRewardSelected = not selected 167 | end 168 | end 169 | 170 | refreshInfoBar(self) 171 | 172 | self.confirmButton:keypressed(key) 173 | end -------------------------------------------------------------------------------- /src/states/SystemMenuState.lua: -------------------------------------------------------------------------------- 1 | SystemMenuState = {} 2 | 3 | local windowWidth = 144 4 | local windowHeight = 64+18 5 | local buttonWidth, buttonHeight = 128, 18 6 | 7 | function SystemMenuState:init() 8 | print('system init') 9 | self.window = Window() 10 | self.buttons = SelectionGroup() 11 | self.returnButton = Button(i18n.back, buttonWidth, buttonHeight, 12 | buttonIdleColor, buttonSelectColor, function() 13 | GameState.pop() 14 | end) 15 | self.restartButton = Button(i18n.retry, buttonWidth, buttonHeight, 16 | buttonIdleColor, buttonSelectColor, function() 17 | GameState.pop() 18 | GameState.switch(InitState) 19 | end) 20 | self.exitButton = Button(i18n.exit, buttonWidth, buttonHeight, 21 | buttonIdleColor, buttonSelectColor, function () 22 | GameState.switch(IdleState) 23 | screenManager:view('/') 24 | end) 25 | self.buttons:add(self.returnButton) 26 | self.buttons:add(self.restartButton) 27 | self.buttons:add(self.exitButton) 28 | self.buttonStartY = (screenHeight-windowHeight)/2 + 22 29 | end 30 | 31 | function SystemMenuState:enter() 32 | cursor:setVisible(true) 33 | end 34 | 35 | function SystemMenuState:draw() 36 | setColor(coverColor) 37 | love.graphics.rectangle('fill', 0, 0, screenWidth, screenHeight) 38 | 39 | 40 | local windowX = (screenWidth - windowWidth )/2 41 | local windowY = (screenHeight - windowHeight)/2 42 | self.window:draw(windowX, windowY, windowWidth, windowHeight) 43 | 44 | setColor(white) 45 | love.graphics.printf(i18n.system, windowX, windowY + 4, windowWidth, 'center') 46 | 47 | self.returnButton:draw((screenWidth-buttonWidth)/2, self.buttonStartY) 48 | self.restartButton:draw((screenWidth-buttonWidth)/2, self.buttonStartY+buttonHeight) 49 | self.exitButton:draw((screenWidth-buttonWidth)/2, self.buttonStartY+buttonHeight*2) 50 | 51 | cursor:draw() 52 | end 53 | 54 | 55 | 56 | function SystemMenuState:keypressed(key) 57 | self.buttons:keypressed(key) 58 | 59 | if key == keys.Menu then 60 | GameState.pop() 61 | elseif key == keys.DPad_up then 62 | self.buttons:Prev() 63 | elseif key == keys.DPad_down then 64 | self.buttons:Next() 65 | end 66 | end -------------------------------------------------------------------------------- /src/states/TutorialState.lua: -------------------------------------------------------------------------------- 1 | TutorialState = {} 2 | 3 | function TutorialState:enter() 4 | cursor:setVisible(false) 5 | isTutorialPlayed = true 6 | self.step = 1 7 | end 8 | 9 | function TutorialState:draw() 10 | setColor(white) 11 | if self.step == 1 then 12 | love.graphics.draw(imgTutorial1, 0, 0) 13 | elseif self.step == 2 then 14 | love.graphics.draw(imgTutorial2, 0, 0) 15 | end 16 | end 17 | 18 | function TutorialState:keypressed(key) 19 | if key == keys.A then 20 | self.step = self.step + 1 21 | if self.step == 3 then 22 | GameState.pop() 23 | end 24 | end 25 | end -------------------------------------------------------------------------------- /src/states/UpkeepState.lua: -------------------------------------------------------------------------------- 1 | UpkeepState = {} 2 | 3 | function UpkeepState:enter() 4 | cursor:setVisible(false) 5 | -- all spaces runs onUpkeep 6 | for slotId, slot in pairs(map.slots) do 7 | local resident = map:getSlotOccupied(slotId) 8 | local space = slot.card.space 9 | if resident ~= nil and space.onUpkeep ~= nil then 10 | space.onUpkeep(resident) 11 | end 12 | end 13 | 14 | GameState.switch(LifeCheckState) 15 | end -------------------------------------------------------------------------------- /src/states/WinState.lua: -------------------------------------------------------------------------------- 1 | WinState = {} 2 | 3 | local windowWidth = 144 4 | local windowHeight = 48 + 14 5 | 6 | local function onComplete() 7 | for id, slot in pairs(map.slots) do 8 | timer.after(id*0.2, function() 9 | local card = slot.baseCard 10 | card:flip() 11 | end) 12 | end 13 | GameState.switch(RewardState) 14 | end 15 | 16 | function WinState:init() 17 | self.window = Window() 18 | self.buttons = SelectionGroup() 19 | self.nextButton = Button(i18n.next, buttonWidth, buttonHeight, 20 | buttonIdleColor, buttonSelectColor, function() 21 | self.isDrawWindow = false 22 | for id, slot in pairs(map.slots) do 23 | timer.after(id*0.2, function() 24 | local card = slot.baseCard 25 | local callback = (id == #map.slots) and onComplete or nil 26 | card:moveTo(rewardCardX+(id-1)*rewardCardInterval, rewardCardY, 0.4, callback) 27 | end) 28 | end 29 | end) 30 | self.buttons:add(self.nextButton) 31 | self.windowX = (screenWidth - windowWidth )/2 32 | self.windowY = (screenHeight - windowHeight)/2 33 | self.buttonStartY = (screenHeight-windowHeight)/2 + 12 +14 + 8 34 | end 35 | 36 | function WinState:enter() 37 | cursor:setVisible(true) 38 | player:upgrade() 39 | self.isDrawWindow = true 40 | end 41 | 42 | function WinState:draw() 43 | if self.isDrawWindow then 44 | self.window:draw( self.windowX, self.windowY, windowWidth, windowHeight) 45 | setColor(white) 46 | love.graphics.printf(i18n.win, self.windowX, self.windowY + 4, windowWidth, 'center') 47 | love.graphics.printf(i18n.update, self.windowX, self.windowY + 4+14, windowWidth, 'center') 48 | self.nextButton:draw((screenWidth-buttonWidth)/2, self.buttonStartY) 49 | cursor:draw() 50 | end 51 | end 52 | 53 | function WinState:keypressed(key) 54 | self.buttons:keypressed(key) 55 | end -------------------------------------------------------------------------------- /src/ui/Button.lua: -------------------------------------------------------------------------------- 1 | local Button = class {} 2 | 3 | function Button:init(text, width, height, idleBGColor, selectBGColor, confirmCallback) 4 | self.text = text 5 | self.width = width 6 | self.height = height 7 | self.idleBGColor = idleBGColor 8 | self.selectBGColor = selectBGColor 9 | self.confirmCallback = confirmCallback 10 | 11 | self.textWidth = love.graphics.getFont():getWidth(text) 12 | end 13 | 14 | function Button:setSelect(isSelect) 15 | self.isSelect = isSelect 16 | end 17 | 18 | function Button:draw(x, y) 19 | -- move cursor 20 | if self.isSelect then 21 | cursor:moveTo(x+self.width/2+12, y+self.height-4) 22 | end 23 | 24 | -- idle background 25 | if self.idleBGColor and not self.isSelect then 26 | setColor(self.idleBGColor) 27 | love.graphics.rectangle('fill', x,y, self.width,self.height) 28 | end 29 | 30 | -- selected background 31 | if self.selectBGColor and self.isSelect then 32 | setColor(self.selectBGColor) 33 | love.graphics.rectangle('fill', x,y, self.width,self.height) 34 | end 35 | 36 | --text 37 | setColor(white) 38 | love.graphics.printf(self.text, x, y+2, self.width, 'center') 39 | end 40 | 41 | function Button:keypressed(key) 42 | if key == keys.A and self.isSelect then 43 | self.confirmCallback() 44 | end 45 | end 46 | 47 | return Button -------------------------------------------------------------------------------- /src/ui/Cursor.lua: -------------------------------------------------------------------------------- 1 | local Cursor = class {} 2 | 3 | local defaultX = -60 4 | local defaultY = -60 5 | 6 | function Cursor:init(x, y) 7 | self.x = x or defaultX 8 | self.y = y or defaultY 9 | self.isTweening = false 10 | self.visible = true 11 | self.isTip = false 12 | end 13 | 14 | function Cursor:setVisible(visible) 15 | self.visible = visible 16 | end 17 | 18 | function Cursor:setTip(isTip) 19 | self.isTip = isTip 20 | end 21 | 22 | function Cursor:moveTo(x, y) 23 | if not self.isTweening and 24 | (not floatequal(self.x, x, 1) or not floatequal(self.y, y, 1)) then 25 | self.isTweening = true 26 | timer.tween(0.1, self, {x = x, y = y}, 'linear', function() 27 | self.isTweening = false 28 | end) 29 | end 30 | end 31 | 32 | function Cursor:moveToCard(card) 33 | self:moveTo(card.x+cardWidth/2, card.y+cardHeight-4) 34 | end 35 | 36 | function Cursor:moveToMapSlot(slotId) 37 | local slot = map.slots[slotId] 38 | self:moveTo(mapX + slot.x + 48, mapY + slot.y + 26) 39 | end 40 | 41 | function Cursor:onMoveDone() 42 | self.isTweening = false 43 | end 44 | 45 | function Cursor:draw() 46 | if not self.visible then return end 47 | setColor(white) 48 | local img = self.isTip and imgTipCursor or imgCursor 49 | love.graphics.draw(img, self.x, self.y) 50 | end 51 | 52 | return Cursor -------------------------------------------------------------------------------- /src/ui/InfoBar.lua: -------------------------------------------------------------------------------- 1 | local InfoBar = class {} 2 | 3 | function InfoBar:init(width) 4 | self.width = width 5 | self.isShowFlipInfo = false 6 | end 7 | 8 | function InfoBar:setText(text) 9 | self.text = text 10 | end 11 | 12 | function InfoBar:setCardInfo(card) 13 | if card.isShowAction then 14 | self.text = card.action.info 15 | else 16 | self.text = card.space.info 17 | end 18 | end 19 | 20 | function InfoBar:setShowFlipInfo(isShowFlipInfo) 21 | self.isShowFlipInfo = isShowFlipInfo 22 | end 23 | 24 | function InfoBar:draw(x, y) 25 | setColor(infoBarBgColor) 26 | love.graphics.rectangle('fill', x, y, self.width, 20) 27 | 28 | -- button y flip card 29 | if self.isShowFlipInfo then 30 | setColor(white) 31 | love.graphics.draw(imgButtonY, x+246, y+4) 32 | setColor(infoBarFlipInfoColor) 33 | love.graphics.print(i18n.flipCard, x+258, y+4) 34 | end 35 | 36 | 37 | if self.text == nil then return end 38 | setColor(infoBarTextColor) 39 | love.graphics.print(self.text, x+4, y+4) 40 | end 41 | 42 | return InfoBar -------------------------------------------------------------------------------- /src/ui/SelectionGroup.lua: -------------------------------------------------------------------------------- 1 | SelectionGroup = class{ 2 | init = function(self) 3 | self.selected = 1 4 | self.elements = {} 5 | end; 6 | } 7 | 8 | function SelectionGroup:add(element) 9 | table.add(self.elements, element) 10 | if #self.elements == 1 then 11 | element:setSelect(true) 12 | end 13 | end 14 | 15 | function SelectionGroup:Next() 16 | local length = #self.elements 17 | local selected = self.selected+1 18 | if selected > length then 19 | selected = 1 20 | end 21 | self.selected = selected 22 | 23 | self:RefreshSelect() 24 | end 25 | 26 | function SelectionGroup:Prev() 27 | local length = #self.elements 28 | local selected = self.selected-1 29 | if selected < 1 then 30 | selected = length 31 | end 32 | self.selected = selected 33 | 34 | self:RefreshSelect() 35 | end 36 | 37 | function SelectionGroup:RefreshSelect() 38 | for _,element in pairs(self.elements) do 39 | element:setSelect(false) 40 | end 41 | self.elements[self.selected]:setSelect(true) 42 | end 43 | 44 | function SelectionGroup:keypressed(key) 45 | for _,element in pairs(self.elements) do 46 | element:keypressed(key) 47 | end 48 | end -------------------------------------------------------------------------------- /src/ui/Window.lua: -------------------------------------------------------------------------------- 1 | local Window = class {} 2 | 3 | function Window:draw(x, y, w, h) 4 | setColor(windowColor) 5 | love.graphics.rectangle('fill', x, y, w, h) 6 | end 7 | 8 | return Window -------------------------------------------------------------------------------- /src/utils.lua: -------------------------------------------------------------------------------- 1 | function drawText(text, x, y, color) 2 | color = color or white 3 | setColor(color) 4 | love.graphics.print(text, x, y+3) 5 | end 6 | 7 | function setColor(color) 8 | color[4] = color[4] or 1 9 | love.graphics.setColor({color[1]/255, color[2]/255, color[3]/255, color[4]}) 10 | end 11 | 12 | function drawFPS() 13 | setColor(white) 14 | love.graphics.print(tostring(love.timer.getFPS( )), 8, 8) 15 | end 16 | 17 | function drawLogs() 18 | setColor(white) 19 | for _,log in pairs(logs) do 20 | love.graphics.print(log, 8, 16) 21 | end 22 | 23 | end 24 | 25 | --Create card 26 | function createRandomCard(scoreRange, x, y) 27 | --random action and space 28 | local action, space, totalScore 29 | repeat 30 | action = randomElement(actions.container) 31 | space = randomElement(spaces.container) 32 | totalScore = action.score + space.score 33 | until totalScore >= scoreRange[1] and totalScore <= scoreRange[2] 34 | 35 | local card = Card(action, space, x, y) 36 | return card 37 | end 38 | 39 | function fillAllDecks() 40 | fillDeck(decks.PublicDeck) 41 | fillDeck(decks.PlayerDeck) 42 | fillDeck(decks.BansheeDeck) 43 | fillDeck(decks.GhostDeck) 44 | fillDeck(decks.TrollDeck) 45 | end 46 | 47 | function fillDeck(deck) 48 | for i = 1, deck.size do 49 | if i>#deck.cards then 50 | deck.cards[i] = createRandomCard(deck.scoreRange, deck.x, deck.y) 51 | else 52 | deck.cards[i].x = deck.x 53 | deck.cards[i].y = deck.y 54 | end 55 | deck.cards[i].deck = deck 56 | end 57 | -- shuffle the deck 58 | shuffle(deck.cards) 59 | end 60 | 61 | 62 | function reload(packageName) 63 | package.loaded[packageName] = nil 64 | return require(packageName) 65 | end 66 | 67 | function random(m, n) 68 | --love.math.setRandomSeed(os.time()) 69 | return love.math.random(m,n) 70 | end 71 | 72 | function random(n) 73 | --love.math.setRandomSeed(os.clock()) 74 | return love.math.random(n) 75 | end 76 | 77 | -- random element in table, no mater if the table is integer sequence 78 | function randomElement(myTable, except) 79 | -- iterate over whole table to get all keys 80 | local keySet = {} 81 | for k in pairs(myTable) do 82 | table.insert(keySet, k) 83 | end 84 | -- now you can reliably return a random key 85 | local result 86 | repeat 87 | result = myTable[keySet[random(#keySet)]] 88 | until result ~= except 89 | return result 90 | end 91 | 92 | function table.contains(tab, val) 93 | for _, value in pairs(tab) do 94 | if value == val then 95 | return true 96 | end 97 | end 98 | 99 | return false 100 | end 101 | 102 | function table.find(tab, val) 103 | for i, value in pairs(tab) do 104 | if value == val then 105 | return i 106 | end 107 | end 108 | 109 | return 0 110 | end 111 | 112 | function table.clean(t) 113 | for k in pairs (t) do 114 | t [k] = nil 115 | end 116 | end 117 | 118 | function table.add(t, element) 119 | t[#t+1] = element 120 | end 121 | 122 | function discardCard(card) 123 | local originDeck = card.deck 124 | if originDeck ~= nil then originDeck:discardCard(card) end 125 | end 126 | 127 | function getLifePercent(char) 128 | return char.life/char.initLife 129 | end 130 | 131 | -- choose the first heal card from hand 132 | function chooseHandCardHeal(char) 133 | for id, card in pairs(char.hand) do 134 | if table.contains(card.action.type, 'heal') then 135 | return id 136 | end 137 | end 138 | return 0 139 | end 140 | 141 | function chooseHandCardAttack(char) 142 | for id, card in pairs(char.hand) do 143 | if table.contains(card.action.type, 'attack') then 144 | return id 145 | end 146 | end 147 | return 0 148 | end 149 | 150 | function chooseHandCardMove(char) 151 | for id, card in pairs(char.hand) do 152 | if table.contains(card.action.type, 'move') then 153 | return id 154 | end 155 | end 156 | return 0 157 | end 158 | 159 | function chooseHandCardDrop(char) 160 | for id, card in pairs(char.hand) do 161 | if table.contains(card.action.type, 'drop') then 162 | return id 163 | end 164 | end 165 | return 0 166 | end 167 | 168 | function chooseHandCardSpaceRecover(char) 169 | for id, card in pairs(char.hand) do 170 | if table.contains(card.action.type, 'spaceRecover') then 171 | return id 172 | end 173 | end 174 | return 0 175 | end 176 | 177 | function dropFirstHandCard(char) 178 | if #char.hand == 0 then return end 179 | ---- if currently playing card, that card should not be droped 180 | if char.currentCardId ~= 0 and #char.hand == 1 then return end 181 | 182 | local dropId = 1 183 | if char.currentCardId == 1 then dropId = 2 end 184 | 185 | discardCard(char.hand[dropId]) 186 | table.remove(char.hand, dropId) 187 | end 188 | 189 | function sortDeck(deck) 190 | table.sort(deck.cards, function(e1, e2) 191 | return e1.action.score+e1.space.score < e2.action.score+e2.space.score 192 | end) 193 | end 194 | 195 | -- moveMode: 'instant', 'fly', 'hit', 'jump', 'arrive' 196 | function charMove(char, newSlot, moveMode, onComplete) 197 | char.slot = newSlot 198 | 199 | local newX = map.slots[newSlot].x 200 | + math.floor(mapSlotWidth/2) - char.spriteWidth/2 201 | local newY = map.slots[newSlot].y 202 | + math.floor(mapSlotHeight/2) - char.spriteHeight 203 | if moveMode == 'instant' then 204 | char.x = newX 205 | char.y = newY 206 | if onComplete ~= nil then onComplete() end 207 | elseif moveMode == 'fly' then 208 | timer.tween(0.5, char, {x=newX, y=newY}, 'out-quint', onComplete) 209 | elseif moveMode == 'hit' then 210 | timer.tween(0.25, char, {x=newX, y=newY}, 'out-quint', onComplete) 211 | elseif moveMode == 'jump' then 212 | local time = 0.8 213 | timer.tween(time, char, {x=newX}, 'out-in-cubic', onComplete) 214 | local yTop = (char.y + newY)/2 - 80 215 | timer.tween(time/2, char, {y=yTop}, 'out-cubic', function() 216 | timer.tween(time/2, char, {y=newY}, 'in-cubic') 217 | end) 218 | elseif moveMode == 'arrive' then 219 | char.x = newX 220 | char.y = newY - 120 221 | timer.tween(0.8, char, {x=newX, y=newY}, 'out-quint', onComplete) 222 | end 223 | end 224 | 225 | function drawTip(char, tip) 226 | if tip.y ~= tip.baseY then 227 | setColor(white) 228 | love.graphics.draw(tip.img, mapX + char.x + tip.x, 229 | mapY + char.y + tip.y) 230 | love.graphics.setFont(fontNum) 231 | love.graphics.print((tip.value>0 and '+' or '')..tostring(tip.value), 232 | mapX + char.x + tip.x + 6, mapY + char.y + tip.y + 4) 233 | love.graphics.setFont(fontCN) 234 | end 235 | end 236 | 237 | function changeLife(char, value) 238 | char.life = char.life + value 239 | if value > 0 then 240 | -- show heal tip 241 | char.healTip.value = value 242 | timer.tween(1, char.healTip, {y = char.healTip.baseY - 16}, 'out-cubic', 243 | function() char.healTip.y = char.healTip.baseY end) 244 | elseif value < 0 then 245 | -- shake of damage 246 | char.isShaking = true 247 | timer.after(0.4, function() char.isShaking = false end) 248 | -- show damage tip 249 | char.damageTip.value = value 250 | timer.tween(1, char.damageTip, {y = char.damageTip.baseY - 16}, 'out-cubic', 251 | function() char.damageTip.y = char.damageTip.baseY end) 252 | end 253 | end 254 | 255 | function isPlayingDefence(char) 256 | return char.playingCardAsAction and 257 | table.contains(char.playingCard.action.type, 'defence') 258 | end 259 | 260 | function shallowcopy(orig) 261 | local orig_type = type(orig) 262 | local copy 263 | if orig_type == 'table' then 264 | copy = {} 265 | for orig_key, orig_value in pairs(orig) do 266 | copy[orig_key] = orig_value 267 | end 268 | else -- number, string, boolean, etc 269 | copy = orig 270 | end 271 | return copy 272 | end 273 | 274 | function deepcopy(orig) 275 | local orig_type = type(orig) 276 | local copy 277 | if orig_type == 'table' then 278 | copy = {} 279 | for orig_key, orig_value in next, orig, nil do 280 | copy[deepcopy(orig_key)] = deepcopy(orig_value) 281 | end 282 | setmetatable(copy, deepcopy(getmetatable(orig))) 283 | else -- number, string, boolean, etc 284 | copy = orig 285 | end 286 | return copy 287 | end 288 | 289 | function floatequal(left,right,precision) 290 | local diff = math.abs(left-right) 291 | return diff < precision 292 | end 293 | 294 | function shuffle(list) 295 | for i = #list, 2, -1 do 296 | local j = random(i) 297 | list[i], list[j] = list[j], list[i] 298 | end 299 | end -------------------------------------------------------------------------------- /test/mock/mockPeachy.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | function M:new(...) 4 | return {play = function(...)end, 5 | onLoop = function(...)end, 6 | stop = function(...)end, 7 | update = function(...)end} 8 | end 9 | 10 | return M -------------------------------------------------------------------------------- /test/mock/mocklove.lua: -------------------------------------------------------------------------------- 1 | -- mocklove.lua 2 | -- No comment. 3 | -- 4 | -- Author: Brian L Price 5 | -- 6 | -- Created: 27/06/2013 7 | -- Copyright: (c) Brian L Price 2013 8 | -- Licence: GPLv3 9 | -- 10 | -- This file is part of DSRL (Dark Stars Rogue L(ua, ove, ike)). 11 | -- 12 | local table = table 13 | 14 | local M = {} 15 | local _callstack = {} 16 | M['_callstack'] = _callstack 17 | 18 | local function decode_args(...) 19 | local r = {} 20 | for _, e in ipairs({...}) do 21 | table.insert(r, e) 22 | end 23 | table.insert(_callstack, r) 24 | end 25 | 26 | function M._clear_callstack() 27 | _callstack = {} 28 | M['_callstack'] = _callstack 29 | end 30 | 31 | local mg = {} 32 | M['graphics'] = mg 33 | 34 | function mg.setPointSize(...) 35 | decode_args(mg, 'setPointSize', ...) 36 | end 37 | 38 | function mg.setPointStyle(...) 39 | decode_args(mg, 'setPointStyle', ...) 40 | end 41 | 42 | function mg.setColor(...) 43 | decode_args(mg, 'setColor', ...) 44 | end 45 | 46 | function mg.point(...) 47 | decode_args(mg, 'point', ...) 48 | end 49 | 50 | function mg.rectangle(...) 51 | decode_args(mg, 'rectangle', ...) 52 | end 53 | 54 | function mg.setColorMode(...) 55 | decode_args(mg, 'setColorMode', ...) 56 | end 57 | 58 | function mg.setLineWidth(...) 59 | decode_args(mg, 'setLineWidth', ...) 60 | end 61 | 62 | function mg.print(...) 63 | decode_args(mg, 'print', ...) 64 | end 65 | 66 | function mg.setFont(...) 67 | decode_args(mg, 'setFont', ...) 68 | end 69 | 70 | local mgf = {} 71 | function mg.newFont(...) 72 | decode_args(mg, 'newFont', ...) 73 | return mgf 74 | end 75 | 76 | function mgf.getWidth(...) 77 | decode_args(mgf, 'getWidth', ...) 78 | return 32 79 | end 80 | 81 | function mgf.getHeight() 82 | decode_args(mgf, 'getHeight') 83 | return 32 84 | end 85 | 86 | function mg.draw(...) 87 | decode_args(mg, 'draw', ...) 88 | end 89 | 90 | local mgi = {} 91 | function mg.newImage(...) 92 | decode_args(mg, 'newImage', ...) 93 | return mgi 94 | end 95 | 96 | function mgi.getWidth() 97 | decode_args(mgi, 'getWidth') 98 | return 1600 99 | end 100 | 101 | function mgi.getHeight() 102 | decode_args(mgi, 'getHeight') 103 | return 900 104 | end 105 | 106 | local me = {} 107 | M['event'] = me 108 | function me.push(...) 109 | decode_args(me, 'push', ...) 110 | end 111 | 112 | local mm = {} 113 | M['math'] = mm 114 | function mm.randomseed(...) 115 | --math.randomseed(...) 116 | end 117 | 118 | function mm.random(n,m) 119 | local result = math.random(n,m) 120 | return result 121 | end 122 | 123 | function mm.random(m) 124 | local result = math.random(m) 125 | return result 126 | end 127 | 128 | return M 129 | -------------------------------------------------------------------------------- /test/mock/mocktimer.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | function M.tween(time, self, subject, method, after, ...) 4 | if after ~= nil then 5 | after() 6 | end 7 | end 8 | 9 | function M.after(...) 10 | 11 | end 12 | 13 | return M -------------------------------------------------------------------------------- /test/tests.lua: -------------------------------------------------------------------------------- 1 | luaunit = require('luaunit') 2 | 3 | -- Add parent path to search list 4 | package.path = package.path .. ";..\\?.lua;" 5 | 6 | class = require 'lib.hump.class' 7 | love = require 'mock.mocklove' 8 | timer = require('mock.mocktimer') 9 | peachy = require('mock.mockPeachy') 10 | 11 | require('src.utils') 12 | require('src.states.ResolutionState') 13 | require('src.states.UpkeepState') 14 | require('src.ui.SelectionGroup') 15 | 16 | i18n = require('assets.i18n.en') 17 | 18 | require('src.const') 19 | require('src.entities.Card') 20 | actions = require('src.entities.Actions') 21 | decks = require('src.entities.Decks') 22 | spaces = require('src.entities.Spaces') 23 | map = require('src.entities.Map') 24 | player = require('src.entities.Player') 25 | enemies = require('src.entities.Enemies') 26 | require('src.entities.Card') 27 | 28 | require('testsHumpClass') 29 | require('testsOfUtils') 30 | require('testsFillAllDecks') 31 | require('testsPickCards') 32 | require('testsActions') 33 | require('testsMap') 34 | require('testsAttackAndDefence') 35 | require('testsSpaces') 36 | require('testsPlaySpace') 37 | require('testsDiscardCardState') 38 | require('testsAIChooseCards') 39 | require('testsUpkeepState') 40 | require('testsBanshee') 41 | require('testsGhost') 42 | 43 | 44 | os.exit( luaunit.LuaUnit.run() ) -------------------------------------------------------------------------------- /test/testsAIChooseCards.lua: -------------------------------------------------------------------------------- 1 | testsAIChooseCards = {} 2 | 3 | function testsAIChooseCards:setUp() 4 | self.me = {} 5 | self.me.life = 2 6 | self.me.initLife = 10 7 | self.me.hand = { 8 | {action=actions.container.attack1}, 9 | {action=actions.container.heal1}, 10 | {action=actions.container.defence1}, 11 | {action=actions.container.move}, 12 | {action=actions.container.drop1}} 13 | self.me.slot = 1 14 | 15 | self.opponent = {} 16 | self.opponent.slot = 2 17 | self.opponent.hand = {{action = actions.container.attack1},{action = actions.container.attack1}, 18 | {action = actions.container.attack1}} 19 | currentEnemy = self.opponent 20 | end 21 | 22 | function testsAIChooseCards:teatDown() 23 | currentEnemy = nil 24 | end 25 | 26 | function testsAIChooseCards:testLifeDanger_Heal() 27 | luaunit.assertEquals(baseAIChooseCard(self.me, self.opponent), 2) 28 | end 29 | 30 | function testsAIChooseCards:testPlayerHandTooMuch_Drop() 31 | self.me.life = 10 32 | table.add(self.opponent.hand, {action = actions.container.attack1}) 33 | luaunit.assertEquals(baseAIChooseCard(self.me, self.opponent), 5) 34 | end 35 | 36 | function testsAIChooseCards:testPlayerNearby_Attack() 37 | self.me.life = 10 38 | local cardId, targetSlot = baseAIChooseCard(self.me, self.opponent) 39 | luaunit.assertEquals(cardId, 1) 40 | luaunit.assertEquals(targetSlot, 2) 41 | end 42 | 43 | function testsAIChooseCards:testPlayerToFar_Move() 44 | self.me.life = 10 45 | self.opponent.slot = 3 46 | local cardId, targetSlot = baseAIChooseCard(self.me, self.opponent) 47 | luaunit.assertEquals(cardId, 4) 48 | luaunit.assertEquals(targetSlot, 2) 49 | end 50 | 51 | function testsAIChooseCards:testSpaceTooBad_Move() 52 | self.me.life = 10 53 | table.remove(self.me.hand, 1) 54 | map.slots[1].card = {space = {benefit = 1}} 55 | map.slots[2].card = {space = {benefit = 4}} 56 | map.slots[6].card = {space = {benefit = 2}} 57 | map.slots[7].card = {space = {benefit = 1}} 58 | local cardId, targetSlot = baseAIChooseCard(self.me, self.opponent) 59 | luaunit.assertEquals(cardId, 3) 60 | luaunit.assertEquals(targetSlot, 6) 61 | end -------------------------------------------------------------------------------- /test/testsActions.lua: -------------------------------------------------------------------------------- 1 | testsActions = {} 2 | 3 | function testsActions:setUp() 4 | GameState = {switch = function()end} 5 | currentEnemy = enemies.container.banshee 6 | 7 | player.slot = 1 8 | player.playingCard = Card(actions.container.move) 9 | player.targetSlot = 2 10 | player.playingCardAsAction = true 11 | player.currentCardId = 1 12 | 13 | currentEnemy.playingCard = Card(actions.container.move) 14 | currentEnemy.playingCardAsAction = true 15 | currentEnemy.currentCardId = 1 16 | currentEnemy.slot = 7 17 | currentEnemy.targetSlot = 7 18 | currentEnemy.hand = {{action=actions.container.attack1}, {action=actions.container.attack1} 19 | , currentEnemy.playingCard} 20 | end 21 | 22 | function testsActions:testMove() 23 | currentEnemy.targetSlot = 5 24 | 25 | ResolutionState:move(player.playingCard.action, currentEnemy.playingCard.action) 26 | 27 | luaunit.assertEquals(player.slot, 2) 28 | luaunit.assertEquals(currentEnemy.slot, 5) 29 | end 30 | 31 | function testsActions:testIfBothSameTarget_NeitherMove() 32 | currentEnemy.targetSlot = 2 33 | 34 | ResolutionState:move(player.playingCard.action, currentEnemy.playingCard.action) 35 | 36 | luaunit.assertEquals(player.slot, 1) 37 | luaunit.assertEquals(currentEnemy.slot, 7) 38 | end 39 | 40 | function testsActions:testDropCard() 41 | local playerPlayingCard = {action = actions.container.drop1} 42 | player.playingCard = playerPlayingCard 43 | player.targetSlot = 7 44 | player.hand = {playerPlayingCard, {action=actions.container.attack1}} 45 | 46 | ResolutionState:cardsEffect(player.playingCard.action, currentEnemy.playingCard.action) 47 | 48 | luaunit.assertEquals(#currentEnemy.hand, 2) 49 | end 50 | 51 | function testsActions:testDropCard_NotSecondCardWhenFirstIsPlayingCard() 52 | local playerPlayingCard = {action = actions.container.drop1} 53 | player.playingCard = playerPlayingCard 54 | player.targetSlot = 7 55 | player.hand = {playerPlayingCard, {action=actions.container.attack1}} 56 | 57 | currentEnemy.hand = {currentEnemy.playingCard, {action=actions.container.attack1}} 58 | 59 | ResolutionState:cardsEffect(player.playingCard.action, currentEnemy.playingCard.action) 60 | luaunit.assertEquals(#currentEnemy.hand, 1) 61 | luaunit.assertEquals(currentEnemy.hand[1], currentEnemy.playingCard) 62 | end 63 | 64 | function testsActions:testDropCard_NotDropPlayingCardWhenOnly() 65 | local playerPlayingCard = {action = actions.container.drop1} 66 | player.playingCard = playerPlayingCard 67 | player.targetSlot = 7 68 | player.hand = {playerPlayingCard, {action=actions.container.attack1}} 69 | 70 | currentEnemy.hand = {currentEnemy.playingCard} 71 | 72 | ResolutionState:cardsEffect(player.playingCard.action, currentEnemy.playingCard.action) 73 | 74 | luaunit.assertEquals(#currentEnemy.hand, 1) 75 | end 76 | 77 | function testsActions:testDropCard_MissSlot_NotDrop() 78 | local playerPlayingCard = {action = actions.container.drop1} 79 | player.playingCard = playerPlayingCard 80 | player.targetSlot = 2 81 | player.hand = {playerPlayingCard, {action=actions.container.attack1}} 82 | 83 | ResolutionState:cardsEffect(player.playingCard.action, currentEnemy.playingCard.action) 84 | 85 | luaunit.assertEquals(#currentEnemy.hand, 3) 86 | end 87 | 88 | function testsActions:testSpaceRecover() 89 | map.slots[6].card = Card(nil, spaces.container.circle, 0, 0, decks.PlayerDeck) 90 | map.slots[6].baseCard = Card(nil, spaces.container.graveyard, 0, 0, decks.PublicDeck) 91 | 92 | currentEnemy.playingCard = Card(actions.container.spaceRecover) 93 | currentEnemy.targetSlot = 6 94 | 95 | ResolutionState:shiftSpace() 96 | 97 | luaunit.assertEquals(map.slots[6].card, map.slots[6].baseCard) 98 | end 99 | 100 | function testsActions:testUniverseRecover() 101 | for _, slot in pairs(map.slots) do 102 | slot.baseCard = Card(nil, spaces.container.circle, 0, 0, decks.PublicDeck) 103 | slot.card = slot.baseCard 104 | end 105 | map.slots[1].card = Card(nil, spaces.container.circle, 0, 0, decks.PlayerDeck) 106 | map.slots[3].card = Card(nil, spaces.container.fence, 0, 0, decks.PlayerDeck) 107 | map.slots[6].card = Card(nil, spaces.container.graveyard, 0, 0, decks.PlayerDeck) 108 | 109 | currentEnemy.playingCard = Card(actions.container.universeRecover) 110 | 111 | ResolutionState:shiftSpace() 112 | 113 | for _, slot in pairs(map.slots) do 114 | luaunit.assertEquals(slot.card, slot.baseCard) 115 | end 116 | end 117 | 118 | function testsActions:testA1Drop1_DropCard() 119 | local playerPlayingCard = {action = actions.container.drop1} 120 | player.playingCard = playerPlayingCard 121 | player.targetSlot = 1 122 | player.hand = {playerPlayingCard, {action=actions.container.attack1}} 123 | 124 | currentEnemy.playingCard = {action = actions.container.a1drop1} 125 | currentEnemy.attack = 0 126 | currentEnemy.targetSlot = 1 127 | currentEnemy.life = 4 128 | 129 | ResolutionState:cardsEffect(player.playingCard.action, currentEnemy.playingCard.action) 130 | 131 | luaunit.assertEquals(currentEnemy.attack, 1) 132 | luaunit.assertEquals(#player.hand, 1) 133 | end -------------------------------------------------------------------------------- /test/testsAttackAndDefence.lua: -------------------------------------------------------------------------------- 1 | testsAttackAndDefence = {} 2 | 3 | function testsAttackAndDefence:setUp() 4 | ResolutionState:reset() 5 | player.slot = 1 6 | end 7 | 8 | function testsAttackAndDefence:tearDown() 9 | ResolutionState:reset() 10 | end 11 | 12 | function testsAttackAndDefence:testCorrectDamageCalc() 13 | player.playingCard = Card(actions.container.attack1) 14 | player.playingCardAsAction = true 15 | player.targetSlot = 2 16 | currentEnemy.life = 5 17 | currentEnemy.defence = 1 18 | currentEnemy.slot = 2 19 | map.slots[1].card = Card(nil, spaces.container.circle) 20 | 21 | player.playingCard.action:effect(player, currentEnemy) 22 | ResolutionState.calcDamage() 23 | 24 | luaunit.assertEquals(currentEnemy.life, 4) 25 | end 26 | 27 | function testsAttackAndDefence:testNotRightSlot_NoDamage() 28 | player.playingCard = {action = actions.container['attack1']} 29 | player.playingCardAsAction = true 30 | player.targetSlot = 2 31 | currentEnemy.life = 5 32 | currentEnemy.slot = 3 33 | map.slots[1].card = {space = spaces.container.circle} 34 | 35 | player.playingCard.action:effect(player, currentEnemy) 36 | ResolutionState.calcDamage() 37 | 38 | luaunit.assertEquals(currentEnemy.life, 5) 39 | end -------------------------------------------------------------------------------- /test/testsBanshee.lua: -------------------------------------------------------------------------------- 1 | testsBanshee = {} 2 | 3 | function testsBanshee:setUp() 4 | fillAllDecks() 5 | player.slot = 1 6 | player.playingCard = Card(actions.container.move) 7 | player.targetSlot = 1 8 | 9 | currentEnemy = enemies.container.banshee 10 | currentEnemy:init() 11 | currentEnemy.slot = 2 12 | currentEnemy.hand = {Card(actions.container.move)} 13 | currentEnemy.targetSlot = 2 14 | end 15 | 16 | function testsBanshee:testSpecialAttack() 17 | currentEnemy:playCard(player) 18 | ResolutionState:reset() 19 | ResolutionState:cardsEffect(player.playingCard.action, currentEnemy.playingCard.action) 20 | luaunit.assertEquals(currentEnemy.specialCounter, 1) 21 | luaunit.assertNotEquals(player.damagePending, 3) 22 | 23 | currentEnemy:playCard(player) 24 | ResolutionState:reset() 25 | ResolutionState:cardsEffect(player.playingCard.action, currentEnemy.playingCard.action) 26 | luaunit.assertEquals(currentEnemy.specialCounter, 2) 27 | luaunit.assertNotEquals(player.damagePending, 3) 28 | 29 | currentEnemy:playCard(player) 30 | ResolutionState:reset() 31 | ResolutionState:cardsEffect(player.playingCard.action, currentEnemy.playingCard.action) 32 | luaunit.assertEquals(currentEnemy.specialCounter, 0) 33 | luaunit.assertEquals(player.damagePending, 3) 34 | end -------------------------------------------------------------------------------- /test/testsDiscardCardState.lua: -------------------------------------------------------------------------------- 1 | require('src.states/DiscardCardState') 2 | 3 | testsDiscardCardState = {} 4 | 5 | function testsDiscardCardState:tearDown() 6 | 7 | end 8 | 9 | function testsDiscardCardState:testConfirmSelection_RemovedCards() 10 | decks.PlayerDeck.cards = { 11 | {isDiscardSelected = false}, 12 | {isDiscardSelected = true}, 13 | {isDiscardSelected = true}, 14 | } 15 | DiscardCardState:confirmSelection() 16 | 17 | luaunit.assertEquals(#decks.PlayerDeck.cards, 1) 18 | for _, card in pairs(decks.PlayerDeck.cards) do 19 | luaunit.assertIsNil(card.isDiscardSelected) 20 | end 21 | end -------------------------------------------------------------------------------- /test/testsFillAllDecks.lua: -------------------------------------------------------------------------------- 1 | testsFillAllDecks = {} 2 | 3 | function testsFillAllDecks:setUp() 4 | 5 | end 6 | 7 | function testsFillAllDecks:tearDown() 8 | table.clean(decks.PublicDeck.cards) 9 | end 10 | 11 | function testsFillAllDecks:testAllDecksExecuted() 12 | fillAllDecks() 13 | luaunit.assertNotIsNil(decks.PublicDeck) 14 | end 15 | 16 | function testsFillAllDecks:testCreateAllCards() 17 | fillAllDecks() 18 | luaunit.assertEquals(#decks.PublicDeck.cards, decks.PublicDeck.size) 19 | end -------------------------------------------------------------------------------- /test/testsGhost.lua: -------------------------------------------------------------------------------- 1 | testsGhost = {} 2 | 3 | function testsGhost:setUp() 4 | fillAllDecks() 5 | for _, slot in pairs(map.slots) do 6 | slot.card = Card(nil, spaces.container.circle) 7 | end 8 | ResolutionState:reset() 9 | player.slot = 1 10 | player.playingCard = Card(actions.container.move) 11 | player.targetSlot = 1 12 | 13 | currentEnemy = enemies.container.ghost 14 | currentEnemy:init() 15 | currentEnemy.slot = 2 16 | currentEnemy.hand = {Card(actions.container.move)} 17 | currentEnemy.targetSlot = 2 18 | end 19 | 20 | function testsGhost:testSpecialAttack_AllShiftToGraveyard_And_Continue() 21 | currentEnemy.specialCounter = 3 22 | currentEnemy:playCard(player) 23 | 24 | ResolutionState:shiftSpace() 25 | 26 | for _, slot in pairs(map.slots) do 27 | luaunit.assertEquals(slot.card.space, spaces.container.graveyard) 28 | end 29 | 30 | luaunit.assertIsTrue(ResolutionState.isPlayerShiftSpaceDone) 31 | end 32 | 33 | function testsGhost:testSpecialAttack_AllAlreadyGraveyard_Continue() 34 | for _, slot in pairs(map.slots) do 35 | slot.card = Card(nil, spaces.container.graveyard) 36 | end 37 | currentEnemy.specialCounter = 3 38 | currentEnemy:playCard(player) 39 | 40 | ResolutionState:shiftSpace() 41 | 42 | luaunit.assertIsTrue(ResolutionState.isPlayerShiftSpaceDone) 43 | end -------------------------------------------------------------------------------- /test/testsHumpClass.lua: -------------------------------------------------------------------------------- 1 | testsHumpClass = {} 2 | 3 | function testsHumpClass:test2Instances() 4 | local Feline = class{ 5 | init = function(self, size) 6 | self.size = size 7 | self.elements = {} 8 | end; 9 | } 10 | 11 | local garfield = Feline(.7) 12 | local felix = Feline(.8) 13 | 14 | luaunit.assertEquals(garfield.size, .7) 15 | luaunit.assertEquals(felix.size, .8) 16 | 17 | table.add(garfield.elements, 'a') 18 | table.add(felix.elements, 'b') 19 | 20 | luaunit.assertEquals(#garfield.elements, 1) 21 | luaunit.assertEquals(#felix.elements, 1) 22 | luaunit.assertEquals(garfield.elements[1], 'a') 23 | luaunit.assertEquals(felix.elements[1], 'b') 24 | end 25 | 26 | function testsHumpClass:testSelectionGroup2Instances() 27 | local a = SelectionGroup() 28 | local b = SelectionGroup() 29 | 30 | a.name = 'a' 31 | b.name = 'b' 32 | luaunit.assertEquals(a.name, 'a') 33 | luaunit.assertEquals(b.name, 'b') 34 | 35 | a:add({name = 'a', setSelect = function(isSelect)end}) 36 | b:add({name = 'b', setSelect = function(isSelect)end}) 37 | 38 | luaunit.assertEquals(#a.elements, 1) 39 | luaunit.assertEquals(#b.elements, 1) 40 | luaunit.assertEquals(a.elements[1].name, 'a') 41 | luaunit.assertEquals(b.elements[1].name, 'b') 42 | end -------------------------------------------------------------------------------- /test/testsMap.lua: -------------------------------------------------------------------------------- 1 | testsMap = {} 2 | 3 | function testsMap:testGetNoHoleNeighbours_NoHole() 4 | for i = 1, 7 do 5 | local neighbours = map:getNoHoleNeighbours(i) 6 | luaunit.assertIsFalse(table.contains(neighbours, 0)) 7 | end 8 | end 9 | 10 | function testsMap:testRandomNeighbour_No0() 11 | for i = 1, 500 do 12 | local neighbour = map:randomNeighbour(i%7 + 1) 13 | luaunit.assertNotIs(neighbour, 0) 14 | end 15 | end 16 | 17 | function testsMap:testRandomNeighbour_NoExceptValues() 18 | for i = 1, 500 do 19 | local neighbour = map:randomNeighbour(1, 2) 20 | luaunit.assertNotIs(neighbour, 2) 21 | end 22 | end 23 | 24 | function testsMap:testGetSlotOccupied() 25 | player.slot = 1 26 | currentEnemy.slot = 2 27 | 28 | luaunit.assertEquals(map:getSlotOccupied(1), player) 29 | luaunit.assertEquals(map:getSlotOccupied(2), currentEnemy) 30 | luaunit.assertIsNil(map:getSlotOccupied(3)) 31 | 32 | end 33 | 34 | function testsMap:testGetBestBenefitNeighbour() 35 | map.slots[1].card = {space = spaces.container.graveyard} 36 | map.slots[2].card = {space = spaces.container.fence} 37 | map.slots[6].card = {space = spaces.container.castle} 38 | map.slots[7].card = {space = spaces.container.graveyard} 39 | 40 | luaunit.assertEquals(map:getBestBenefitNeighbour(1), 6) 41 | 42 | map.slots[1].card = {space = spaces.container.castle} 43 | 44 | luaunit.assertEquals(map:getBestBenefitNeighbour(1), 1) 45 | end -------------------------------------------------------------------------------- /test/testsOfUtils.lua: -------------------------------------------------------------------------------- 1 | testsOfUtils = {} 2 | 3 | function testsOfUtils:setUp() 4 | 5 | end 6 | 7 | function testsOfUtils:tearDown() 8 | 9 | end 10 | 11 | function testsOfUtils:testCreateRandomCard() 12 | local card = createRandomCard({2,4}) 13 | -- 14 | --score in range 15 | local score = card.action.score + card.space.score 16 | luaunit.assertIsTrue(score >= 2 and score <= 4) 17 | end 18 | 19 | function testsOfUtils:testGetLifePercent() 20 | local char = {life = 2, initLife = 10} 21 | luaunit.assertEquals(getLifePercent(char), 0.2) 22 | end 23 | 24 | function testsOfUtils:testChooseHandCardHeal() 25 | local char = {hand = { 26 | {action = actions.container.attack1}, 27 | {action = actions.container.heal1}, 28 | {action = actions.container.defence1}}} 29 | luaunit.assertEquals(chooseHandCardHeal(char), 2) 30 | end 31 | 32 | function testsOfUtils:testSortDeck() 33 | fillAllDecks() 34 | sortDeck(decks.PublicDeck) 35 | for i = 1, #decks.PublicDeck.cards-1 do 36 | card = decks.PublicDeck.cards[i] 37 | cardNext = decks.PublicDeck.cards[i+1] 38 | luaunit.assertIsTrue(card.action.score+card.space.score <= 39 | cardNext.action.score+cardNext.space.score) 40 | end 41 | end -------------------------------------------------------------------------------- /test/testsPickCards.lua: -------------------------------------------------------------------------------- 1 | testsPickCards = {} 2 | 3 | function testsPickCards:setUp() 4 | fillAllDecks() 5 | end 6 | 7 | function testsPickCards:tearDown() 8 | table.clean(decks.PublicDeck.cards) 9 | end 10 | 11 | function testsPickCards:testPickPublicDeckCards() 12 | local hand = decks.PublicDeck:pickCards(2) 13 | 14 | luaunit.assertEquals(#hand, 2) 15 | luaunit.assertNotIsNil(hand[1].action) 16 | luaunit.assertNotIs(hand[1].space) 17 | end -------------------------------------------------------------------------------- /test/testsPlaySpace.lua: -------------------------------------------------------------------------------- 1 | testsPlaySpace = {} 2 | 3 | function testsPlaySpace:setUp() 4 | map.slots[1].card = Card(nil, spaces.container.circle) 5 | map.slots[1].card.deck = decks.PlayerDeck 6 | player.playingCard = Card(nil, spaces.container.fence) 7 | player.playingCardAsAction = false 8 | player.targetSlot = 1 9 | 10 | currentEnemy.playingCard = Card(actions.container.move) 11 | end 12 | 13 | function testsPlaySpace:tearDown() 14 | map.slots[1].card = nil 15 | player.playingCard = nil 16 | table.clean(decks.PlayerDeck.discardCards) 17 | end 18 | 19 | function testsPlaySpace:testChangeMapSlotCard() 20 | ResolutionState:shiftSpace() 21 | luaunit.assertEquals(map.slots[1].card, player.playingCard) 22 | end 23 | 24 | function testsPlaySpace:testOldCardToDiscardPile() 25 | local oldCard = map.slots[1].card 26 | ResolutionState:shiftSpace() 27 | luaunit.assertEquals(decks.PlayerDeck.discardCards[1], oldCard) 28 | end -------------------------------------------------------------------------------- /test/testsSpaces.lua: -------------------------------------------------------------------------------- 1 | testsSpaces = {} 2 | 3 | function testsSpaces:setUp() 4 | GameState = {switch = function()end} 5 | currentEnemy = enemies.container.banshee 6 | ResolutionState:reset() 7 | player.slot = 1 8 | player.playingCardAsAction = true 9 | end 10 | 11 | function testsSpaces:tearDown() 12 | ResolutionState:reset() 13 | end 14 | 15 | function testsSpaces:testFenceAffectDefence() 16 | player.playingCard = {action = actions.container.move} 17 | player.targetSlot = 1 18 | map.slots[1].card = {space = spaces.container.fence} 19 | currentEnemy.slot = 2 20 | map.slots[2].card = {space = spaces.container.fence} 21 | ResolutionState.extraDefence() 22 | 23 | luaunit.assertEquals(player.defence, 1) 24 | luaunit.assertEquals(currentEnemy.defence, 1) 25 | end 26 | 27 | function testsSpaces:testCircleAffectAttack() 28 | player.playingCard = Card(actions.container.attack1) 29 | map.slots[1].card = Card(nil, spaces.container.circle) 30 | player.playingCard.action:effect(player, currentEnemy) 31 | 32 | luaunit.assertEquals(player.attack, 2) 33 | end 34 | 35 | function testsSpaces:testGraveyardRunOnUpkeep() 36 | player.life = 3 37 | map.slots[1].card = {space = spaces.container.graveyard} 38 | 39 | UpkeepState:enter() 40 | 41 | luaunit.assertEquals(player.life, 2) 42 | end 43 | 44 | function testsSpaces:testGraveyard_ImmuneChar_Nothing() 45 | currentEnemy = enemies.container.ghost 46 | currentEnemy.life = 5 47 | currentEnemy.slot = 2 48 | map.slots[2].card = {space = spaces.container.graveyard} 49 | 50 | UpkeepState:enter() 51 | 52 | luaunit.assertEquals(currentEnemy.life, 5) 53 | end 54 | 55 | function testsSpaces:testCovePickCardOnDamaged() 56 | fillAllDecks() 57 | 58 | map.slots[1].card = {space = spaces.container.well} 59 | map.slots[1].card = {space = spaces.container.well} 60 | 61 | player.playingCard = {action = actions.container.move} 62 | player.life = 3 63 | player.targetSlot = 1 64 | player.currentCardId = 1 65 | player.hand = {player.playingCard} 66 | 67 | currentEnemy.playingCard = {action = actions.container.attack1} 68 | currentEnemy.targetSlot = 1 69 | currentEnemy.hand = {currentEnemy.playingCard, {action = actions.container.attack1}} 70 | 71 | ResolutionState:enter() 72 | 73 | luaunit.assertEquals(#player.hand, 1) 74 | luaunit.assertEquals(#currentEnemy.hand, 2) 75 | end 76 | 77 | function testsSpaces:testDesertDropCardOnAttack() 78 | map.slots[1].card = {space = spaces.container.desert} 79 | map.slots[2].card = {space = spaces.container.circle} 80 | 81 | player.playingCard = {action = actions.container.attack1} 82 | player.life = 3 83 | player.targetSlot = 2 84 | player.currentCardId = 1 85 | player.hand = {player.playingCard, {action = actions.container.attack1}} 86 | 87 | currentEnemy.playingCard = {action = actions.container.attack1} 88 | currentEnemy.targetSlot = 1 89 | currentEnemy.hand = {currentEnemy.playingCard, {action = actions.container.attack1}} 90 | 91 | ResolutionState.onAttack() 92 | 93 | luaunit.assertEquals(#player.hand, 2) 94 | luaunit.assertEquals(#currentEnemy.hand, 1) 95 | end 96 | 97 | function testsSpaces:testDesert_NotCorrectSlot_NoDropCard() 98 | map.slots[1].card = {space = spaces.container.desert} 99 | map.slots[2].card = {space = spaces.container.circle} 100 | 101 | player.playingCard = {action = actions.container.attack1} 102 | player.life = 3 103 | player.targetSlot = 3 104 | player.currentCardId = 1 105 | player.hand = {player.playingCard, {action = actions.container.attack1}} 106 | 107 | currentEnemy.playingCard = {action = actions.container.attack1} 108 | currentEnemy.targetSlot = 1 109 | currentEnemy.hand = {currentEnemy.playingCard, {action = actions.container.attack1}} 110 | 111 | ResolutionState.onAttack() 112 | 113 | luaunit.assertEquals(#player.hand, 2) 114 | luaunit.assertEquals(#currentEnemy.hand, 2) 115 | end -------------------------------------------------------------------------------- /test/testsUpkeepState.lua: -------------------------------------------------------------------------------- 1 | testsUpkeepState = {} 2 | --------------------------------------------------------------------------------