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": "按 wasd 鍵移動。", + "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]