diff --git a/packages/junon-io/client/main.ejs b/packages/junon-io/client/main.ejs
index dabf19c3..81ca3d82 100644
--- a/packages/junon-io/client/main.ejs
+++ b/packages/junon-io/client/main.ejs
@@ -120,6 +120,7 @@
+
@@ -973,6 +974,36 @@
value1, value2, ... , valueN |
Returns the smallest value among the given arguments. Ex. $min(1,2,3,4,5) = 1 |
+
+ | $sin |
+ degrees |
+ Calculates the sine of an angle in degrees. Ex. $sin(30) = 0.5 |
+
+
+ | $cos |
+ degrees |
+ Calculates the cosine of an angle in degrees. Ex. $cos(0) = 1 |
+
+
+ | $tan |
+ degrees |
+ Calculates the tangent of an angle in degrees. Ex. $tan(45) = 1 |
+
+
+ | $asin |
+ value (-1 ~ 1) |
+ Calculates the arc sine in degrees. Ex. $asin(0.5) = 30 |
+
+
+ | $acos |
+ value (-1 ~ 1) |
+ Calculates the arc cosine in degrees |
+
+
+ | $atan |
+ value |
+ Calculates the arc tangent in degrees. |
+
| $isLoggedIn |
$player |
@@ -983,6 +1014,11 @@
$entityId |
the type name of a building or entity. Ex. /chat @a The building type of entity number 2017 is $getBuildingType(2017) |
+
+ | $getEntityType |
+ $entityId |
+ the type name of a building or entity. Ex. /chat @a The entity type of entity number 2017 is $getEntityType(2017) |
+
| $getDay |
None |
@@ -1020,8 +1056,8 @@
| $getAngle |
- $player |
- Returns the angle of the specified player. Ex. /chat @a $getAngle($player) |
+ $entityId |
+ Gets the current facing angle of an entity in degrees. |
diff --git a/packages/junon-io/client/src/main.js b/packages/junon-io/client/src/main.js
index 2617bba0..f617da29 100644
--- a/packages/junon-io/client/src/main.js
+++ b/packages/junon-io/client/src/main.js
@@ -21,6 +21,7 @@ const Polyglot = require('node-polyglot')
const japaneseTranslationMap = require("../../common/translations/ja")
const englishTranslationMap = require("../../common/translations/en")
const russianTranslationMap = require("../../common/translations/ru")
+const trationalChineseTranslationMap = require("../../common/translations/zh-Hant")
const ObjectPool = require("../../common/entities/object_pool")
const TeamEntry = require("./components/team_entry")
const Lighting = require("./entities/lighting")
@@ -141,6 +142,8 @@ class Main {
phrases = Object.assign({}, phrases, japaneseTranslationMap)
} else if (language === "ru") {
phrases = Object.assign({}, phrases, russianTranslationMap)
+ } else if (language === "zh-Hant") {
+ phrases = Object.assign({}, phrases, trationalChineseTranslationMap)
}
}
diff --git a/packages/junon-io/common/constants.json b/packages/junon-io/common/constants.json
index 9cc3fd55..26870e86 100644
--- a/packages/junon-io/common/constants.json
+++ b/packages/junon-io/common/constants.json
@@ -985,6 +985,7 @@
"gold": 2000
},
"isBuildingDestroyer": true,
+ "canDamageWalls": true,
"attackGroups": ["players", "buildings", "mobs"],
"drop": ["Web"],
"resistance": ["fire"],
@@ -1044,6 +1045,8 @@
"cost": {
"gold": 500
},
+ "isBuildingDestroyer": true,
+ "canDamageWalls": true,
"attackGroups": ["players", "buildings", "mobs", "distributions"],
"sprite": {
"width": 40,
@@ -1121,6 +1124,8 @@
},
"Sapper": {
"parent": "Mobs.Guard",
+ "resistance": ["fire"],
+ "immunity": ["poison"],
"sprite": {
"width": 60,
"height": 181
@@ -1143,6 +1148,8 @@
"cost": {
"gold": 2500
},
+ "isBuildingDestroyer": true,
+ "canDamageWalls": true,
"attackGroups": ["players", "buildings", "mobs"],
"sprite": {
"width": 50,
@@ -1168,6 +1175,8 @@
"Firebat": {
"parent": "Mobs.BaseMob",
"hasBlood": true,
+ "isBuildingDestroyer": true,
+ "canDamageWalls": true,
"drop": ["Flamethrower"],
"resistance": ["fire"],
"immunity": ["poison"],
diff --git a/packages/junon-io/common/translations/zh-Hant.json b/packages/junon-io/common/translations/zh-Hant.json
new file mode 100644
index 00000000..49c1f735
--- /dev/null
+++ b/packages/junon-io/common/translations/zh-Hant.json
@@ -0,0 +1,807 @@
+{
+ "New Game": "新遊戲",
+ "Visit Colony": "訪問殖民地",
+ "Tutorial": "教學模式",
+ "Starting": "載入中",
+ "Load Game": "載入遊戲中",
+ "Login": "登入",
+ "Logout": "登出",
+ "Back": "返回",
+ "Online": "線上",
+ "Offline": "離線",
+ "Initializing": "初始化中",
+ "Last Played At": "最後遊玩時間",
+ "Public": "公開",
+ "Private": "私人",
+ "Start Game": "開始遊戲",
+ "Welcome": "歡迎",
+ "Awoken The Spiders You Have": "蜘蛛已被驚醒",
+ "Unable to Find Spawn Position for Trader. Skipping Event": "無法找到商人生成位置,已跳過該事件",
+ "Raid Incoming": "襲擊即將來臨",
+ "Spider Infestation": "蜘蛛入侵中",
+ "Respawn": "重生",
+ "Iron Ore": "鐵礦",
+ "Iron Bar": "鐵錠",
+ "Copper Ore": "銅礦",
+ "Copper Bar": "銅錠",
+ "Circuit Board": "電路板",
+ "Cloth": "布料",
+ "Sand": "沙子",
+ "Glass": "玻璃",
+ "Wood": "木材",
+ "Bottle": "瓶子",
+ "Water Bottle": "水瓶",
+ "Blood Bottle": "血瓶",
+ "Wire": "電線",
+ "Potato": "馬鈴薯",
+ "Potato Seed": "馬鈴薯種子",
+ "Plant Fiber": "植物纖維",
+ "Fiber Seed": "纖維種子",
+ "Wheat": "小麥",
+ "Wheat Seed": "小麥種子",
+ "Gel": "凝膠",
+ "Alien Meat": "外星生物肉",
+ "Animal Meat": "動物肉",
+ "Human Meat": "人肉",
+ "Gelatin": "明膠",
+ "Steak": "牛排",
+ "Hot Dog": "熱狗",
+ "Vegan Pizza": "素食披薩",
+ "Slimy Meat Pizza": "黏滑肉披薩",
+ "Beer": "啤酒",
+ "Cola": "可樂",
+ "First Aid Kit": "急救箱",
+ "Antidote": "解毒劑",
+ "Stimpack": "興奮劑",
+ "Mop": "拖把",
+ "Pistol": "手槍",
+ "Shotgun": "霰彈槍",
+ "Bullet Ammo": "子彈",
+ "Bullet Damage": "子彈傷害",
+ "Fire Extinguisher": "滅火器",
+ "Flame Thrower": "火焰噴射器",
+ "Lighter": "打火機",
+ "Survival Tool": "生存工具",
+ "Drill": "鑽頭",
+ "Katana": "武士刀",
+ "Space Suit": "太空服",
+ "Furnace": "熔爐",
+ "Butcher Table": "屠宰台",
+ "Stove": "爐灶",
+ "Brewery": "釀造機",
+ "Chemistry Station": "化學工作站",
+ "Mining Drill": "採礦鑽機",
+ "Ammo Printer": "彈藥製造機",
+ "Refinery": "精煉機",
+ "Wall": "牆壁",
+ "Spike Trap": "尖刺陷阱",
+ "Manual Airlock": "手動氣閘",
+ "Airlock": "氣閘門",
+ "Lamp": "燈",
+ "Steel Crate": "鋼鐵箱",
+ "Water Pump": "抽水泵",
+ "Liquid Pipe": "液體管道",
+ "Liquid Tank": "液體儲存槽",
+ "Suit Station": "太空服站",
+ "Beacon": "信標",
+ "Mini Turret": "小型砲塔",
+ "Air Alarm": "空氣警報器",
+ "Ventilator": "通風機",
+ "Oxygen Generator": "製氧機",
+ "Small Oxygen Tank": "小型氧氣罐",
+ "Gas Pipe": "氣體管道",
+ "Bed": "床",
+ "Table": "桌子",
+ "Trading Table": "貿易桌",
+ "Chair": "椅子",
+ "Cage": "籠子",
+ "Television": "電視",
+ "Pot": "盆栽",
+ "Lattice": "格柵地板",
+ "Steel Floor": "鋼鐵地板",
+ "Soil": "土壤",
+ "Wood Floor": "木地板",
+ "Carpet Floor": "地毯地板",
+ "Tiled Floor": "磁磚地板",
+ "Purple Floor": "紫色地板",
+ "Plated Floor": "鍍層地板",
+ "Stripe Plated Floor": "條紋鍍層地板",
+ "Green Floor": "綠色地板",
+ "Rail Track": "軌道",
+ "Rail Stop": "電車站",
+ "Oil Refinery": "石油鑽探精煉機",
+ "Power Generator": "燃料發電機",
+ "Fuel Pipe": "燃料管道",
+ "Fuel Tank": "燃料槽",
+ "Solar Panel": "太陽能板",
+ "Remaining": "剩餘",
+ "Owner": "擁有者",
+ "Stored": "儲存電量",
+ "Production": "產電量",
+ "Power Needed": "所需電力",
+ "Slime": "史萊姆",
+ "Slime Corpse": "史萊姆屍體",
+ "Brood": "寄生蟲",
+ "Brood Corpse": "寄生蟲屍體",
+ "Spider": "蜘蛛",
+ "Spider Corpse": "蜘蛛屍體",
+ "Monkey": "猴子",
+ "Monkey Corpse": "猴子屍體",
+ "Chicken": "雞",
+ "Chicken Corpse": "雞屍體",
+ "Clean Bot": "清潔機器人",
+ "Clean Bot Corpse": "損壞的清潔機器人",
+ "Trader": "商人",
+ "Empire": "帝國",
+ "Guard": "守衛",
+ "Chemist": "化學家",
+ "Chemist Corpse": "化學家屍體",
+ "Messenger": "收稅使者",
+ "Messenger Corpse": "收稅使者屍體",
+ "O2": "氧氣濃度",
+ "Crafting": "製作",
+ "Ingredients": "材料",
+ "Suit": "太空服",
+ "Inventory": "物品欄",
+ "Map": "地圖",
+ "Colony": "殖民地資訊",
+ "Name": "殖民地名稱",
+ "Location": "位置",
+ "Members": "成員",
+ "Days Alive": "存活天數",
+ "Alliance": "對外關係資訊",
+ "Status": "狀態",
+ "Friendly": "友好",
+ "Neutral": "中立",
+ "Hostile": "敵對",
+ "Relationship": "友好度",
+ "Insufficient Power": "電力不足",
+ "Generates Iron Ore": "正在生成鐵礦",
+ "Resume": "繼續遊戲",
+ "Settings": "設定",
+ "Exit Game": "結束遊戲",
+ "Sound": "音效",
+ "Volume": "音量",
+ "Controls": "控制",
+ "Move Up": "向上移動",
+ "Move Left": "向左移動",
+ "Move Down": "向下移動",
+ "Move Right": "向右移動",
+ "Rotate": "旋轉",
+ "Craft": "製作",
+ "Interact": "互動",
+ "Attack": "攻擊",
+ "Chat": "聊天",
+ "Camera Mode": "相機模式",
+ "Stats View": "通訊狀態檢視",
+ "Press Any Key": "請按下要設定的按鍵",
+ "Trade": "交易",
+ "Sell": "賣出",
+ "Trader has arrived": "商人已抵達",
+ "Tax Officer Has Arrived": "收稅使者已抵達",
+ "Confirm": "確認",
+ "Needs Fuel": "需要補充燃料",
+ "Bullet Ammo Required": "需要子彈",
+ "Destination": "目的地",
+ "Go": "出發",
+ "No Passengers on Tram": "電車上沒有任何東西",
+ "Web": "蜘蛛網",
+ "Web Trap": "黏網陷阱",
+ "Sign": "標誌牌",
+ "Happiness": "幸福度",
+ "Make Pet": "設為寵物",
+ "Place ore in left slot to refine gold": "將礦石放入左側槽以精煉黃金",
+ "You Died": "你已死亡!",
+ "Blood Pack": "血袋",
+ "Lead Pipe": "鐵管",
+ "Cryo Tube": "冷凍艙",
+ "Oxygen Tank": "氧氣罐",
+ "Generates Copper Ore": "正在生成銅礦",
+ "Buy": "購買",
+ "ColonyName": "殖民地名稱",
+ "ColonyDay": "經過天數",
+ "ColonyOnline": "人數",
+ "PlayersOnline": "在線人數",
+ "TutorialWelcomeMessage": "這是用來了解如何供電以及為房間補充氧氣的示範基地。按 [C] 鍵打開製作選單,查看各個裝置的功能。",
+ "Potato.Description": "生存的主食。",
+ "PotatoSeed.Description": "種在土壤中可長成馬鈴薯。",
+ "PlantFiber.Description": "製作藥品等材料。",
+ "FiberSeed.Description": "種在土壤中可長成纖維植物。",
+ "Wheat.Description": "未加工的小麥,可用於製作麵包等。",
+ "WheatSeed.Description": "種在土壤中可長成小麥。",
+ "Gel.Description": "可加工成明膠。",
+ "AlienMeat.Description": "生的外星生物肉,建議烹煮後再食用。",
+ "AnimalMeat.Description": "生的動物肉,烹煮後更美味。",
+ "HumanMeat.Description": "生的人肉。絕對不要吃!?",
+ "Gelatin.Description": "富含蛋白質,美味。",
+ "Steak.Description": "烹煮過的肉,別問是什麼肉。",
+ "HotDog.Description": "烹煮過的動物肉。美式經典食物",
+ "VeganPizza.Description": "素食者也能享用的美味披薩。",
+ "SlimyMeatPizza.Description": "由史萊姆和動物肉製成的異國風味披薩。",
+ "LectersDinner.Description": "烹煮過的人肉。令人印象深刻的味道。",
+ "Beer.Description": "恢復30點體力。注意別喝過量。",
+ "Cola.Description": "恢復10點體力。",
+ "FirstAidKit.Description": "恢復50點生命值。",
+ "Antidote.Description": "治療中毒效果。",
+ "BloodPack.Description": "恢復50點生命值。",
+ "Stimpack.Description": "犧牲25點生命值,換取15秒內攻擊速度加倍。",
+ "Entity.Health": "耐久度",
+ "PlantGrowth": "成長度",
+ "DayCount": "第%{day}天",
+ "HourCount": "%{hour}時",
+ "LeadPipe.Description": "基本近戰武器。攻擊力:7",
+ "Mop.Description": "用於清潔地板的工具。",
+ "Pistol.Description": "可遠距離攻擊的武器。攻擊力:10",
+ "Shotgun.Description": "一次發射多發散射彈丸的武器。每顆彈丸造成12點傷害。",
+ "FireExtinguisher.Description": "可用來滅火。",
+ "FlameThrower.Description": "可噴射火焰攻擊。",
+ "Bottle.Description": "可用來盛裝液體。",
+ "Lighter.Description": "可用來點火。需從燃料槽補充燃料。",
+ "SurvivalTool.Description": "可採礦或拆除建築物。攻擊力:2",
+ "Drill.Description": "採礦速度比生存工具更快。攻擊力:5",
+ "Katana.Description": "可同時攻擊三個敵人。攻擊力:15",
+ "SpaceSuit.Description": "在太空中必須穿著以防窒息。",
+ "Furnace.Description": "可將礦石精煉成其他物品。",
+ "ButcherTable.Description": "可將屍體拆解以獲得肉類和其他資源。",
+ "Stove.Description": "在此處可進行烹飪。",
+ "Brewery.Description": "製作飲料解渴。",
+ "ChemistryStation.Description": "可製作各種化學藥品。",
+ "MiningDrill.Description": "自動從地下開採礦石。",
+ "AmmoPrinter.Description": "可製作武器彈藥。",
+ "Refinery.Description": "將礦石熔煉成黃金(Gold)。",
+ "Wall.Description": "用於圍出房間。不透氣。",
+ "SpikeTrap.Description": "對踩到陷阱的敵人造成傷害。",
+ "ManualAirlock.Description": "手動開關的氣閘。無需電力即可操作。",
+ "Airlock.Description": "不透氣的門。使用它可以使內部區域充滿氧氣。",
+ "Lamp.Description": "照亮周圍環境。",
+ "WaterPump.Description": "抽水源的水。必須設置在水源附近並用管道連接。",
+ "LiquidPipe.Description": "連接液體相關裝置以輸送液體。",
+ "LiquidTank.Description": "儲存液體。",
+ "CryoTube.Description": "復活並治療放入其中的生物。",
+ "SuitStation.Description": "可存放太空服。",
+ "Beacon.Description": "將成為訪問你殖民地的玩家生成點。",
+ "MiniTurret.Description": "防禦用砲塔。需要電力和彈藥。每發子彈攻擊力:12",
+ "AirAlarm.Description": "當室內沒有氧氣時會亮紅燈警報。",
+ "Ventilator.Description": "可將氧氣注入密封的房間。必須連接氣體管道。",
+ "OxygenGenerator.Description": "從水中產生氧氣。",
+ "SmallOxygenTank.Description": "儲存少量氧氣。必須連接氣體管道。",
+ "GasPipe.Description": "連接氣體相關裝置以輸送氣體。",
+ "Bed.Description": "在上面睡覺可恢復體力。。",
+ "Table.Description": "裝飾用桌子。",
+ "TradingTable.Description": "設置後可吸引商人前來。",
+ "Chair.Description": "裝飾用椅子。",
+ "Cage.Description": "用於收容馴服的動物或外星生物。",
+ "Television.Description": "裝飾用電視。",
+ "Pot.Description": "可放置花草的裝飾用花盆。",
+ "Lattice.Description": "用於在外部放置裝置的輕量地板。只能放置在小行星區域外(如太空上)",
+ "SteelFloor.Description": "可在上面行走。也用於在水面、石油上或太空中放置裝置。",
+ "Soil.Description": "用於種植農作物。",
+ "WoodFloor.Description": "木質地板。",
+ "CarpetFloor.Description": "鋪有紅地毯的地板。",
+ "TiledFloor.Description": "磁磚地板。",
+ "PurpleFloor.Description": "紫色地板。",
+ "PlatedFloor.Description": "鍍層地板。",
+ "StripePlatedFloor.Description": "條紋鍍層地板。",
+ "GreenFloor.Description": "綠色地板。",
+ "RailTrack.Description": "電車行駛的軌道。",
+ "RailStop.Description": "電車出發和停靠的車站。必須設置在軌道末端。",
+ "Wire.Description": "在裝置間供電。",
+ "OilRefinery.Description": "從地下開採石油並精煉成化學燃料。",
+ "PowerGenerator.Description": "使用化學燃料發電。",
+ "FuelPipe.Description": "連接裝置以輸送燃料。",
+ "FuelTank.Description": "儲存燃料。",
+ "AllianceName": "勢力名稱",
+ "taxes_not_paid.Description": "未繳稅",
+ "cannibalism.Description": "食用了帝國士兵的肉",
+ "extreme_cannibalism.Description": "吞噬了大量帝國士兵的肉",
+ "RailStationName": "電車站名稱",
+ "WebTrap.Description": "使敵人被困住5秒無法動彈的陷阱。",
+ "Sign.Description": "可在上面書寫想讓其他玩家看到的文字。",
+ "RifleAmmo.Description": "突擊步槍用彈藥。",
+ "Grenade.Description": "爆炸並對敵人造成傷害的投擲武器。",
+ "PoisonGrenade.Description": "在一定區域產生毒氣的投擲武器。",
+ "Web.Description": "黏性物質,可用來製作物品。",
+ "Gold.Description": "用於買賣物品或接受服務的貨幣。",
+ "to save your progress": "即可儲存進度",
+ "SurvivalWelcomeMessage": "建立殖民地,建造你的太空要塞。敵人會在夜晚出現。",
+ "IronBar.Description": "鐵錠。",
+ "CopperBar.Description": "銅錠。",
+ "CircuitBoard.Description": "電路板。",
+ "Cloth.Description": "用於製作家具和裝備的材料。",
+ "Sand.Description": "製作特定物品的材料。",
+ "Glass.Description": "玻璃。",
+ "Wood.Description": "製作家具等物品的材料。",
+ "Assault Rifle": "突擊步槍",
+ "Bio Raptor": "生化迅猛龍",
+ "Bio Raptor Corpse": "生化迅猛龍屍體",
+ "Car":"汽車",
+ "Car Corpse": "汽車殘骸",
+ "SteelCrate.Description": "可儲存物品。",
+ "SolarPanel.Description": "利用太陽能發電。只能放置在格柵(Lattice)上",
+ "Messenger.DemandPayment": "以我們神聖帝國的名義,命令你繳納稅金作為忠誠的證明!",
+ "Messenger.RightChoice": "明智的選擇!",
+ "Messenger.Blasphemy": "褻瀆我們神聖帝國的叛徒!",
+ "Messenger.Choices.ProceedPay": "支付 %{gold} Gold。",
+ "Messenger.Choices.RefusePay": "拒絕支付。",
+ "Shotgun Shell": "霰彈槍彈藥",
+ "Rifle Ammo": "步槍彈藥",
+ "Livestock": "牲畜",
+ "WaterBottle.Description": "可將水裝入製氧機,或用於澆灌植物。",
+ "BloodBottle.Description": "內含血液。",
+ "BulletAmmo.Description": "手槍和砲塔使用的彈藥。",
+ "OutdatedGameClient": "遊戲用戶端已過期。請重新整理頁面。",
+ "NetworkOutdatedGameClient": "遊戲用戶端已過期。請重新整理頁面。",
+ "Escape Pod": "逃生艙",
+ "Core": "核心",
+ "Core.Description": "電源。可提供500W電力。",
+ "Connecting": "連接中",
+ "DisconnectedReconnecting": "連線已中斷。將在 %{seconds} 秒後重新連線。",
+ "Taming": "馴服度",
+ "Following": "跟隨中",
+ "Take Along": "使其跟隨",
+ "Release": "釋放",
+ "Message": "訊息",
+ "Done": "完成",
+ "Edit": "編輯",
+ "Cancel": "取消",
+ "Cook": "烹飪",
+ "Brew": "釀造",
+ "Place Ammunition": "請裝填彈藥",
+ "MatchmakerUnavailable": "無法與伺服器同步。請重新整理頁面後重試。",
+ "Relationship.EmpireFriendly": "帝國目前對你友好。",
+ "Relationship.EmpireNeutral": "帝國目前對你中立。",
+ "Relationship.EmpireHostile": "帝國目前對你敵對。",
+ "Potato Plant": "馬鈴薯植株",
+ "Fiber Plant": "纖維植物",
+ "Wheat Plant": "小麥植株",
+ "Lecters Dinner": "萊克特風晚餐",
+ "Game Settings": "遊戲設定",
+ "Zoom In": "放大",
+ "Zoom Out": "縮小",
+ "ShotgunShell.Description": "霰彈槍彈藥。",
+ "Make Livestock": "設為牲畜",
+ "Events.TaxCollection": "收稅使者來訪",
+ "Events.RaidWarning": "襲擊倒數",
+ "Events.Raid": "襲擊",
+ "Events.RaidEnd": "襲擊結束",
+ "Events.TraderArrived": "商人已抵達。",
+ "Events.SlaveTraderArrived": "奴隸商人已抵達。",
+ "Events.SpiderInfestation": "蜘蛛入侵中",
+ "Events.MeteorShower": "流星雨",
+ "Shotgun Shells Required": "需要霰彈槍彈藥。",
+ "Rifle Ammo Required": "需要步槍彈藥。",
+ "Corpse Required": "需要屍體。",
+ "Background Volume": "背景音樂音量",
+ "Effects Volume": "音效音量",
+ "No Saves Found": "找不到儲存資料",
+ "Privacy": "隱私設定",
+ "Credits": "製作名單",
+ "Inventory Full": "物品欄已滿。",
+ "Mining not allowed when owner is offline": "殖民地擁有者離線時不允許採礦。",
+ "You are not allowed to use flamethrower": "你沒有使用火焰噴射器的權限。",
+ "You dont have building permissions": "你沒有建築權限。",
+ "That doesnt belong to you": "此物品不屬於你。",
+ "You dont have storage viewing permissions": "你沒有查看儲存內容物的權限。",
+ "Invalid Item": "無法在此處使用此物品。",
+ "You just ate": "你剛剛才吃過東西。",
+ "Not enough Gold": "黃金(Gold)不足。",
+ "Not enough inventory to store gold": "沒有足夠的物品欄空間存放Gold。",
+ "Not enough items available": "物品不足。",
+ "Bot Limit Reached": "機器人數量已達上限。",
+ "Pet Limit Reached": "寵物數量已達上限。",
+ "Insufficient Blood": "血液不足。",
+ "Taming Limit Reached": "馴服數量已達上限。",
+ "The Spiders have been left in peace": "蜘蛛已恢復休眠狀態。",
+ "Rail Network is currently in use": "此車站目前正在使用中,無法拆除。",
+ "Player.Kicked": "你已被踢出。",
+ "ButcherTable.HarvestFailure": "未能成功獲取肉類或其他物品。",
+ "AssaultRifle.Description": "能快速連續發射子彈的突擊步槍。",
+ "Entity.Mob.Health": "生命值",
+ "OxygenTank.Description": "儲存氧氣。必須連接氣體管道。",
+ "Grenade": "手榴彈",
+ "AssignedRole": "你已被賦予 %{roleName} 職位",
+ "GuestRole": "你已被賦予訪客職位",
+ "MemberRole": "你已被賦予成員職位",
+ "Visibility": "可見性",
+ "You are running out of oxygen": "你的氧氣即將耗盡。",
+ "You are tired and restless": "你感到疲倦不安。",
+ "You are feeling hungry": "你感到飢餓。",
+ "IronOre.Description": "鐵礦。",
+ "CopperOre.Description": "銅礦。",
+ "VisitOtherColony": "訪問其他殖民地",
+ "Visit": "訪問殖民地",
+ "RestartIn": "%{number} 秒後重新開始",
+ "Guard Corpse": "守衛屍體",
+ "MemberList": "成員列表",
+ "Days": "天",
+ "Poison Grenade": "毒氣彈",
+ "Yes": "是",
+ "No": "否",
+ "Access Denied": "無權進入。",
+ "Sealed Door": "密封門",
+ "SealedDoor.Description": "限制特定人物以外進入的門。",
+ "Unreachable": "無法觸及。",
+ "Permissions": "權限設定",
+ "Storage Access": "儲存空間存取權",
+ "Asteroid Mining": "小行星採礦",
+ "DemoteMembersPermission": "當殖民地擁有者離開遊戲時,是否將成員降級為訪客",
+ "Everyone": "所有人",
+ "ColonyLeaveConfirmMessage": "由於你尚未登入,你的殖民地將被刪除。確定要繼續嗎?",
+ "Permission": "權限",
+ "AllowMembers": "成員",
+ "AllowEveryone": "所有人",
+ "Send Money": "發送金錢",
+ "To": "發送給",
+ "Send": "發送",
+ "Top": "熱門",
+ "MineColony": "我的殖民地",
+ "AnonymousGameWarning": "[警告] 此殖民地將不會儲存。請登入以儲存進度。",
+ "ColonyFull": "此殖民地人數已達上限。",
+ "Save and Quit": "儲存並離開",
+ "Info": "資訊",
+ "Player.Banned": "你已被禁止進入殖民地。",
+ "No results": "無結果",
+ "Items": "物品",
+ "Manage": "管理",
+ "VendingStorage": "陳列",
+ "Purchase": "購買",
+ "VendingEmpty": "此自動販賣機目前是空的",
+ "Balance": "餘額",
+ "Deposit": "存款",
+ "Withdraw": "提款",
+ "Large Table": "大桌子",
+ "LargeTable.Description": "大型裝飾用桌子。",
+ "Food Vending Machine": "食物自動販賣機",
+ "Drinks Vending Machine": "飲料自動販賣機",
+ "Combat Armor": "戰鬥護甲",
+ "CombatArmor.Description": "使近戰更容易。減少4點傷害。",
+ "BoughtTooMuch": "你今天已經買了很多了。請明天再試。",
+ "Yellow Floor": "黃色地板",
+ "Bronze Floor": "青銅地板",
+ "Blue Floor": "藍色地板",
+ "Gray Floor": "灰色地板",
+ "Grass Floor": "草地地板",
+ "Pink Floor": "粉紅色地板",
+ "YellowFloor.Description": "黃色地板。",
+ "BronzeFloor.Description": "青銅地板。",
+ "BlueFloor.Description": "藍色地板。",
+ "GrayFloor.Description": "灰色地板。",
+ "GrassFloor.Description": "鋪有草皮的地板。",
+ "PinkFloor.Description": "粉紅色地板。",
+ "FoodVendingMachine.Description": "可陳列食物進行販售。",
+ "DrinksVendingMachine.Description": "可陳列飲料進行販售。",
+ "Atm.Description": "可從帳戶提領或存入Gold。",
+ "AdminRole": "你已被賦予管理員權限。",
+ "Coffee Seed": "咖啡種子",
+ "Sunflower Seed": "向日葵種子",
+ "Poppy Seed": "罌粟種子",
+ "Blue Seed": "藍蓮花種子",
+ "Coffee": "咖啡",
+ "Coffee.Description": "恢復50點體力。",
+ "Coffee Bean": "咖啡豆",
+ "CoffeeBean.Description": "沖泡咖啡的材料。",
+ "Coffee Plant": "咖啡樹",
+ "CoffeePlant.Description": "種在土壤中可長成咖啡樹。",
+ "Sunflower": "向日葵",
+ "Sunflower.Description": "可裝飾在盆栽裡的花。",
+ "Poppy": "罌粟花",
+ "Poppy.Description": "可裝飾在盆栽裡的花。",
+ "Blue Lotus": "藍蓮花",
+ "BlueLotus.Description": "可裝飾在盆栽裡的花。",
+ "SunflowerSeed.Description": "種在土壤中可長成向日葵。",
+ "BlueSeed.Description": "種在土壤中可長成藍蓮花。",
+ "PoppySeed.Description": "種在土壤中可長成罌粟花。",
+ "Flower Required": "需要花朵。",
+ "Ivory Wall": "象牙白牆壁",
+ "IvoryWall.Description": "白色牆壁。",
+ "Green Wall": "綠色牆壁",
+ "GreenWall.Description": "綠色牆壁。",
+ "Blue Wall": "藍色牆壁",
+ "BlueWall.Description": "藍色牆壁。",
+ "Favorite": "最愛",
+ "Favorites": "我的最愛",
+ "New": "新",
+ "You dont have permission to sell": "你沒有販售物品的權限。",
+ "Poppy Farm": "罌粟花田",
+ "BlueLotus Farm": "藍蓮花田",
+ "Sunflower Farm": "向日葵田",
+ "PlantFiber Farm": "纖維植物農田",
+ "CoffeeBean Farm": "咖啡豆農田",
+ "Wheat Farm": "小麥農田",
+ "Potato Farm": "馬鈴薯農田",
+ "Choose Crop to Grow": "選擇要種植的作物",
+ "ChooseCrop": "[E] 選擇作物",
+ "Slave Trader": "奴隸商人",
+ "Trade Slaves": "奴隸貿易",
+ "Nuu Slave": "努星人奴隸",
+ "Pixi Slave": "皮克西星人奴隸",
+ "Garam Slave": "加拉姆星人奴隸",
+ "Slavers Table": "奴隸貿易桌",
+ "SlaversTable.Description": "設置後可吸引奴隸商人前來。",
+ "Farm Controller": "農場控制器",
+ "FarmController.Description": "放置在土壤附近。可指示奴隸種植何種作物。",
+ "State": "狀態",
+ "Tasks": "任務",
+ "Plant Seed": "播種",
+ "Water Crop": "澆水",
+ "Harvest Crop": "收成",
+ "Butcher Corpse": "屠宰",
+ "Edit Sign": "編輯標誌牌",
+ "Access Storage": "存取儲存空間",
+ "Access Mining Drill": "存取採礦鑽機",
+ "Refill Turret": "補充砲塔彈藥",
+ "FarmControllerTip": "奴隸開始播種前,附近需要有種子箱。",
+ "Play": "遊玩",
+ "Walkthrough.movement": "按 w、a、s、d 鍵移動。",
+ "Walkthrough.mining": "前往Room2,點擊小行星採集 10 個 鐵礦石。",
+ "Walkthrough.lead_pipe": "按 C 鍵開啟製作選單,製作 鐵管。",
+ "Walkthrough.release_guard": "前往Room3,從 冷凍艙 釋放 守衛。將游標指向冷凍艙按 E 鍵釋放。注意守衛會攻擊你。",
+ "Walkthrough.kill_guard": "擊倒守衛。",
+ "Walkthrough.grab_potatoes": "前往Room4,從鐵箱取出 馬鈴薯。右鍵點擊開啟儲存箱",
+ "Walkthrough.eat_potatoes": "吃 馬鈴薯 充飢。",
+ "Walkthrough.player_permissions": "點擊Dummy Player,右上角會顯示權限選單。點擊顯示 [Guest] 的地方 來更改權限。",
+ "Walkthrough.dismantle": "裝備 生存工具,將游標指向鐵箱按住點擊,拆除 鐵箱。",
+ "Walkthrough.tame_slime": "餵食馬鈴薯 馴服 一隻史萊姆。裝備馬鈴薯(拿在手上)並接近史萊姆即可餵食。",
+ "Walkthrough.kill_slime": "擊倒另一隻史萊姆。",
+ "Walkthrough.butcher_slime": "將游標指向史萊姆屍體按 E 鍵可拾取。帶著史萊姆屍體前往 屠宰台 進行分解。",
+ "Walkthrough.cook_slime": "在 爐灶 將 凝膠 烹飪成 明膠。",
+ "Walkthrough.sleep": "前往下一個房間,在 床 上 睡覺 恢復體力。",
+ "Walkthrough.zoom": "按 - 或 = 鍵放大縮小",
+ "Walkthrough.suit_station": "從太空服站拿取 太空服。",
+ "Walkthrough.collect_water": "前往下一個房間。目前製氧機沒有水。使用水瓶可以為製氧機加水。從逃生艙取出 瓶子,從液體儲存槽取水。",
+ "Walkthrough.fill_oxygen_generator": "為 製氧機 加滿水。加滿水後房間應會充滿氧氣。",
+ "Walkthrough.refill_space_suit": "將游標指向製氧機按 E 鍵,也可為太空服補充氧氣。",
+ "Walkthrough.craft_iron_bar": "你也可以自動化氧氣供應過程。只需用液體管道連接液體儲存槽和製氧機。用 熔爐 製作一些 鐵錠。所需的鐵礦石在逃生艙內。",
+ "Walkthrough.craft_liquid_pipe": "按 C 鍵開啟製作選單,製作 液體管道。也可使用下方的搜尋欄搜尋「液體管道」。",
+ "Walkthrough.connect_liquid_pipe": "在製氧機和液體儲存槽之間放置 液體管道 進行連接。",
+ "Walkthrough.rail_tram": "恭喜!房間已充滿氧氣。離開房間,從 車站 搭乘 礦車 前往農場車站。",
+ "Walkthrough.plant_seed": "從逃生艙取出 小麥種子 種在土壤中。",
+ "Walkthrough.water_crop": "用瓶子從湖中取水,為種下的種子澆水。澆水可加速作物生長。",
+ "Walkthrough.farm_controller": "你可以透過指示奴隸來自動化農業。只需在土壤附近放置 農場控制器。從逃生艙取出農場控制器並放置。",
+ "Walkthrough.select_wheat": "將游標指向農場控制器開啟選單,選擇 小麥 作為要種植的作物。",
+ "Walkthrough.end": "只要奴隸能從附近的箱子取得種子,他們就會持續種植小麥。如果他們在附近箱子找到瓶子,也會為尚未澆水的作物澆水。",
+ "AskTutorialMessage": "你想先進行教學嗎?如果你是新手,強烈建議先透過教學了解遊戲基礎。",
+ "TutorialCreateNewColonyMessage": "恭喜完成教學!現在要建立你自己的太空基地嗎?",
+ "Unset": "未設定",
+ "Not enough Ingredients": "材料不足。",
+ "Slave": "奴隸",
+ "Dummy Player": "假人玩家",
+ "Partners": "合作夥伴",
+ "Permission Denied": "你沒有權限。",
+ "Local": "本地",
+ "Global": "全域",
+ "Guests are not allowed to do that": "訪客不被允許執行此操作。",
+ "Admin": "管理員",
+ "Account": "帳戶",
+ "Transfer": "轉帳",
+ "Amount": "金額",
+ "StoveSlaveNotice": "奴隸只會烹飪此種食物",
+ "Bread": "麵包",
+ "Bread.Description": "早晨的能量來源。",
+ "Targets": "目標設定",
+ "MobTarget": "敵對NPC",
+ "PlayerTarget": "敵對玩家",
+ "You dont have permission to mine asteroids": "你沒有採集小行星的權限",
+ "Team": "隊伍",
+ "Teams": "隊伍",
+ "Player": "玩家",
+ "requests to join your team": "請求加入你的隊伍",
+ "Upgrade": "升級",
+ "Request Sent": "請求已發送",
+ "Join Team": "加入隊伍",
+ "Accept": "接受",
+ "Reject": "拒絕",
+ "Join": "加入",
+ "Leave": "離開隊伍",
+ "Restart Character": "重置角色重新開始(*進度將遺失)",
+ "You just did that recently": "你最近才執行過此操作。",
+ "Preview": "預覽",
+ "FlamethrowerTurret": "火焰噴射砲塔",
+ "Delete Role": "刪除職位",
+ "New Role": "新增職位",
+ "RoleName": "職位名稱",
+ "Build": "建造",
+ "Mine Asteroids": "採集小行星",
+ "Plant Seeds": "播種",
+ "Harvest Crops": "收成農作物",
+ "Sell To Trader": "出售給商人",
+ "PeacefulModeDescription": "無需繳稅,也不會被襲擊。適合建造美麗基地或進行角色扮演。",
+ "SurvivalModeDescription": "不繳稅將導致帝國軍隊襲擊。適合喜歡適度風險和刺激的玩家。",
+ "HardcoreModeDescription": "帝國軍隊每天都會襲擊。每次襲擊敵人會變得更強。適合挑戰自己生存極限的玩家。",
+ "GameModeWarning": "注意:遊戲中途無法變更遊戲模式。",
+ "Choose a Game Mode": "選擇遊戲模式",
+ "Peaceful": "和平",
+ "Survival": "生存",
+ "Hardcore": "硬核",
+ "AcceptDecide": "選擇並應用模式",
+ "You must login to use this": "你必須登入才能使用此功能。",
+ "Fence": "圍欄",
+ "Fence.Description": "限制寵物、機器人和奴隸移動的門,但玩家可自由通過。",
+ "Mode": "難度",
+ "Deconstruct": "拆除",
+ "Collect Miner": "收集鑽機礦石",
+ "Deep Drill": "深層鑽機",
+ "DeepDrill.Description": "從油田地下開採珍貴資源。需要燃料和電力。",
+ "Flamethrower Turret": "火焰噴射砲塔",
+ "FlamethrowerTurret.Description": "向目標噴射火焰。啟動需要燃料和電力。",
+ "Missile Turret": "飛彈砲塔",
+ "MissileTurret.Description": "發射追蹤飛彈攻擊鎖定的目標。",
+ "Sulfur Ore": "硫磺礦石",
+ "SulfurOre.Description": "硫磺礦石",
+ "Explosives": "炸藥",
+ "Explosives.Description": "用於製作手榴彈和飛彈。",
+ "Missile": "飛彈",
+ "Ammunition for missile turret": "飛彈砲塔彈藥。",
+ "open": "開啟",
+ "close": "關閉",
+ "use": "使用",
+ "Generates Sulfur Ore": "正在生成硫磺礦石",
+ "MissileTurret.PlaceAmmunition": "請裝填飛彈。",
+ "Blue Energy Sword": "藍色能量劍",
+ "Red Energy Sword": "紅色能量劍",
+ "Green Energy Sword": "綠色能量劍",
+ "BlueEnergySword.Description": "由隕石礦鍛造而成,能產生特殊力量的劍。造成25點傷害。",
+ "RedEnergySword.Description": "由隕石礦鍛造而成,能產生特殊力量的劍。造成25點傷害。",
+ "GreenEnergySword.Description": "由隕石礦鍛造而成,能產生特殊力量的劍。造成25點傷害。",
+ "Vodka": "伏特加",
+ "Vodka.Description": "恢復40點體力。有了它,俄羅斯就是無敵的!",
+ "Meteorite": "隕石礦",
+ "Meteor Shower": "流星雨來襲",
+ "Miasma": "瘴氣",
+ "Forge": "鍛造爐",
+ "Forge.Description": "可製作武器和太空服。",
+ "Disinfectant": "消毒劑",
+ "Disinfectant.Description": "噴灑可清除瘴氣。",
+ "Wrench": "扳手",
+ "Wrench.Description": "可用於修理建築物。",
+ "Floor": "地板",
+ "Floor.Description": "可自訂的地板。雙擊可更改顏色和紋理。",
+ "Sky": "天空",
+ "Sky.Description": "天空",
+ "Water": "水",
+ "Water.Description": "水",
+ "Oil": "石油",
+ "Oil.Description": "石油",
+ "Rock": "地面",
+ "Rock.Description": "地面",
+ "Asteroid": "小行星",
+ "Asteroid.Description": "小行星",
+ "Copper Asteroid": "銅礦",
+ "CopperAsteroid.Description": "銅礦",
+ "Meteorite Asteroid": "隕石礦",
+ "MeteoriteAsteroid.Description": "隕石礦",
+ "Report Player": "舉報玩家",
+ "ReportPlayer.Description": "如果你認為有玩家違反了 %{rules} 中列出的規則,請在此舉報。如有可能,請附上螢幕截圖。無故舉報屬違規行為,可能導致你被禁止。",
+ "Offending user": "違規用戶名稱",
+ "Description": "描述",
+ "Attach screenshot": "附上螢幕截圖",
+ "Assassins Knife": "刺客匕首",
+ "AssassinsKnife.Description": "偽裝成通訊器的暗殺匕首。",
+ "Radio": "通訊器",
+ "Radio.Description": "通訊器。發現屍體時請用它回報。",
+ "Bar": "酒吧",
+ "Engine Room": "引擎室",
+ "Chemistry Lab": "化學實驗室",
+ "ExitCamera": "返回",
+ "Camera": "攝影機",
+ "Invite": "邀請",
+ "NeededToStart": "需要 %{count} 名玩家才能開始遊戲。",
+ "armory": "軍械庫",
+ "incinerator": "焚化爐",
+ "Medbay": "醫療室",
+ "medbay": "醫療室",
+ "security": "保全室",
+ "oxygen_supply": "氧氣供應室",
+ "prison": "監獄",
+ "corpses": "停屍間",
+ "chemistry": "化學實驗室",
+ "meeting": "會議室",
+ "biology": "生物實驗室",
+ "engine": "引擎室",
+ "bar": "酒吧",
+ "kitchen": "廚房",
+ "garden": "農業區",
+ "No player targets available": "附近沒有可攻擊的目標玩家。",
+ "Voted": "已投票",
+ "Vote": "投票",
+ "votes": "票",
+ "Skip Vote": "跳過投票",
+ "Vote the Imposter": "投票給你認為是冒名頂替者的人",
+ "Play Again": "再玩一次",
+ "Spectate": "觀戰",
+ "Total Tasks Completed": "任務完成率",
+ "Host Private Game": "開設私人遊戲",
+ "FindTheImposter.Title": "尋找冒名頂替者",
+ "FindTheImposter.Description": "完成所有任務或投票淘汰冒名頂替者即可獲勝。殺死所有船員則冒名頂替者獲勝。",
+ "CanUseAgain": "%{cooldown} 秒後可再次使用",
+ "No corpses nearby": "附近沒有屍體。",
+ "Meeting Room": "會議室",
+ "Spawn Area": "重生區",
+ "Oxygen Supply Room": "氧氣供應室",
+ "Power Switch": "電源開關",
+ "PowerSwitch.Description": "可控制多個電源。可開啟或關閉。",
+ "Terminal": "終端機",
+ "Terminal.Description": "裝飾用電腦,可使用。",
+ "Underground Vent": "通風管道",
+ "UndergroundVent.Description": "通風管道。可瞬間移動到另一個管道口。",
+ "Lava": "熔岩",
+ "Security Camera": "監視攝影機",
+ "SecurityCamera.Description": "可監視特定區域。",
+ "Security Monitor": "監視螢幕",
+ "SecurityMonitor.Description": "可查看監視攝影機的畫面。",
+ "Wall Lamp": "壁燈",
+ "WallLamp.Description": "照亮周圍環境。",
+ "Custom": "自訂顏色",
+ "Intensity": "亮度",
+ "Wood Table": "木桌",
+ "WoodTable.Description": "木製桌子。",
+ "Wood Chair": "木椅",
+ "WoodChair.Description": "木製椅子。",
+ "Domination.Title": "領土爭奪",
+ "Domination.Description": "與其他玩家組隊,摧毀敵方基地。",
+ "Command Blocks": "指令方塊",
+ "Triggers": "觸發器",
+ "Logs": "日誌",
+ "Functions": "函數",
+ "Function": "函數",
+ "Enable Triggers": "啟用觸發器",
+ "New Trigger": "新增觸發器",
+ "Create": "建立",
+ "Usage": "用法",
+ "Function.Usage1": "函數呼叫用法如下:",
+ "Function.Usage2": "基本上,它會根據輸入值(例如玩家名稱)輸出一個值(例如該玩家的Gold數量)。函數可用於指令或ifelse條件。",
+ "Arguments": "參數",
+ "Returns": "回傳值",
+ "Tesla Coil": "特斯拉線圈",
+ "TeslaCoil.Description": "向最多4個目標發射閃電。啟動需要電力。",
+ "Bomber Turret": "轟炸砲塔",
+ "BomberTurret.Description": "向目標發射炸彈。啟動需要電力和炸彈。",
+ "Scar17.Description": "發射高速子彈的點射武器。",
+ "Plasma Gun": "電漿槍",
+ "PlasmaGun.Description": "發射電漿彈的武器。",
+ "Plasma Cell": "電漿彈",
+ "PlasmaCell.Description": "電漿槍彈藥。",
+ "Rocket Launcher": "火箭發射器",
+ "RocketLauncher.Description": "向目標發射飛彈的武器。",
+ "Squid Staff": "魔法杖",
+ "SquidStaff.Discription": "一次發射5發能量彈的武器。",
+ "Tree": "樹木",
+ "Tree.Description": "裝飾用樹木。",
+ "Christmas Tree": "聖誕樹",
+ "ChristmasTree.Description": "聖誕樹。",
+ "Red Present": "紅色禮物",
+ "RedPresent.Description": "紅色禮物盒。",
+ "Blue Present": "藍色禮物",
+ "BluePresent.Description": "藍色禮物盒。",
+ "Green Present": "綠色禮物",
+ "GreenPresent.Description": "綠色禮物盒。",
+ "Pocket Trader": "隨身貿易商",
+ "PocketTrader.Description": "使用後可在貿易商不在附近時進行交易。",
+ "Bowl": "碗",
+ "Bowl.Description": "可用來製作湯品。",
+ "Omelette": "歐姆蛋",
+ "Fries": "薯條",
+ "Potato Soup": "馬鈴薯湯",
+ "Slime Broth": "史萊姆湯",
+ "Miso Soup": "味噌湯",
+ "Energy Drink": "能量飲料",
+ "EnergyDrink.Description": "飲用後暫時提高移動速度。",
+ "Alien Juice": "外星果汁",
+ "AlienJuice.Description": "飲用後暫時隱形。",
+ "Timer Bomb": "定時炸彈",
+ "TimerBomb.Description": "設置後將在10秒後爆炸。",
+ "Hazmat Suit": "防護衣",
+ "HazmatSuit.Description": "保護你免受瘴氣和毒素傷害",
+ "Prisoner Suit": "囚犯服",
+ "PrisonerSuit.Description": "囚犯服裝。",
+ "Police Suit": "警察制服",
+ "PoliceSuit.Description": "警察制服。",
+ "Lab Coat": "實驗袍",
+ "LabCoat.Description": "研究人員服裝。",
+ "Cultist Suit": "邪教徒服裝",
+ "CultistSuit.Description": "邪教領袖服裝。",
+ "Santa Hat": "聖誕帽",
+ "Name Tag": "命名牌",
+ "Missile.Description": "飛彈砲塔和火箭發射器使用的彈藥。",
+ "Events.Alarm": "修理燈泡"
+}
\ No newline at end of file
diff --git a/packages/junon-io/server/commands/fly.js b/packages/junon-io/server/commands/fly.js
index fee8f1cf..21a5f7d5 100644
--- a/packages/junon-io/server/commands/fly.js
+++ b/packages/junon-io/server/commands/fly.js
@@ -6,7 +6,9 @@ class Fly extends BaseCommand {
getUsage() {
return [
"/fly",
- "/fly [player]"
+ "/fly [on|off]",
+ "/fly [player]",
+ "/fly [player] [on|off]"
]
}
@@ -19,21 +21,37 @@ class Fly extends BaseCommand {
}
perform(player, args) {
- const username = args[0]
-
- let targetPlayers = this.getPlayersBySelector(username)
- if (targetPlayers.length === 0) {
- if (player.isPlayer()) {
- player.toggleFly()
+ let state = null
+ if (args.length > 0) {
+ const lastArg = args[args.length - 1].toLowerCase()
+ if (lastArg === "on" || lastArg === "off") {
+ state = lastArg
+ args = args.slice(0, -1)
}
+ }
+
+ const selector = args[0]
+ let targetPlayers = this.getPlayersBySelector(selector)
+
+ if (targetPlayers.length === 0 && player.isPlayer()) {
+ targetPlayers = [player]
+ }
+
+ if (targetPlayers.length === 0 && selector) {
+ player.showChatError("no players found")
return
}
- targetPlayers.forEach((targetPlayer) => {
- targetPlayer.toggleFly()
+ targetPlayers.forEach(targetPlayer => {
+ const shouldEnable = state === null ? !targetPlayer.isFlying : (state === "on")
+ this.setFly(targetPlayer, shouldEnable)
})
}
+ setFly(player, enabled) {
+ if (player.isFlying === enabled) return
+ player.toggleFly()
+ }
}
module.exports = Fly
\ No newline at end of file
diff --git a/packages/junon-io/server/commands/god.js b/packages/junon-io/server/commands/god.js
index 58c69eee..123babb3 100644
--- a/packages/junon-io/server/commands/god.js
+++ b/packages/junon-io/server/commands/god.js
@@ -6,7 +6,9 @@ class God extends BaseCommand {
getUsage() {
return [
"/god",
- "/god [player]"
+ "/god [on|off]",
+ "/god [player]",
+ "/god [player] [on|off]"
]
}
@@ -20,31 +22,37 @@ class God extends BaseCommand {
perform(caller, args) {
- const selector = args[0]
- if (selector) {
- if (!caller.isSectorOwner()) {
- caller.showChatError("permission denied")
- return
+ let state = null
+ if (args.length > 0) {
+ const lastArg = args[args.length - 1].toLowerCase()
+ if (lastArg === "on" || lastArg === "off") {
+ state = lastArg
+ args = args.slice(0, -1)
}
+ }
+
+ const selector = args[0]
+ let targetPlayers = this.getPlayersBySelector(selector)
- let targetPlayers = this.getPlayersBySelector(selector)
- if (targetPlayers.length === 0) {
+ if (targetPlayers.length === 0 && caller.isPlayer()) {
+ targetPlayers = [caller]
+ }
+
+ if (targetPlayers.length === 0 && selector) {
caller.showChatError("no players found")
return
- }
-
- targetPlayers.forEach((player) => {
- this.toggleGod(player)
- })
- } else {
- if (caller.isPlayer()) {
- this.toggleGod(caller)
- }
}
+
+ targetPlayers.forEach(targetPlayer => {
+ const shouldEnable = state === null ? !targetPlayer.godMode : (state === "on")
+ this.setGod(targetPlayer, shouldEnable)
+ })
}
- toggleGod(player) {
- player.godMode = !player.godMode
+ setGod(player, enabled) {
+ if (player.godMode === enabled) return
+
+ player.godMode = enabled
if (player.godMode) {
player.setHealth(player.getMaxHealth())
}
@@ -53,9 +61,4 @@ class God extends BaseCommand {
}
}
-module.exports = God
-
-
-
-
-
+module.exports = God
\ No newline at end of file
diff --git a/packages/junon-io/server/commands/projectile.js b/packages/junon-io/server/commands/projectile.js
index e565b92a..977583de 100644
--- a/packages/junon-io/server/commands/projectile.js
+++ b/packages/junon-io/server/commands/projectile.js
@@ -23,9 +23,23 @@ class Projectile extends BaseCommand {
const type = args[0]
const row = parseInt(args[1])
const col = parseInt(args[2])
- let keyValueArgs = isNaN(row) ? args.slice(3) : args.slice(1)
+ let keyValueArgs = isNaN(row) ? args.slice(1) : args.slice(3)
let keyValueMap = this.convertKeyValueArgsToObj(keyValueArgs)
+ let owner = caller
+
+ if (keyValueMap.owner) {
+ const entityId = parseInt(keyValueMap.owner, 10)
+
+ if (!isNaN(entityId)) {
+ const foundOwner = this.game.getEntity(entityId)
+
+ if (foundOwner) {
+ owner = foundOwner
+ }
+ }
+ }
+
let x, y
if (col) {
@@ -42,6 +56,12 @@ class Projectile extends BaseCommand {
if (typeof x === 'undefined' || typeof y === 'undefined') return
+ const baseSpawnConfig = {
+ owner: owner,
+ type: type,
+ keyValueMap: keyValueMap
+ };
+
if (keyValueMap["scatter"]) {
let scatterCount = 10
let rowSpread = 8
@@ -50,10 +70,10 @@ class Projectile extends BaseCommand {
let deltaCol = rowSpread - Math.floor(Math.random() * rowSpread) * 2
let otherX = x + deltaCol * Constants.tileSize + Constants.tileSize/2
let otherY = y + deltaRow * Constants.tileSize + Constants.tileSize/2
- this.sector.spawnProjectile({ caller: caller, type: type, x: otherX, y: otherY, keyValueMap: keyValueMap })
+ this.sector.spawnProjectile({ ...baseSpawnConfig, caller: caller, x: otherX, y: otherY })
}
} else {
- this.sector.spawnProjectile({ caller: caller, type: type, x: x, y: y, keyValueMap: keyValueMap })
+ this.sector.spawnProjectile({ ...baseSpawnConfig, caller: caller, x: x, y: y })
}
}
diff --git a/packages/junon-io/server/commands/spawn_mob.js b/packages/junon-io/server/commands/spawn_mob.js
index 67572684..c5f9146e 100644
--- a/packages/junon-io/server/commands/spawn_mob.js
+++ b/packages/junon-io/server/commands/spawn_mob.js
@@ -129,7 +129,47 @@ class SpawnMob extends BaseCommand {
}
}
+ if (keyValueMap["raid"] === "true") {
+ if (!keyValueMap["attackables"]) {
+ data.attackables = ["players", "mobs", "buildings"]
+ }
+ if (!keyValueMap["status"]) {
+ let status = Protocol.definition().MobStatus.Hostile
+ if (typeof status !== 'undefined' && status !== null) {
+ data.status = status
+ }
+ }
+ }
+
const mobs = this.sector.spawnMob(data)
+
+ if (keyValueMap["raid"] === "true") {
+ const Raid = require("../entities/raid")
+ const eventManager = this.game.eventManager
+ let permanentRaid = null
+
+ if (eventManager.raids && Array.isArray(eventManager.raids)) {
+ permanentRaid = eventManager.raids.find(raid => raid.data && raid.data.permanent === true)
+ }
+
+ if (!permanentRaid) {
+ const raidData = {
+ sector: this.sector,
+ team: caller.getTeam ? caller.getTeam() : undefined,
+ permanent: true
+ }
+ permanentRaid = new Raid(eventManager, raidData)
+ permanentRaid.prepare()
+ }
+
+ if (permanentRaid) {
+ mobs.forEach((mob) => {
+ mob.setRaid(permanentRaid)
+ permanentRaid.addToMobGroup(mob)
+ })
+ }
+ }
+
mobs.forEach((mob) => {
mob.onCommandSpawned(caller)
@@ -142,7 +182,7 @@ class SpawnMob extends BaseCommand {
}
})
- if (mobs.length > 1) {
+ if (mobs.length > 1 && keyValueMap["raid"] !== "true") {
let entityGroup = new EntityGroup()
mobs.forEach((mob) => {
entityGroup.addChild(mob)
@@ -151,4 +191,4 @@ class SpawnMob extends BaseCommand {
}
}
-module.exports = SpawnMob
+module.exports = SpawnMob
\ No newline at end of file
diff --git a/packages/junon-io/server/commands/spectate.js b/packages/junon-io/server/commands/spectate.js
index 38ad31a0..deff7c00 100644
--- a/packages/junon-io/server/commands/spectate.js
+++ b/packages/junon-io/server/commands/spectate.js
@@ -6,7 +6,9 @@ class Spectate extends BaseCommand {
getUsage() {
return [
"/spectate",
- "/spectate [player]"
+ "/spectate [on|off]",
+ "/spectate [player]",
+ "/spectate [player] [on|off]"
]
}
@@ -19,43 +21,58 @@ class Spectate extends BaseCommand {
}
perform(caller, args) {
+ let state = null
+ if (args.length > 0) {
+ const lastArg = args[args.length - 1].toLowerCase()
+ if (lastArg === "on" || lastArg === "off") {
+ state = lastArg
+ args = args.slice(0, -1)
+ }
+ }
+
const selector = args[0]
+
if (selector) {
- if (!caller.isSectorOwner()) {
- caller.showChatError("permission denied")
+ if (!caller.isAdminMode && !this.game.isPeaceful()) {
return
}
- let targetPlayers = this.getPlayersBySelector(selector)
- if (targetPlayers.length === 0) {
- caller.showChatError("no players found")
+ const hasPermission = caller.isAdminMode || caller.isSectorOwner() || caller.hasCommandsPermission()
+ if (!hasPermission) {
return
}
+ }
- targetPlayers.forEach((player) => {
- this.toggleSpectate(player)
- })
- } else {
- if (caller.isPlayer()) {
- this.toggleSpectate(caller)
- }
+ let targetPlayers = this.getPlayersBySelector(selector)
+
+ if (targetPlayers.length === 0 && caller.isPlayer()) {
+ targetPlayers = [caller]
}
-
+
+ if (targetPlayers.length === 0 && selector) {
+ caller.showChatError("no players found")
+ return
+ }
+
+ targetPlayers.forEach(targetPlayer => {
+ const isSpectating = !!targetPlayer.ghost
+ const shouldEnable = state === null ? !isSpectating : (state === "on")
+ this.setSpectate(targetPlayer, shouldEnable)
+ })
}
- toggleSpectate(player) {
- if (player.ghost) {
- player.possess(player)
- player.showChatSuccess("spectate mode: OFF")
- } else {
+ setSpectate(player, enabled) {
+ const isSpectating = !!player.ghost
+ if (isSpectating === enabled) return
+
+ if (enabled) {
player.transformIntoGhost()
player.showChatSuccess("spectate mode: ON")
+ } else {
+ player.possess(player)
+ player.showChatSuccess("spectate mode: OFF")
}
}
}
-module.exports = Spectate
-
-
-
-
+module.exports = Spectate
\ No newline at end of file
diff --git a/packages/junon-io/server/entities/base_entity.js b/packages/junon-io/server/entities/base_entity.js
index 2af70ec3..88dbec64 100644
--- a/packages/junon-io/server/entities/base_entity.js
+++ b/packages/junon-io/server/entities/base_entity.js
@@ -1737,22 +1737,29 @@ class BaseEntity extends BaseTransientEntity {
}
getKillerFromAttacker(attacker) {
- if (attacker.isMob()) return attacker
- if (attacker.isPlayer()) return attacker
- if (typeof attacker.isProjectile !== 'function') return null
-
- if (attacker.isProjectile()) {
- if (attacker.weapon) {
- if (attacker.weapon.isBuilding()) {
- return attacker.weapon.placer
- } else {
- // weapon equipment
- return attacker.owner
- }
+ if (attacker.isMob && attacker.isMob() || attacker.isPlayer && attacker.isPlayer()) {
+ return attacker;
+ }
+
+ if (attacker.isBuilding && attacker.isBuilding()) {
+ return attacker;
+ }
+
+ if (attacker.isProjectile && attacker.isProjectile()) {
+ const shooter = attacker.sourceEntity;
+
+ if (shooter) {
+ if (shooter.isBuilding && shooter.isBuilding()) {
+ return shooter;
+ } else {
+ return shooter;
}
}
}
+ return null;
+}
+
getDamage() {
return this.getStats(this.level).damage
}
diff --git a/packages/junon-io/server/entities/buildings/base_building.js b/packages/junon-io/server/entities/buildings/base_building.js
index 60f6e89f..6f71bf4f 100644
--- a/packages/junon-io/server/entities/buildings/base_building.js
+++ b/packages/junon-io/server/entities/buildings/base_building.js
@@ -1930,6 +1930,24 @@ Object.assign(BaseBuilding.prototype, Upgradable.prototype, {
})
Object.assign(BaseBuilding.prototype, Destroyable.prototype, {
+ damage(amount, attacker) {
+ if (!this.isDestroyable() || this.godMode) return;
+ if (attacker && attacker.isCollidable && !this.isCollidable(attacker)) return;
+ amount -= this.getDamageResistance(amount, attacker);
+ if (amount <= 0) return;
+ this.reduceHealth(amount);
+ this.onDamaged(attacker, amount);
+ },
+
+ getDamageResistance(amount, attacker) {
+ const isMeleeAttack = attacker && typeof attacker.hasMeleeWeapon === 'function' && attacker.hasMeleeWeapon();
+ if (this.hasCategory("melee_resistant") && isMeleeAttack) {
+ return Math.floor(amount * 0.5);
+ }
+
+ return 0;
+},
+
onDamaged(attacker, amount) {
let data = {
entityId: this.getId(),
diff --git a/packages/junon-io/server/entities/buildings/blue_present.js b/packages/junon-io/server/entities/buildings/blue_present.js
index 9d994bc5..0f0b01c6 100644
--- a/packages/junon-io/server/entities/buildings/blue_present.js
+++ b/packages/junon-io/server/entities/buildings/blue_present.js
@@ -11,11 +11,6 @@ class BluePresent extends BaseBuilding {
getType() {
return Protocol.definition().BuildingType.BluePresent
}
-
- isCollidable() {
- return false
- }
-
}
module.exports = BluePresent
diff --git a/packages/junon-io/server/entities/buildings/chair.js b/packages/junon-io/server/entities/buildings/chair.js
index ead378a0..37803110 100644
--- a/packages/junon-io/server/entities/buildings/chair.js
+++ b/packages/junon-io/server/entities/buildings/chair.js
@@ -11,11 +11,6 @@ class Chair extends BaseBuilding {
getType() {
return Protocol.definition().BuildingType.Chair
}
-
- isCollidable() {
- return false
- }
-
}
module.exports = Chair
diff --git a/packages/junon-io/server/entities/buildings/green_present.js b/packages/junon-io/server/entities/buildings/green_present.js
index 94d92b63..3b179ab6 100644
--- a/packages/junon-io/server/entities/buildings/green_present.js
+++ b/packages/junon-io/server/entities/buildings/green_present.js
@@ -11,11 +11,6 @@ class GreenPresent extends BaseBuilding {
getType() {
return Protocol.definition().BuildingType.GreenPresent
}
-
- isCollidable() {
- return false
- }
-
}
module.exports = GreenPresent
diff --git a/packages/junon-io/server/entities/buildings/lamp.js b/packages/junon-io/server/entities/buildings/lamp.js
index b7cfcc43..0b0911a5 100644
--- a/packages/junon-io/server/entities/buildings/lamp.js
+++ b/packages/junon-io/server/entities/buildings/lamp.js
@@ -49,11 +49,6 @@ class Lamp extends BaseBuilding {
getType() {
return Protocol.definition().BuildingType.Lamp
}
-
- isCollidable() {
- return false
- }
-
}
diff --git a/packages/junon-io/server/entities/buildings/rail_stop.js b/packages/junon-io/server/entities/buildings/rail_stop.js
index fe04a346..8659ca16 100644
--- a/packages/junon-io/server/entities/buildings/rail_stop.js
+++ b/packages/junon-io/server/entities/buildings/rail_stop.js
@@ -261,10 +261,6 @@ class RailStop extends BaseBuilding {
super.breakBuilding(lastBreaker)
}
- isCollidable() {
- return false
- }
-
shouldObstruct(body, hit) {
let box = this.getCollisionBox()
diff --git a/packages/junon-io/server/entities/buildings/red_present.js b/packages/junon-io/server/entities/buildings/red_present.js
index cf48073f..d0f6bbb6 100644
--- a/packages/junon-io/server/entities/buildings/red_present.js
+++ b/packages/junon-io/server/entities/buildings/red_present.js
@@ -11,11 +11,6 @@ class RedPresent extends BaseBuilding {
getType() {
return Protocol.definition().BuildingType.RedPresent
}
-
- isCollidable() {
- return false
- }
-
}
module.exports = RedPresent
diff --git a/packages/junon-io/server/entities/buildings/sign.js b/packages/junon-io/server/entities/buildings/sign.js
index 3ae144f9..8a9ae30f 100644
--- a/packages/junon-io/server/entities/buildings/sign.js
+++ b/packages/junon-io/server/entities/buildings/sign.js
@@ -17,11 +17,6 @@ class Sign extends BaseBuilding {
getType() {
return Protocol.definition().BuildingType.Sign
}
-
- isCollidable() {
- return false
- }
-
}
module.exports = Sign
diff --git a/packages/junon-io/server/entities/equipments/hand/hand_equipment.js b/packages/junon-io/server/entities/equipments/hand/hand_equipment.js
index 7555b1dd..c99599c6 100644
--- a/packages/junon-io/server/entities/equipments/hand/hand_equipment.js
+++ b/packages/junon-io/server/entities/equipments/hand/hand_equipment.js
@@ -34,21 +34,12 @@ class HandEquipment extends BaseEquipment {
}
getDamage(targetEntity) {
- let baseDamage = this.getEquipmentDamage()
- if (!this.game.isMiniGame() &&
- targetEntity &&
- targetEntity.hasCategory("melee_resistant")) {
- baseDamage = 2
- }
-
- if (!this.owner) return baseDamage
-
- if (this.owner.isMob() || this.owner.isPlayer()) {
- return Math.floor(this.owner.getDamageMultiplier() * baseDamage)
- } else {
- return baseDamage
- }
+ const customStats = this.sector.entityCustomStats[this.item.id];
+ if (customStats && typeof customStats.damage !== 'undefined') {
+ return customStats.damage;
}
+ return this.getEquipmentDamage() || 0;
+}
getEquipmentDamage() {
if (this.sector) {
diff --git a/packages/junon-io/server/entities/equipments/hand/melee_equipment.js b/packages/junon-io/server/entities/equipments/hand/melee_equipment.js
index b72e1838..f85cf371 100644
--- a/packages/junon-io/server/entities/equipments/hand/melee_equipment.js
+++ b/packages/junon-io/server/entities/equipments/hand/melee_equipment.js
@@ -40,7 +40,13 @@ class MeleeEquipment extends HandEquipment {
success = true
} else {
- let target = user.getMeleeTarget(this.getMeleeRange(), meleeTargetOptions) //this.getMeleeTarget(user)
+ let target
+ if (user.isMob && user.isMob()) {
+ target = targetEntity
+ } else {
+ target = user.getMeleeTarget(this.getMeleeRange(), meleeTargetOptions)
+ }
+
success = this.useOnTarget(user, target)
}
@@ -86,20 +92,21 @@ class MeleeEquipment extends HandEquipment {
}
useOnTarget(user, target) {
- const damage = this.getDamage(target)
- if (target) {
- target.damage(damage, user, this)
- if (user.isPlayer()) {
- }
-
- if (this.canStunEnemy()) {
- this.applyStun(target)
- }
+ const damage = user.getDamage(target);
+ if (target) {
+ target.damage(damage, user, this);
+
+ if (user.isPlayer()) {
}
- return true
+ if (this.canStunEnemy()) {
+ this.applyStun(target);
+ }
}
+ return true;
+}
+
applyStun(target) {
let knockChance = 1 //0.15
if (Math.random() < knockChance) {
diff --git a/packages/junon-io/server/entities/event_handler.js b/packages/junon-io/server/entities/event_handler.js
index bbc5e4c3..3d468ee3 100644
--- a/packages/junon-io/server/entities/event_handler.js
+++ b/packages/junon-io/server/entities/event_handler.js
@@ -226,6 +226,56 @@ class EventHandler {
return Math.max(...values.map(v => this._safeNumber(v)))
}
+ sin(degrees) {
+ const deg = this._safeNumber(degrees)
+ return this._fixFloat(Math.sin(deg * Math.PI / 180))
+ }
+
+ cos(degrees) {
+ const deg = this._safeNumber(degrees)
+ return this._fixFloat(Math.cos(deg * Math.PI / 180))
+ }
+
+ tan(degrees) {
+ const deg = this._safeNumber(degrees)
+ if (Math.abs(deg % 180) === 90) return NaN
+ return this._fixFloat(Math.tan(deg * Math.PI / 180))
+ }
+
+ asin(value) {
+ const num = this._safeNumber(value)
+ if (num < -1 || num > 1) return NaN
+ return this._fixFloat(Math.asin(num) * 180 / Math.PI)
+ }
+
+ acos(value) {
+ const num = this._safeNumber(value)
+ if (num < -1 || num > 1) return NaN
+ return this._fixFloat(Math.acos(num) * 180 / Math.PI)
+ }
+
+ atan(value) {
+ const num = this._safeNumber(value)
+ return this._fixFloat(Math.atan(num) * 180 / Math.PI)
+ }
+
+ getAngle(entityId) {
+ let player = this.getPlayer(entityId)
+ let angle = 0
+
+ if (player && typeof player.angle === 'number') {
+ angle = player.angle
+ } else {
+ let entity = this.game.getEntity(entityId)
+ if (entity && typeof entity.angle === 'number') {
+ angle = entity.angle
+ }
+ }
+
+ angle = ((angle % 360) + 360) % 360
+ return angle
+ }
+
length(value) {
return value.toString().length
}
@@ -750,17 +800,34 @@ class EventHandler {
getBuildingType(entityId) {
let entity = this.game.getEntity(entityId)
-
+
if (!entity) return ""
-
+
+ if (typeof entity.getTypeName === 'function') {
+ return entity.getTypeName()
+ }
+
+ return entity.type || ""
+ }
+
+ getEntityType(entityId) {
+ let player = this.game.getPlayerByName(entityId)
+ if (player) {
+ return "Player"
+ }
+
+ let entity = this.game.getEntity(entityId)
+
+ if (!entity) return ""
+
if (entity.isPlayer()) {
return "Player"
}
-
+
if (typeof entity.getTypeName === 'function') {
return entity.getTypeName()
}
-
+
return entity.type || ""
}
@@ -1199,7 +1266,14 @@ class EventHandler {
"$getStructureByCoords": true,
"$hasEffect": true,
"$getTotalMobCount": true,
- "$getAngle": true
+ "$getAngle": true,
+ "$sin": true,
+ "$cos": true,
+ "$tan": true,
+ "$asin": true,
+ "$acos": true,
+ "$atan": true,
+ "$getEntityType" : true,
}
}
diff --git a/packages/junon-io/server/entities/mobs/base_mob.js b/packages/junon-io/server/entities/mobs/base_mob.js
index d4cbc13a..0f4e029e 100644
--- a/packages/junon-io/server/entities/mobs/base_mob.js
+++ b/packages/junon-io/server/entities/mobs/base_mob.js
@@ -2147,6 +2147,37 @@ class BaseMob extends BaseEntity {
return false
}
+ getWeapon() {
+ if (this.equipments && typeof this.equipments.get === 'function') {
+ const item = this.equipments.get(Protocol.definition().EquipmentRole.Hand);
+ return item ? item.instance : null;
+ }
+ return null;
+}
+
+getDamage(attackTarget) {
+ const weapon = this.getWeapon();
+ let totalDamage;
+ if (weapon) {
+ const baseDamage = super.getDamage();
+ const weaponDamage = weapon.getDamage(attackTarget);
+ totalDamage = baseDamage + weaponDamage;
+ } else {
+ totalDamage = super.getDamage();
+ }
+
+ if (this.sector) {
+ if (this.sector.entityCustomStats[this.id] && typeof this.sector.entityCustomStats[this.id].damage !== 'undefined') {
+ totalDamage = this.sector.entityCustomStats[this.id].damage;
+ } else if (this.sector.mobCustomStats[this.type] && typeof this.sector.mobCustomStats[this.type].damage !== 'undefined') {
+ totalDamage = this.sector.mobCustomStats[this.type].damage;
+ }
+ }
+
+ const damageMultiplier = this.getDamageMultiplier(attackTarget);
+ return Math.floor(totalDamage * damageMultiplier);
+}
+
getRotatedAngle() {
return this.angle
}
@@ -2162,21 +2193,28 @@ class BaseMob extends BaseEntity {
}
getDamage(attackTarget) {
- let damage = super.getDamage()
+ const weapon = this.getWeapon();
+ let totalDamage;
+ if (weapon) {
+ const baseDamage = super.getDamage();
+ const weaponDamage = weapon.getDamage(attackTarget);
+ totalDamage = baseDamage + weaponDamage;
+ } else {
+ totalDamage = super.getDamage();
+ }
- if (this.sector) {
- if (this.sector.entityCustomStats[this.id]) {
- damage = this.sector.entityCustomStats[this.id].damage
- } else if (this.sector.mobCustomStats[this.type]) {
- damage = this.sector.mobCustomStats[this.type].damage
- }
+ if (this.sector) {
+ if (this.sector.entityCustomStats[this.id] && typeof this.sector.entityCustomStats[this.id].damage !== 'undefined') {
+ totalDamage = this.sector.entityCustomStats[this.id].damage;
+ } else if (this.sector.mobCustomStats[this.type] && typeof this.sector.mobCustomStats[this.type].damage !== 'undefined') {
+ totalDamage = this.sector.mobCustomStats[this.type].damage;
}
-
- let damageMultiplier = this.getDamageMultiplier(attackTarget)
-
- return Math.floor(damageMultiplier * damage)
}
+ const damageMultiplier = this.getDamageMultiplier(attackTarget);
+ return Math.floor(damageMultiplier * totalDamage);
+}
+
getResistance() {
return this.getConstants().resistance || []
}
@@ -2278,13 +2316,24 @@ Object.assign(BaseMob.prototype, Movable.prototype, {
Object.assign(BaseMob.prototype, Destroyable.prototype, {
- getDamageResistance(amount, attackEntity) {
- if (attackEntity.hasCategory("fire") && this.isResistantTo("fire")) {
- return Math.floor(amount / 2)
- }
+ damage(amount, attacker) {
+ if (!this.isDestroyable() || this.godMode || this.isInvincible) return;
+ amount -= this.getDamageResistance(amount, attacker);
+ if (amount <= 0) return;
+ this.reduceHealth(amount);
+ this.onDamaged(attacker, amount);
+},
+
+ getDamageResistance(amount, attacker) {
+ const isMeleeAttack = attacker && typeof attacker.hasMeleeWeapon === 'function' && attacker.hasMeleeWeapon();
+
+ if (this.hasCategory("melee_resistant") && isMeleeAttack) {
+ return Math.floor(amount * 0.5);
+ }
+
+ return 0;
+},
- return 0
- },
onDamaged(attacker, amount) {
this.attackerId = attacker.id
diff --git a/packages/junon-io/server/entities/mobs/chemist.js b/packages/junon-io/server/entities/mobs/chemist.js
index 1a290f6d..9ccec041 100644
--- a/packages/junon-io/server/entities/mobs/chemist.js
+++ b/packages/junon-io/server/entities/mobs/chemist.js
@@ -150,6 +150,10 @@ class Chemist extends LandMob {
}
getWeapon() {
+ if (!this.equipments) {
+ return null;
+ }
+
let item = this.equipments.get(Protocol.definition().EquipmentRole.Hand)
if (!item) return null
return item.instance || item.getKlass(item.type)
diff --git a/packages/junon-io/server/entities/mobs/firebat.js b/packages/junon-io/server/entities/mobs/firebat.js
index 1e829543..3fee84bb 100644
--- a/packages/junon-io/server/entities/mobs/firebat.js
+++ b/packages/junon-io/server/entities/mobs/firebat.js
@@ -124,11 +124,15 @@ class Firebat extends LandMob {
}
getWeapon() {
- let item = this.equipments.get(Protocol.definition().EquipmentRole.Hand)
- if (!item) return null
- return item.instance || item.getKlass(item.type)
+ if (!this.equipments) {
+ return null;
}
+ let item = this.equipments.get(Protocol.definition().EquipmentRole.Hand)
+ if (!item) return null
+ return item.instance || item.getKlass(item.type)
+}
+
remove() {
super.remove()
diff --git a/packages/junon-io/server/entities/mobs/pirate.js b/packages/junon-io/server/entities/mobs/pirate.js
index b6fef681..58a4e152 100644
--- a/packages/junon-io/server/entities/mobs/pirate.js
+++ b/packages/junon-io/server/entities/mobs/pirate.js
@@ -102,12 +102,6 @@ class Pirate extends LandMob {
}
}
- getWeapon() {
- let item = this.equipments.get(Protocol.definition().EquipmentRole.Hand)
- if (!item) return null
- return item.instance
- }
-
remove() {
super.remove()
diff --git a/packages/junon-io/server/entities/player.js b/packages/junon-io/server/entities/player.js
index 2e1cdd2c..3497fef7 100644
--- a/packages/junon-io/server/entities/player.js
+++ b/packages/junon-io/server/entities/player.js
@@ -60,6 +60,7 @@ class Player extends BaseEntity {
this.sessionId = socket.sessionId
this.remoteAddress = Helper.getSocketRemoteAddress(socket)
this.fingerprint = data.fingerprint
+ this.isClientReadyAndWaitingForSector = false
if (this.sector.isZoomAllowed()) {
this.screenWidth = this.socket.screenWidth
@@ -2652,7 +2653,19 @@ class Player extends BaseEntity {
onPlayerReady() {
// we just got notified by client that they received connection
+ // if sector is not ready yet, wait for it to finish loading
+ if (!this.sector.sectorLoader.isFinished) {
+ this.isClientReadyAndWaitingForSector = true
+ // sectorLoader will call this.onPlayerReady again when it is done
+ return
+ }
+
+ this.finishPlayerReadySetup();
+ }
+
+ finishPlayerReadySetup() {
this.isPlayerReady = true
+ this.isClientReadyAndWaitingForSector = false
for (let tutorialName in this.tutorialIndex) {
this.sendClientTutorialIndex(tutorialName)
@@ -5164,9 +5177,27 @@ class Player extends BaseEntity {
return true
}
- getDamage() {
- let damage = super.getDamage()
- return Math.floor(this.getDamageMultiplier() * damage)
+ getWeapon() {
+ const activeItem = this.getActiveItem();
+ if (activeItem && activeItem.instance && typeof activeItem.instance.isWeapon === 'function' && activeItem.instance.isWeapon()) {
+ return activeItem.instance;
+ }
+ return null;
+}
+
+ getDamage(attackTarget) {
+ const weapon = this.getWeapon();
+ let damage;
+
+ if (weapon) {
+ damage = weapon.getDamage(attackTarget);
+ } else {
+ damage = super.getDamage();
+ }
+
+ const damageMultiplier = this.getDamageMultiplier(attackTarget);
+
+ return Math.floor(damage * damageMultiplier);
}
diff --git a/packages/junon-io/server/entities/projectiles/base_projectile.js b/packages/junon-io/server/entities/projectiles/base_projectile.js
index 6e26e755..45a5aaad 100644
--- a/packages/junon-io/server/entities/projectiles/base_projectile.js
+++ b/packages/junon-io/server/entities/projectiles/base_projectile.js
@@ -24,7 +24,8 @@ class BaseProjectile extends BaseEntity {
super(sector, { id: data.id, x: data.source.x, y: data.source.y, w: data.w, h: data.h })
this.weapon = data.weapon
- this.sourceEntity = data.weapon
+ this.owner = data.owner ? data.owner : data.weapon.owner
+ this.sourceEntity = this.owner
this.destinationEntity = data.destinationEntity
if (data.keyValueMap) {
@@ -32,7 +33,6 @@ class BaseProjectile extends BaseEntity {
this.shouldHitFloor = data.keyValueMap.shouldHitFloor
}
- this.owner = data.owner ? data.owner : data.weapon.owner
this.source = data.source
this.destination = data.destination
@@ -276,6 +276,14 @@ class BaseProjectile extends BaseEntity {
this.remove()
}
+ canDamageWalls() {
+ if (this.sourceEntity && typeof this.sourceEntity.canDamageWalls === 'function') {
+ return this.sourceEntity.canDamageWalls();
+ }
+
+ return this.getConstants().canDamageWalls;
+ }
+
canDamage(entity) {
if(this.sourceEntity && this.sourceEntity.canAttack) return this.sourceEntity.canAttack(entity)
/* if can/can't attack, then can/can't damage.
diff --git a/packages/junon-io/server/entities/projectiles/collidable_projectile.js b/packages/junon-io/server/entities/projectiles/collidable_projectile.js
index 6fba25c3..2a586e91 100644
--- a/packages/junon-io/server/entities/projectiles/collidable_projectile.js
+++ b/packages/junon-io/server/entities/projectiles/collidable_projectile.js
@@ -133,8 +133,8 @@ class CollidableProjectile extends BaseProjectile {
}
getCollidables() {
- return [this.sector.playerTree, this.sector.mobTree, this.sector.structureMap]
- }
+ return [this.sector.playerTree, this.sector.mobTree, this.sector.structureMap, this.sector.armorMap]
+}
}
diff --git a/packages/junon-io/server/entities/projectiles/grenade.js b/packages/junon-io/server/entities/projectiles/grenade.js
index f32ec891..63c954e3 100644
--- a/packages/junon-io/server/entities/projectiles/grenade.js
+++ b/packages/junon-io/server/entities/projectiles/grenade.js
@@ -60,6 +60,7 @@ class Grenade extends BaseProjectile {
createExplosion() {
return this.sector.createProjectile("Explosion", {
+ owner: this.getOwner(),
weapon: this.weapon,
source: { x: this.getX(), y: this.getY() },
destination: { x: this.getX(), y: this.getY() }
diff --git a/packages/junon-io/server/entities/raid.js b/packages/junon-io/server/entities/raid.js
index fa00511b..6a980032 100644
--- a/packages/junon-io/server/entities/raid.js
+++ b/packages/junon-io/server/entities/raid.js
@@ -5,6 +5,7 @@ const EventBus = require('eventbusjs')
class Raid {
constructor(eventManager, data) {
+ this.data = data
this.eventManager = eventManager
this.game = eventManager.game
@@ -71,6 +72,7 @@ class Raid {
}
isRaidTooLong() {
+ if (this.data && this.data.permanent) return false
let twelveHours = Constants.physicsTimeStep * Constants.secondsPerHour * 12
return (this.game.timestamp - this.occurTimestamp) > twelveHours
}
@@ -96,8 +98,6 @@ class Raid {
this.team = this.game.getEntity(data.team.id)
}
- if (!this.team) return
-
if (data.spawnGroundRow) {
let ground = this.sector.groundMap.get(data.spawnGroundRow, data.spawnGroundCol)
if (!ground) {
@@ -115,11 +115,12 @@ class Raid {
this.occurTimestamp = this.game.timestamp + (Constants.physicsTimeStep * Constants.secondsPerHour)
}
-
- let structures = this.team.getRaidableOwnedStructures()
- this.structureCount = structures.length
-
- if (structures.length === 0) return
+ if (this.team) {
+ let structures = this.team.getRaidableOwnedStructures()
+ this.structureCount = structures.length
+ } else {
+ this.structureCount = 0
+ }
}
determineSpawnRoom(team) {
@@ -487,6 +488,7 @@ class Raid {
}
onRaidGoalTargetRemoved() {
+ if (this.data && this.data.permanent) return
if (this.game.isHardcore()) return
if (this.boss) return
@@ -584,4 +586,4 @@ class Raid {
}
-module.exports = Raid
+module.exports = Raid
\ No newline at end of file
diff --git a/packages/junon-io/server/entities/sector.js b/packages/junon-io/server/entities/sector.js
index 2a97d81b..5033a73d 100644
--- a/packages/junon-io/server/entities/sector.js
+++ b/packages/junon-io/server/entities/sector.js
@@ -871,6 +871,13 @@ class Sector {
})
this.enableChunkInvalidations()
+ // after loading, we can rebuild invalidated chunks
+ this.forEachPlayer((player) => {
+ if (player.isClientReadyAndWaitingForSector) {
+ player.finishPlayerReadySetup();
+ }
+ })
+
this.game.postGameReady()
}
@@ -2746,7 +2753,10 @@ class Sector {
return false
}
- options.owner = this
+ if (!options.owner) {
+ options.owner = this
+ }
+
options.source = { x: x, y: y}
options.destination = { x: x, y: y}
klass.build(options)
@@ -3128,8 +3138,6 @@ class Sector {
}
processMiasma() {
- if (this.game.isPeaceful()) return
-
const isOneMinuteInterval = this.game.timestamp % (Constants.physicsTimeStep * 60) === 0
if (!isOneMinuteInterval) return
diff --git a/packages/junon-io/server/server.js b/packages/junon-io/server/server.js
index a2b15870..0cb72ecb 100644
--- a/packages/junon-io/server/server.js
+++ b/packages/junon-io/server/server.js
@@ -28,6 +28,7 @@ const Polyglot = require('node-polyglot')
const englishTranslationMap = require('../common/translations/en')
const japaneseTranslationMap = require('../common/translations/ja')
const russianTranslationMap = require('../common/translations/ru')
+const trationalChineseTranslationMap = require('../common/translations/zh-Hant')
const SectorModel = require("junon-common/db/sector")
const ObjectPool = require("../common/entities/object_pool")
const TileHit = require("./entities/tile_hit")
@@ -44,6 +45,7 @@ global.i18n = {
this.instances["en"] = new Polyglot({ phrases: englishTranslationMap, allowMissing: true })
this.instances["ja"] = new Polyglot({ phrases: japaneseTranslationMap, allowMissing: true })
this.instances["ru"] = new Polyglot({ phrases: russianTranslationMap, allowMissing: true })
+ this.instances["zh-Hant"] = new Polyglot({ phrases: trationalChineseTranslationMap, allowMissing: true })
},
hasLanguage(language) {
return this.instances[language]