From 14fe35bba0e473ecaee54f78552f90dd71a925ad Mon Sep 17 00:00:00 2001 From: Gureumi Date: Thu, 5 Jul 2018 22:21:12 +0900 Subject: [PATCH 01/47] Update bundle_ko.properties --- core/assets/bundles/bundle_ko.properties | 836 +++++++++++------------ 1 file changed, 381 insertions(+), 455 deletions(-) diff --git a/core/assets/bundles/bundle_ko.properties b/core/assets/bundles/bundle_ko.properties index b0b5a47d15..e4ff2ff8c1 100644 --- a/core/assets/bundles/bundle_ko.properties +++ b/core/assets/bundles/bundle_ko.properties @@ -1,79 +1,93 @@ -text.about = 만든이 : [ROYAL]Anuken[] - [SKY]anukendev@gmail.com[]\n이 게임은 [orange]GDL[] Metal Monstrosity Jam 을 사용했습니다.\n\n크레딧\n- [YELLOW]bfxr[] 가 SFX 를 만듬\n- [GREEN]Roccow[] 가 음악을 만듬\n\n특별히 감사한 분들\n- [coral]MitchellFJN[]: 테스트하고 피드백을 주신 분\n- [sky]Luxray5474[]: wiki 를 만들고 코드에 기여하신 분\n- [lime]Epowerj[]: 코드를 만들고 아이콘을 제작하신 분\n- itch.io 그리고 Google Play 에서의 모든 베타 테스터 분들\n -text.credits = 크레딧 -text.discord = Mindustry 디스코드에 참여하세요! -text.changes = [SCARLET]주의!\n[]몇몇 중요한 게임 메커니즘이 변경되었습니다.\n\n- [accent]텔레포터[]는 이제 전력을 사용합니다.\n- [accent]제련소[]와 [accent]도가니[] 는 이제 최대 자원저장 공간을 가집니다.\n- [accent]도가니[] 는 이제 석탄 연료를 필요로 합니다. -text.link.discord.description = 공식 Mindustry 디스코드 채팅방 +text.about = 제작자 : [ROYAL]Anuken[] - [SKY]anukendev@gmail.com[] +text.credits = 제작자 +text.discord = Mindustry Discord 에 참여하세요! +text.changes = [SCARLET]주의!!\\n[]몇몇 게임 메커니즘이 변경되었습니다\\n\\n- [accent]텔레포터[] 는 이제 전력을 필요로 합니다.\\n- [accent]제련소[]와 [accent]도가니[]는 이제 최대 수용량을 가집니다.\\n- [accent]도가니[]는 이제 연료로 석탄을 필요로 합니다. +text.link.discord.description = 공식 Mindustry Discord 채팅방 text.link.github.description = 게임 소스코드 -text.link.dev-builds.description = 개발중인 빌드 (불안정) -text.link.trello.description = 공식 trello 보드에서 현재 계획중인 기능을 찾을 수 있습니다. -text.link.itch.io.description = itch.io 사이트에서 PC 버전 다운로드 또는 웹 버전을 플레이 할 수 있습니다. -text.link.google-play.description = Google Play 스토어 목록 +text.link.dev-builds.description = 개발중인 빌드 +text.link.trello.description = 다음 계획된 기능들을 게시한 공식 trello 보드 +text.link.itch.io.description = PC 버전 다운로드 HTML5 버전이 있는 itch.io 사이트 +text.link.google-play.description = Google 플레이 스토어 등록 정보 text.link.wiki.description = 공식 Mindustry 위키 -text.linkfail = 링크를 열지 못했습니다!\nURL이 클립보드에 복사되었습니다. -text.editor.web = 웹버전은 맵 편집기를 지원하지 않습니다!\n게임을 다운로드 한 후에 사용하세요. -text.web.unsupported = 이 버전의 게임은 멀티플레이를 지원하지 않습니다!\n멀티플레이를 브라우저에서 하고 싶다면 이 itch.io 페이지에서 \"Multiplayer web version\" 버튼을 눌러서 플레이 해 주세요. -text.multiplayer.web = 이 버전의 게임은 멀티플레이를 지원하지 않습니다!\n멀티플레이를 브라우저에서 하고 싶다면 이 itch.io 페이지에서 \"Multiplayer web version\" 버튼을 눌러서 플레이 해 주세요. +text.linkfail = 링크를 여는데 실패했습니다!URL이 기기의 클립보드에 복사되었습니다. +text.editor.web = HTML5 버전은 에디터 기능을 지원하지 않습니다!게임을 다운로드 한 뒤에 사용 해 주세요. +text.web.unsupported = HTML5 버전은 이 기능을 지원하지 않습니다!게임을 다운로드 한 뒤에 사용 해 주세요. +text.multiplayer.web = 이 버전은 멀티플레이를 지원하지 않습니다!멀티플레이를 웹 브라우저에서 즐기고 싶다면, itch.io 페이지에서 \"multiplayer web version\" 링크로 들어가면 됩니다. +text.host.web = HTML5 버전은 게임 호스팅을 지원하지 않습니다!게임을 다운로드 한 뒤에 사용 해 주세요. text.gameover = 코어가 파괴되었습니다. -text.highscore = [YELLOW]최고 점수 달성! -text.lasted = 이번 맵에서 달성한 마지막 웨이브 : -text.level.highscore = 최고 점수: [accent]{0} -text.level.delete.title = 맵 삭제 확인 -text.map.delete = 정말로 이 \"[orange]{0}\" 맵을 삭제하시겠습니까? +text.highscore = [YELLOW]최고점수 달성! +text.lasted = 마지막으로 달성한 단계 +text.level.highscore = 최고 점수 : [accent]{0} +text.level.delete.title = 삭제 확인 +text.map.delete = 정말로 \"[orange]{0}[]\" 맵을 삭제하시겠습니까? text.level.select = 맵 선택 -text.level.mode = 게임 모드: -text.savegame = 저장하기 -text.loadgame = 불러오기 -text.joingame = 서버참가 +text.level.mode = 게임모드: +text.construction.title = 블록 배치 안내서 +text.construction = 당신은 [accent]블록 배치 모드[]를 선택하셨습니다.\n\n블록을 설치하고 싶으면, 자신의 건설 가능 범위 내에서 간단히 탭 하면 됩니다.\n일부 블록을 선택한 후에 확인 버튼을 누르면 배가 배치 작업을 진행할 것입니다.\n- [accent]블록을 삭제[]하고 싶다면 배치하고 싶은 영역을 탭 하세요. \n- [accent]블록을 넓게 배치[]하고 싶다면 배치하고 싶은 시작 영역을 길게 누르며 드래그 하면 됩니다.- [accent]블록을 한줄로 배치[]하고 싶다면 배치하고 싶은 시작 영역을 한번 탭 하고 길게 누르면서 드래그 하면 됩니다. \n- [accent]블록 배치 모드를 취소[]하고 싶다면 화면 하단 왼쪽에 있는 X 버튼을 누르면 됩니다. +text.deconstruction.title = 블록 삭제 안내서 +text.deconstruction = 당신은 [accent]블록 삭제 모드[]를 선택하셨습니다\n블록을 삭제하고 싶다면, 자신의 건설 가능 범위 내에서 간단히 탭 하면 됩니다.\n일부 블록을 선택한 후에 확인 버튼을 누르면 배가 파괴 작업을 진행할 것입니다.\n- [accent]블록을 삭제[]하고 싶다면 배치하고 싶은 영역을 탭 하세요- [accent]블록을 넓은 범위로 삭제[]하고 싶다면 배치하고 싶은 시작 영역을 길게 누르며 드래그 하면 됩니다.- [accent]블록 삭제 모드를 취소[]하고 싶다면 화면 하단 왼쪽에 있는 X 버튼을 누르면 됩니다. +text.showagain = 다음 세션에서 이 메세지를 표시하지 않습니다 +text.unlocks = 잠금 해제 +text.savegame = 게임 저장 +text.loadgame = 게임 불러오기 +text.joingame = 게임 참가\n text.addplayers = 플레이어 추가/제거 text.newgame = 새 게임 text.quit = 나가기 text.maps = 맵 -text.about.button = 소개 +text.maps.none = [LIGHT_GRAY]맵을 찾을 수 없습니다! +text.about.button = 정보 text.name = 이름: -text.unlocked = 새 블록 잠금 해제됨! -text.unlocked.plural = 새 블록들 잠금 해제됨! -text.public = 공개 -text.players = {0}명 온라인 -text.server.player.host = {0} 이 호스트함. -text.players.single = {0}명 온라인. -text.server.mismatch = 패킷오류 : 현재 게임 버전과 서버 버전이 일치하지 않습니다.\n현재 게임 버전이 최신버전인지 확인 해 주세요! +text.unlocked = 새 블록이 잠금 해제되었습니다! +text.unlocked.plural = 새 블록이 잠금 해제되었습니다! +text.server.rollback = 롤백 +text.server.rollback.numberfield = 롤백 개수: +text.blocks.editlogs = 편집 기록 +text.block.editlogsnotfound = [red]이 위치에 대한 편집 기록이 없습니다. +text.public = 공용 +text.players = {0} 플레이어 온라인 +text.server.player.host = 호스트: {0} +text.players.single = {0} 플레이어 온라인 +text.server.mismatch = 패킷 오류: 클라이언트와 서버 버전이 일치하지 않습니다.자신이 서버를 호스트하거나 최신 버전을 사용 해 주세요! text.server.closing = [accent]서버 닫는중... -text.server.kicked.kick = 당신은 서버에서 강제 퇴장 되었습니다. -text.server.kicked.fastShoot = 너님은 총을 너무 빨리 쏴서 강퇴당했다. -text.server.kicked.invalidPassword = 잘못된 비밀번호 입니다! -text.server.kicked.clientOutdated = 현재 플레이중인 게임 버전이 낮습니다!\n게임을 업데이트 해 주세요. -text.server.kicked.serverOutdated = 이 서버는 현재 클라이언트보다 낮은 버전의 서버입니다!\n서버장에게 업데이트를 요청하세요! -text.server.kicked.banned = 당신은 이 서버에서 차단되었습니다. -text.server.kicked.recentKick = 최근에 강제 퇴장되었습니다.\n잠시 후에 다시 입장 해 주세요. -text.server.kicked.nameInUse = 이미 서버 안에 같은 닉네임을 사용하는 유저가 있습니다! -text.server.kicked.idInUse = 당신은 이미 서버에 접속하고 있습니다!\n두 계정을 한 서버에 접속하는 것은 허용되지 않습니다. -text.server.connected = {0} 님이 서버에 입장했습니다. -text.server.disconnected = {0} 님이 서버에서 나갔습니다. -text.nohost = 커스텀 맵은 서버호스팅이 불가능합니다! +text.server.kicked.kick = 당신은 서버에서 추방되었습니다! +text.server.kicked.fastShoot = 당신은 총을 너무 빨리 발사했습니다. +text.server.kicked.invalidPassword = 알 수 없는 비밀번호 입니다! +text.server.kicked.clientOutdated = 오래된 버전의 클라이언트 입니다! 게임을 업데이트 하세요! +text.server.kicked.serverOutdated = 오래된 버전의 서버입니다! 서버 호스트 관리자에게 문의하세요! +text.server.kicked.banned = 당신은 서버에서 밴 망치를 맞아 차단당했습니다. +text.server.kicked.recentKick = 당신은 방금 추방처리 되었습니다.잠시 기다린 후에 접속 해 주세요. +text.server.kicked.nameInUse = 이 닉네임이 이미 서버에서 사용중입니다. +text.server.kicked.nameEmpty = 닉네임에는 반드시 영어 또는 숫자가 있어야 합니다. +text.server.kicked.idInUse = 당신은 이미 서버에 접속중입니다! 다중 계정은 허용되지 않습니다. +text.server.kicked.customClient = 이 서버는 수정된 클라이언트를 지원하지 않습니다. 공식 버전을 사용하세요. +text.server.connected = {0} 님이 접속했습니다. +text.server.disconnected = {0} 님이 나갔습니다. +text.nohost = 커스텀 맵을 호스트 할 수 없습니다! text.host.info = [accent]호스트[] 버튼은 현재 네트워크의 [scarlet]6567[] 과 [scarlet]6568[] 포트를 사용합니다.\n[LIGHY_GRAY]같은 Wi-Fi 또는 로컬 네트워크[] 에서 서버 목록을 볼 수 있습니다.\n\n만약 플레이어들이 이 IP를 통해 어디에서나 연결할 수 있게 하고 싶다면, 공유기 설정에서 [accent]포트 포워딩[]을 해야 합니다.\n\n[LIGHT_GRAY]참고 : LAN 게임 연결에 문제가 있는 사람이 있다면, 방화벽 설정에서 Mindustry 가 로컬 네트워크에 액세스하도록 허용했는지 확인 해 주세요. text.join.info = 여기서 [accent]서버 IP[]를 입력하여 다른 서버에 접속할 수 있습니다.\n또는 [accent]로컬 네트워크(LAN)[] 서버를 검색하여 접속할 수 있습니다.\nLAN 및 WAN 멀티 플레이어 모두 지원됩니다.\n\n[LIGHT_GRAY]참고 : 여기에서는 자동으로 글로벌 서버를 추가하지 않습니다. IP로 다른 사람의 서버에 접속할려면 서버장에게 IP를 요청해야 합니다. text.hostserver = 서버 열기 text.host = 호스트 -text.hosting = [accent]서버여는중... +text.hosting = [accent]서버 여는중.. text.hosts.refresh = 새로고침 text.hosts.discovering = LAN 게임 찾기 -text.server.refreshing = 서버 새로고침 -text.hosts.none = [lightgray]LAN 게임이 없습니다! -text.host.invalid = [scarlet]호스트에 연결할 수 없습니다! -text.server.friendlyfire = 팀킬 허용 +text.server.refreshing = 서버 목록 새로고치는중... +text.hosts.none = [lightgray]LAN 게임을 찾을 수 없습니다! +text.host.invalid = [scarlet]서버에 연결할 수 없습니다! +text.server.friendlyfire = 팀킬 text.trace = 플레이어 추적 -text.trace.playername = 플레이어 이름 : [accent]{0} -text.trace.ip = IP : [accent]{0} -text.trace.id = 고유 ID : [accent]{0} +text.trace.playername = 플레이어 이름: [accent]{0} +text.trace.ip = IP : [accent] +text.trace.id = 고유 ID: [accent]{0} text.trace.android = Android 클라이언트 : [accent]{0} text.trace.modclient = 수정된 클라이언트 : [accent]{0} -text.trace.totalblocksbroken = 총 파괴한 블록 수 : [accent] -text.trace.structureblocksbroken = 총 구조 블럭 파괴수 : [accent] -text.trace.lastblockbroken = 마지막으로 파괴한 블록 : [accent]{0} -text.trace.totalblocksplaced = 총 설치한 블록 수 : [accent]{0} -text.trace.lastblockplaced = 마지막으로 설치한 블록 : [accent] +text.trace.totalblocksbroken = 총 블럭 파괴 수: [accent]{0} +text.trace.structureblocksbroken = 총 구조 블럭 파괴 수: [accent]{0} +text.trace.lastblockbroken = 마지막으로 파괴한 블럭: [accent]{0} +text.trace.totalblocksplaced = 총 설치한 블럭 수: [accent]{0} +text.trace.lastblockplaced = 마지막으로 설치한 블록: [accent]{0} text.invalidid = 잘못된 클라이언트 ID 입니다! 공식 Mindustry 으로 버그 보고서를 제출 해 주세요. -text.server.bans = 차단된 유저들 +text.server.bans = 차단된 유저 text.server.bans.none = 차단된 플레이어가 없습니다. text.server.admins = 관리자들 text.server.admins.none = 관리자가 없습니다! @@ -86,10 +100,10 @@ text.server.outdated.client = [Crimson]클라이언트 버전이 낮습니다![] text.server.version = [lightgray] 버전 : {0} text.server.custombuild = [노란색]수정된 빌드 text.confirmban = 이 플레이어를 차단하시겠습니까? -text.confirmunban = 이 플레이어를 차단 해제 하시겠습니까? +text.confirmunban = 이 플레이어를 차단하시겠습니까? text.confirmadmin = 이 플레이어를 관리자로 설정 하시겠습니까? text.confirmunadmin = 이 플레이어의 관리자 상태를 해제하시겠습니까? -text.joingame.byip = IP로 참가하기... +text.joingame.byip = IP를 입력해서 참가하기... text.joingame.title = 게임 참가 text.joingame.ip = IP: text.disconnect = 서버와 연결이 해제되었습니다. @@ -103,105 +117,112 @@ text.server.invalidport = 포트 번호가 잘못되었습니다. text.server.error = [crimson]{0}[orange]서버를 호스팅 하는데 오류가 발생했습니다.[] text.tutorial.back = < 이전 text.tutorial.next = 다음 > -text.save.new = 새로 저장 -text.save.overwrite = 이 저장 슬롯을 덮어씌우겠습니까? -text.overwrite = 덮어쓰기 -text.save.none = 저장 파일을 찾지 못했습니다! -text.saveload = [accent]저장중... -text.savefail = 게임을 저장하지 못했습니다! -text.save.delete.confirm = 이 저장파일을 삭제 하시겠습니까? -text.save.delete = 삭제 -text.save.export = 저장파일 내보내기 -text.save.import.invalid = [orange] 저장파일이 유효한 파일이 아닙니다!\n\n다른 디바이스에 있는 커스텀 맵을 가져오는건 작동하지 않습니다. -text.save.import.fail = [crimson]저장파일을 불러오지 못함: [orange]{0} +text.save.new = 새로 저장\n +text.save.overwrite = 이 저장 슬롯을 덮어씌우겠습니까?\n +text.overwrite = 덮어쓰기\n +text.save.none = 저장 파일을 찾지 못했습니다!\n +text.saveload = [accent]저장중...\n +text.savefail = 게임을 저장하지 못했습니다!\n +text.save.delete.confirm = 이 저장파일을 삭제 하시겠습니까?\n +text.save.delete = 삭제\n +text.save.export = 저장파일 내보내기\n +text.save.import.invalid = [orange]저장파일이 유효한 파일이 아닙니다!\n\n다른 디바이스에 있는 커스텀 맵을 가져오는건 작동하지 않습니다.\n +text.save.import.fail = [crimson]저장파일을 불러오지 못함: [orange]{0}\n text.save.export.fail = [crimson]저장파일을 내보내지 못함: [orange]{0} -text.save.import = 저장파일 불러오기 -text.save.newslot = 저장 파일이름 : -text.save.rename = 이름 변경 -text.save.rename.text = 새 이름 : -text.selectslot = 저장슬롯을 선택하십시오. -text.slot = [accent]{0}번째 슬롯 -text.save.corrupted = [orange]저장파일이 손상되었습니다! -text.empty = <비어있음> -text.on = 켜기 -text.off = 끄기 +text.save.import = 저장파일 불러오기\n +text.save.newslot = 저장 파일이름 :\n +text.save.rename = 이름 변경\n +text.save.rename.text = 새 이름 :\n +text.selectslot = 저장슬롯을 선택하십시오.\n +text.slot = [accent]{0}번째 슬롯\n +text.save.corrupted = [orange]저장파일이 손상되었습니다!\n +text.empty = <비어있음>\n +text.on = 켜기\n +text.off = 끄기\n text.save.autosave = 자동저장: {0} -text.save.map = 맵: {0} +text.save.map = 맵 : text.save.wave = {0} 단계 text.save.difficulty = 난이도 : {0} text.save.date = 마지막 저장 날짜 : {0} text.confirm = 확인 -text.delete = 삭제 -text.ok = 확인 +text.delete = 삭제\n +text.ok = 승인 text.open = 열기 text.cancel = 취소 text.openlink = 링크 열기 text.copylink = 링크 복사 -text.back = 뒤로 -text.quit.confirm = 종료 하시겠습니까? +text.back = 뒤로가기 +text.quit.confirm = 정말로 종료하시겠습니까? text.changelog.title = 변경사항 -text.changelog.loading = 업데이트 내역 가져오는중.. -text.changelog.error.android = [orange]업데이트 내역은 가끔 Android 4.4 이하에서 작동하지 않습니다.\n이것은 Android 내부 버그입니다. -text.changelog.error.ios = [orange]현재 업데이트 내역은 iOS 에서 지원하지 않습니다. -text.changelog.error = [searlet]업데이트 내역을 가져오는데 오류가 발생했습니다!\n인터넷 연결을 확인 해 주세요. -text.changelog.current = [yellow][[현재 버전] +text.changelog.loading = 변경사항 가져오는중... +text.changelog.error.android = [orange]게임 변경사항은 가끔 Android 4.4 이하에서 작동하지 않습니다.이것은 내부 Android 버그 때문입니다. +text.changelog.error.ios = [orange]현재 iOS에서는 변경 사항을 지원하지 않습니다. +text.changelog.error = [scarlet]게임 변경사항을 가져오는 중 오류가 발생했습니다![]\n인터넷 연결을 확인하십시오. +text.changelog.current = [orange][[현재 버전] text.changelog.latest = [orange][[최신 버전] -text.loading = [accent]로딩중 ... -text.wave = [orange] {0} 단계 -text.wave.waiting = 다음 단계까지 {0} 초 남음 -text.waiting = 대기 중... -text.enemies = 남은 잡몹 수 : {0} -text.enemies.single = {0} 마리 남음 +text.loading = [accent]불러오는중... +text.saving = [accent]저장중...\n +text.wave = [orange]{0} 단계 +text.wave.waiting = 다음 단계 시작까지 {0}초 +text.waiting = 기다리는중... +text.enemies = 남은 몹 : {0} +text.enemies.single = 몹이 1마리 남아있음 text.loadimage = 사진 불러오기 text.saveimage = 사진 저장 text.unknown = 알 수 없음 -text.custom = 관습 +text.custom = 커스텀 text.builtin = 내장 -text.map.delete.confirm = 이 맵을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다! -text.editor.openin = 편집기에서 열기 -text.editor.oregen = 광물 생성 -text.editor.oregen.info = 광물 생성: +text.map.delete.confirm = 이 맵을 삭제하시겠습니까? 이 명령은 취소할 수 없습니다! +text.map.random = [accent]랜덤 맵 +text.map.nospawn = 이 맵에는 플레이어가 스폰 할 코어가 없습니다! 맵 편집기에서 [ROYAL]파란색[]코어를 맵에 추가하세요. +text.editor.slope = \\ +text.editor.openin = 편집기 열기 +text.editor.oregen = 광물 무작위 생성 +text.editor.oregen.info = 광물 무작위 생성: text.editor.mapinfo = 맵 정보 -text.editor.author = 작성자: +text.editor.author = 만든이: text.editor.description = 설명: text.editor.name = 이름: text.editor.teams = 팀 -text.editor.badsize = [orange]사진 크기가 잘못되었습니다![]\n유효한 맵 크기 : {0} -text.editor.errorimageload = 파일을 불러오는 중 오류 발생 : [orange]{0} -text.editor.errorimagesave = 파일 저장 중 오류 발생 : [orange] {0} +text.editor.elevation = 높이 +text.editor.badsize = [orange]사진 크기가 잘못되었습니다![]유효한 맵 크기 : {0} +text.editor.errorimageload = [orange]{0}[] 파일을 불러오는데 오류가 발생했습니다. +text.editor.errorimagesave = [orange]{0}[] 파일 저장중 오류가 발생했습니다. text.editor.generate = 생성 text.editor.resize = 크기 조정 text.editor.loadmap = 맵 불러오기 text.editor.savemap = 맵 저장 text.editor.saved = 저장됨! -text.editor.save.noname = 지도에 이름이 없습니다! '지도 정보'메뉴에서 하나를 설정하십시오. -text.editor.save.overwrite = 귀하의지도는 내장 된지도를 덮어 씁니다! '지도 정보'메뉴에서 다른 이름을 선택하십시오. -text.editor.import = 가져오기 -text.editor.importmap = 지도 가져 오기 -text.editor.importmap.description = 이미 존재하는지도 가져 오기 +text.editor.save.noname = 지도에 이름이 없습니다! '맵 정보' 메뉴에서 설정하세요. +text.editor.save.overwrite = 이 맵의 이름은 기존에 있던 맵을 덮어씁니다! '맵 정보' 메뉴에서 다른 이름을 선택하세요. +text.editor.import.exists = [scarlet]맵을 불러올 수 없음:[] 기존에 있던 '{0}' 맵이 이미 존재합니다! +text.editor.import = 가져오기 +text.editor.importmap = 맵 가져오기 +text.editor.importmap.description = 이미 존재하는 맵 가져오기 text.editor.importfile = 파일 가져오기 -text.editor.importfile.description = 외부 맵 파일 가져 오기 -text.editor.importimage = 지형 이미지 가져 오기 -text.editor.importimage.description = 외부지도 이미지 파일 가져 오기 -text.editor.export = 내보내기 +text.editor.importfile.description = 외부 맵 파일 가져오기 +text.editor.importimage = 지형 사진 가져오기 +text.editor.importimage.description = 외부 맵 이미지 파일 가져오기 +text.editor.export = 내보내기 text.editor.exportfile = 파일 내보내기 -text.editor.exportfile.description = 지도 파일 내보내기 +text.editor.exportfile.description = 맵 파일 내보내기 text.editor.exportimage = 지형 이미지 내보내기 -text.editor.exportimage.description = 지도 이미지 파일 내보내기 -text.editor.loadimage = 지형 가져 오기 +text.editor.exportimage.description = 맵 이미지 파일 내보내기 +text.editor.loadimage = 지형 가져오기 text.editor.saveimage = 지형 내보내기 -text.editor.unsaved = [scarlet]변경사항을 저장하지 않았습니다![]\n종료하시겠습니까? -text.editor.brushsize = 브러쉬 크기 : -text.editor.noplayerspawn = 이맵에는 플레이어의 스폰 지점이 없습니다! +text.editor.unsaved = [scarlet]변경사항을 저장하지 않았습니다![]\n정말로 나가시겠습니까?\n +text.editor.brushsize = 브러쉬 크기 : {0} +text.editor.noplayerspawn = 이 맵에는 플레이어의 스폰 지점이 없습니다! text.editor.manyplayerspawns = 맵에는 플레이어 스폰 지점이 둘 이상 있을 수 없습니다! -text.editor.manyenemyspawns = 잡몹 스폰지점을 개 이상으로 지정할 수 없습니다! +text.editor.manyenemyspawns = {0} 개 이상의 몹 스폰 지점을 설정할 수 없습니다! text.editor.resizemap = 맵 크기 조정 -text.editor.resizebig = [scarlet]경고!\n[]맵 크기가 256이상일경우 랙이 걸리거나 게임이 불안정할 수 있습니다. -text.editor.mapname = 맵 이름 : -text.editor.overwrite = [accent]경고!\n이 작업은 기존 맵을 덮어 쓰게 됩니다! -text.editor.selectmap = 불러올 맵 선택 : -text.width = 넓이 : -text.height = 높이: +text.editor.resizebig = [scarlet]경고![]맵 크기가 256보다 큰지도는 많은 랙을 유발할 수 있습니다. +text.editor.mapname = 맵 이름: +text.editor.overwrite = [accept]경고!이 명령은 기존 맵을 덮어씌우게 됩니다.\n +text.editor.overwrite.confirm = [scarlet]경고![] 이 이름을 가진 맵이 이미 있습니다. 덮어 쓰시겠습니까? +text.editor.selectmap = 불러올 맵 선택: +text.width = 넓이: +text.height = 높이: text.randomize = 무작위 text.apply = 적용 text.update = 업데이트 @@ -209,385 +230,290 @@ text.menu = 메뉴 text.play = 플레이 text.load = 불러오기 text.save = 저장 +text.fps = {0} FPS +text.tps = {0} TPS +text.ping = 핑 : {0}ms text.language.restart = 언어 설정을 적용하려면 게임을 다시 시작하십시오. text.settings.language = 언어 text.settings = 설정 -text.tutorial = 자습서 -text.editor = 에디터 -text.mapeditor = 맵 에디터 -text.donate = 기부하기 +text.tutorial = 게임 방법 +text.editor = 편집기 +text.mapeditor = 맵 편집기 +text.donate = 기부 text.settings.reset = 기본값으로 재설정 text.settings.rebind = rebind text.settings.controls = 컨트롤 -text.settings.game = 게임 설정 -text.settings.sound = 소리 설정 -text.settings.graphics = 그래픽 설정 +text.settings.game = 게임 +text.settings.sound = 소리 +text.settings.graphics = 화면 text.upgrades = 업그레이드 -text.purchased = [LIME]제작됨! +text.purchased = [LIME]생성됨! text.weapons = 무기 -text.paused = 일시중지 -text.respawn = 남은 부활 시간 : +text.paused = 일시 정지 +text.respawn = 남은 부활시간 : text.info.title = [accent]정보 -text.error.title = [crimson]예기지 않은 오류가 발생했습니다! -text.error.crashmessage = [SCARLET]예기치 못한 오류가 발생하여, 무언가 충돌을 일으켰습니다\n[]이 오류에 대한 정확한 내용을 개발자에게 전달해 주세요!\n[ORANGE]anukendev@gmail.com[] 또는 [orange]Mindustry 디스코드[orange]로 보내주세요! -text.error.crashtitle = 오류 발생! -text.mode.break = 삭제 모드 : -text.mode.place = 설치 모드 : -placemode.hold.name = 라인 -placemode.areadelete.name = 구역 삭제 -placemode.touchdelete.name = 클릭하여 삭제 -placemode.holddelete.name = 길게 눌러 삭제 +text.error.title = [crimson]오류가 발생했습니다. +text.error.crashmessage = [scarlet]예기치 않은 오류가 발생하여 게임이 강제 종료되었습니다![]\n이 오류가 발생한 정확한 상황을 개발자에게 알려주십시오. [ORANGE]anukendev@gmail.com[] +text.error.crashtitle = 오류가 발생했습니다. +text.mode.break = 파괴 모드: {0} +text.mode.place = 설치 모드 : {0} +placemode.hold.name = 한줄 +placemode.areadelete.name = 구역 +placemode.touchdelete.name = 터치 +placemode.holddelete.name = 길게 누름 placemode.none.name = 없음 -placemode.touch.name = 클릭 +placemode.touch.name = 터치 placemode.cursor.name = 커서 -text.blocks.extrainfo = [accent]추가 블록 정보: text.blocks.blockinfo = 블록 정보 -text.blocks.powercapacity = [powerinfo]전력 용량 -text.blocks.powershot = [turretinfo]전력당 발사 수 -text.blocks.powersecond = [powerinfo]초당 전력량 -text.blocks.powerdraindamage = [powerinfo]전력 소모량당 데미지 량 -text.blocks.shieldradius = [powerinfo]보호막 반경 -text.blocks.itemspeedsecond = [iteminfo]초당 아이템 속도 -text.blocks.range = [turretinfo]범위 -text.blocks.size = [gray]크기 -text.blocks.powerliquid = [powerinfo]전력당 액체량 -text.blocks.maxliquidsecond = [liquidinfo]초당 최대 액체량 -text.blocks.liquidcapacity = [liquidinfo]액체 저장량 -text.blocks.liquidsecond = [liquidinfo]초당 액체량 -text.blocks.damageshot = [turretinfo]1발당 데미지 -text.blocks.ammocapacity = [turretinfo]탄약 적재량 -text.blocks.ammo = [turretinfo]탄약 -text.blocks.ammoitem = [turretinfo]탄약당 아이템 -text.blocks.maxitemssecond = [iteminfo]초당 최대 아이템 수 -text.blocks.lasertilerange = [powerinfo]레이저 타일 범위 -text.blocks.capacity = [iteminfo]용량 -text.blocks.itemcapacity = [iteminfo]아이템 용량 -text.blocks.maxpowergenerationsecond = [powerinfo]초당 최대 발전량 -text.blocks.powergenerationsecond = [powerinfo]초당 발전량 -text.blocks.generationsecondsitem = [powerinfo]초당 생성 아이템 수 -text.blocks.input = [liquidinfo]입력 -text.blocks.inputliquid = [liquidinfo]입력 액체 -text.blocks.inputitem = [liquidinfo]입력 아이템 -text.blocks.output = [liquidinfo]출력 -text.blocks.secondsitem = [liquidinfo]초당 아이템 수 -text.blocks.maxpowertransfersecond = [powerinfo]초당 최대 전력 이송량 -text.blocks.explosive = [orange]이 건물이 파괴되면 큰 폭발이 일어납니다! -text.blocks.repairssecond = [turretinfo]초당 수리력 -text.blocks.health = [healthstats]체력 -text.blocks.inaccuracy = [turretinfo]명중하지 않을 확률 -text.blocks.shots = [turretinfo]발사수 -text.blocks.shotssecond = [turretinfo]초당 발사수 -text.blocks.fuel = [craftinfo]연료 -text.blocks.fuelduration = [craftinfo]연료 지속 시간 -text.blocks.maxoutputsecond = [craftinfo]초당 최대 출력 수 -text.blocks.inputcapacity = [craftinfo]초당 입력 수 -text.blocks.outputcapacity = [craftinfo]초당 출력 수 -text.blocks.poweritem = [powerinfo]전력당 아이템 수 -text.placemode = 설치 모드 -text.breakmode = 삭제 모드 -text.health = 체력 +text.blocks.powercapacity = 최대 전력 용량 +text.blocks.powershot = 1발당 파워 소모량 +text.blocks.itemspeed = 유닛 이동 속도 +text.blocks.shootrange = 공격 범위 +text.blocks.size = 블록 크기 +text.blocks.liquidcapacity = 최대 액체 용량 +text.blocks.maxitemssecond = 최대 아이템 보관량 +text.blocks.powerrange = 전력 범위 +text.blocks.poweruse = 전력 사용 +text.blocks.inputitemcapacity = 입력 아이템 용량 +text.blocks.outputitemcapacity = 입력 아이템 용량 +text.blocks.itemcapacity = 아이템 용량 +text.blocks.maxpowergeneration = 최대 발전량 +text.blocks.powertransferspeed = 전력 전송량 +text.blocks.craftspeed = 생산 속도 +text.blocks.inputliquid = 입력 액체 +text.blocks.inputliquidaux = 보조 액체 +text.blocks.inputitem = 입력 아이템 +text.blocks.inputitems = 입력 아이템들 +text.blocks.outputitem = 출력 아이템 +text.blocks.drilltier = 드릴 +text.blocks.drillspeed = 기본 드릴 속도 +text.blocks.liquidoutput = 액체 출력 +text.blocks.liquiduse = 액체 사용 +text.blocks.explosive = 이게 터지면 펑 터지면서 주변 블록에게 피해를 입힙니다! +text.blocks.health = 체력 +text.blocks.inaccuracy = 빗맞을 확률 +text.blocks.shots = 총알 +text.blocks.reload = 재장전 +text.blocks.inputfuel = 연료 +text.blocks.fuelburntime = 연료 연소 시간 +text.blocks.inputcapacity = 입력 용량 +text.blocks.outputcapacity = 출력 용량 +text.unit.blocks = 블록들 +text.unit.powersecond = 초당 전력 단위 +text.unit.liquidsecond = 액체 단위 / 초 +text.unit.itemssecond = 항목 / 초 +text.unit.pixelssecond = 초당 픽셀 +text.unit.liquidunits = 액상 단위 +text.unit.powerunits = 전원 장치 +text.unit.degrees = 도 +text.unit.seconds = 초 +text.unit.none = +text.unit.items = 아이템 +text.category.general = 일반 +text.category.power = 전력 +text.category.liquids = 액체 +text.category.items = 아이템 +text.category.crafting = 제작 +text.category.shooting = 발사 setting.difficulty.easy = 쉬움 setting.difficulty.normal = 보통 setting.difficulty.hard = 어려움 -setting.difficulty.insane = 미쳤음 -setting.difficulty.purge = 청소기 +setting.difficulty.insane = 미침 +setting.difficulty.purge = [#FE2E2E]대한[#2E2EFE]민국 setting.difficulty.name = 난이도: -setting.screenshake.name = 화면 흔들림 -setting.smoothcam.name = 부드러운 카메라 이동 +setting.screenshake.name = 화면 흔들기 +setting.smoothcam.name = 부드러운 카메라 setting.indicators.name = 적 위치 표시 화살표 setting.effects.name = 화면 효과 setting.sensitivity.name = 컨트롤러 감도 -setting.saveinterval.name = 자동 저장 간격 +setting.saveinterval.name = 자동저장 간격 setting.seconds = 초 setting.fullscreen.name = 전체 화면 -setting.multithread.name = 멀티 스레드 활성화 -setting.fps.name = 초당 프레임 표시 -setting.vsync.name = 수직동기화 -setting.lasers.name = 파워 레이저 표시 -setting.previewopacity.name = 블럭 배치 미리보기 표시 -setting.healthbars.name = 체력 막대바 표시 +setting.multithread.name = 멀티 스레딩 +setting.fps.name = FPS 표시 +setting.vsync.name = VSync +setting.lasers.name = 파워 레이져 표시 +setting.previewopacity.name = 미리보기 블럭 투명도 +setting.healthbars.name = 몹 체력바 표시 +setting.minimap.name = 미니맵 보기 setting.pixelate.name = 화면 픽셀화 -setting.musicvol.name = 음악 볼륨 -setting.mutemusic.name = 음악 끄기 -setting.sfxvol.name = 효과음 볼륨 -setting.mutesound.name = 효과음 끄기 +setting.musicvol.name = 음악 크기 +setting.mutemusic.name = 음소거 +setting.sfxvol.name = SFX 볼륨 +setting.mutesound.name = 소리 끄기 map.maze.name = 미로 map.fortress.name = 요새 map.sinkhole.name = 싱크홀 map.caves.name = 동굴 map.volcano.name = 화산 map.caldera.name = 칼데라 -map.scorch.name = 타버린 세계 +map.scorch.name = 타버림 map.desert.name = 사막 map.island.name = 섬 -map.grassland.name = 초원 -map.tundra.name = 툰드라 +map.grassland.name = 목초지 +map.tundra.name = 툰트라 map.spiral.name = 나선 -map.tutorial.name = 자습서 -tutorial.intro.text = [yellow] 자습서에 오신 것을 환영합니다.[] 시작하려면 '다음'을 누르세요. -tutorial.moveDesktop.text = 이동하려면 [orange] [[WASD] [] 키를 사용하세요. 또한, [orange]Shift[]를 누르고 있으면 빠르게 이동할 수 있습니다. [orange]CTRL[]을 누른 상태에서 [orange] 마우스 스크롤 휠 []을 사용하여 확대 또는 축소 할 수 있습니다 -tutorial.shoot.text = 마우스를 사용하여 조준하고 [orange]왼쪽 마우스 버튼[]을 눌러 쏘세요. 저기 노란색 [yellow]타겟[]앞에서 연습해보세요. -tutorial.moveAndroid.text = 화면을 이동하기 위해서 한손가락으로 드래그 해 보세요. 확대 및 축소는 두손가락으로 하시면 됩니다. -tutorial.placeSelect.text = 오른쪽 하단에 있는 블럭 메뉴에서 [yellow]컨베이어[]를 선택하세요. -tutorial.placeConveyorDesktop.text = [orange][[마우스 스크롤 휠][]을 사용하여 컨베이어를[orange]앞 방향[]으로 돌린다음 [yellow]표시된 위치[]에 [orange][[왼쪽 마우스 버튼][]을 이용해 컨베이어를 설치 합니다. -tutorial.placeConveyorAndroid.text = 컨베이어를 회전 시키기 위해[orange][[회전 버튼][]를 누르고 [orange]앞방향[]을 보게 한후, 한손가락으로 [yellow]표시된 위치[]에 [orange][[확인][] 버튼으로 설치하세요. -tutorial.placeConveyorAndroidInfo.text = 아니면 왼쪽 하단에 [orange][[설치 모드][]를 눌러 클릭모드로 전환하고 클릭만으로 블럭을 배치 할수 있습니다.\n방향을 바꾸려면 왼쪽하단 5번째 칸에 있는 화살표로 바꾸시면 됩니다.\n[yellow]다음[]을 눌러 한번 해 보세요. -tutorial.placeDrill.text = 이제, 표시된 위치에 [yellow]돌 드릴[]을 선택하여 배치하세요. -tutorial.blockInfo.text = 블록에 대해 더 자세히 알고 싶다면, 오른쪽 상단에있는 [orange]물음표[]를 눌러 설명을 읽으세요. -tutorial.deselectDesktop.text = [orange][[마우스 오른쪽 버튼][]을 사용하여 블록을 선택 해제할 수 있습니다. -tutorial.deselectAndroid.text = [orange]X[] 버튼을 눌러 블록을 선택 해제할 수 있습니다. -tutorial.drillPlaced.text = 드릴은 이제 [yellow] 돌[]을 생산할 것이고, 컨베이어로 내보낸 다음 [yellow]코어[]로 옮길 것입니다. -tutorial.drillInfo.text = 각 광석은 그에 맞는 드릴이 필요합니다. 돌은 돌 드릴이 필요하고, 철은 철 드릴이 필요합니다. -tutorial.drillPlaced2.text = 아이템을 코어로 이동하면 왼쪽 상단의 [orange]아이템 인벤토리[]에 표시됩니다.\n인벤토리의 아이템은 블록을 배치할때 사용됩니다. -tutorial.moreDrills.text = 드릴과 컨베이어는 많이 연결할 수 있습니다. 이것처럼요. -tutorial.deleteBlock.text = 블록을 삭제하고 싶으면 [orange]마우스 오른쪽 버튼[]을 클릭하여 블록을 삭제할 수 있습니다.\n이 컨베이어를 삭제 해 보세요. -tutorial.deleteBlockAndroid.text = 왼쪽 하단에 있는 [orange]블록 삭제모드[]안에 [orange]십자선[] 아이콘을 선택한 다음 블록을 눌러 삭제할 수 있습니다. 이 컨베이어를 삭제해 보세요. -tutorial.placeTurret.text = 이제[yellow] 표시된 위치 []에 [yellow]포탑[]을 선택하여 배치하세요. -tutorial.placedTurretAmmo.text = 이 포탑은 컨베이어에서 [yellow]탄약[]을 받습니다.\n포탑에 커서를 가리키면 [green]녹색 막대[]를 통해 얼마나 많은 탄약이 포탑 안에있는지 확인 할수 있습니다. -tutorial.turretExplanation.text = 포탑은 충분한 탄약을 보유하고있는 한, 범위 내의 가장 가까운 적을 향해 자동으로 공격합니다. -tutorial.waves.text = [yellow]60[]초 마다, 웨이브가 시작되고[coral]적[]들이 특정 위치에 스폰되어 코어를 파괴하기위해 올 것 입니다. -tutorial.coreDestruction.text = 당신의 목표는 [yellow]코어를 최대한 오래동안 방어하는 것[]입니다. 코어가 파괴되면 [coral]게임에서 패배합니다[]. -tutorial.pausingDesktop.text = 휴식을 취해야 하는 경우, 왼쪽 상단에 있는 [orange]일시정지 버튼[] 을 누르거나 [orange]스페이스바[] 를 눌러 게임을 일시정지 시킬 수 있습니다.\n일시정지된 상태에서 블록을 선택하고 배치할 수는 있지만, 움직이거나 공격할 수는 없습니다. -tutorial.pausingAndroid.text = 휴식을 취해야 하는 경우, 왼쪽 상단에 있는 [orange]일시정지 버튼[] 을 눌러 게임을 일시정지 시킬 수 있습니다.\n일시정지된 상태에서 블록을 선택하고 배치할 수는 있지만, 움직이거나 공격할 수는 없습니다. -tutorial.purchaseWeapons.text = 왼쪽 하단에 있는 업그레이드 메뉴를 열어 새로운 [yellow]무기[]를 구입할 수 있습니다. -tutorial.switchWeapons.text = 왼쪽 하단의 아이콘을 클릭하거나 숫자 [orange][[1-9][] 버튼을 사용하여 무기를 바꿀 수 있습니다. -tutorial.spawnWave.text = 웨이브가 시작되었습니다. 적들을 파괴하세요! -tutorial.pumpDesc.text = 이후 웨이브에선 발전기 또는 추출기용 액체를 사용하기 위해 [yellow]펌프[] 를 사용해야 할 수도 있습니다 -tutorial.pumpPlace.text = 펌프는 드릴과 유사하게 작동하지만, 아이템 대신 액체를 생산합니다. [yellow]지정된 위치[]에 펌프를 놓으세요. -tutorial.conduitUse.text = 이제 [orange]파이프[] 를 펌프에서 멀리 떨어트려 두세요 -tutorial.conduitUse2.text = 하나 더... -tutorial.conduitUse3.text = 하나 더... -tutorial.generator.text = 이제 파이프 끝 부분에 [orange]석유 발전기[] 블록을 놓으세요. -tutorial.generatorExplain.text = 이제 이 발전기는 석유에서 [yellow]전력[]을 생성합니다. -tutorial.lasers.text = 전력은 [yellow]파워 레이저[]를 통해 전송됩니다. 회전한 후 이곳에 배치하세요. -tutorial.laserExplain.text = 발전기가 이제 레이저 블록으로 전력을 보냅니다.\n[yellow]불투명[] 광선은 현재 전력을 전송 중임을 의미하고 [yellow]투명[] 광선은 전송하지 않음을 의미합니다. -tutorial.laserMore.text = 블록 위로 마우스를 올리고 상단의 [yellow]노란 막대[]를 확인하여 블록의 전력을 볼 수 있습니다 -tutorial.healingTurret.text = 이 레이저는 [lime]수리포탑[] 에 전력을 공급하는데 사용합니다. 여기에 하나 놓으세요 -tutorial.healingTurretExplain.text = 전력이 있는 한 이 포탑은 [lime]주변 블록을 수리[] 합니다.\n시간이 있을 때 가능한 빨리 기지에 하나가 있는지 확인하세요! -tutorial.smeltery.text = 많은 블록들은[orange]강철[]을 필요로 합니다.\n[orange]제련소[]를 선택해 여기에 놓으세요. -tutorial.smelterySetup.text = 이 제련소는 석탄을 연료로 사용하며 철을 넣으면 [orange]강철[] 을 생산할 것입니다. -tutorial.tunnelExplain.text = 또한 아이템이 [orange]터널 블록[]을 통해 지나가고 다른 쪽에서 돌 블록을 통과하여 나옵니다.\n터널은 최대 2블록까지만 통과할 수 있습니 -tutorial.end.text = 이것으로 자습서를 마칩니다! 행운을 빕니다! -text.keybind.title = 키 지정 -keybind.move_x.name = x축 이동 -keybind.move_y.name = y축 이동 +map.tutorial.name = 게임 방법 +text.keybind.title = 키 바인딩 +keybind.move_x.name = move_x +keybind.move_y.name = move_y keybind.select.name = 선택 -keybind.break.name = 블럭 삭제 -keybind.shoot.name = 발사 -keybind.zoom_hold.name = 확대 할때 누를 버튼 +keybind.break.name = 파괴 +keybind.shoot.name = 사격 +keybind.zoom_hold.name = 길게눌러 확대 keybind.zoom.name = 확대 -keybind.block_info.name = 블록 정보 +keybind.block_info.name = 블럭 정보 keybind.menu.name = 메뉴 -keybind.pause.name = 일시정지 +keybind.pause.name = 일시중지 keybind.dash.name = 달리기 -keybind.chat.name = 대화창 -keybind.player_list.name = player_list +keybind.chat.name = 채팅 +keybind.player_list.name = 플레이어 목록 keybind.console.name = 콘솔 -keybind.rotate_alt.name = 반대로 돌기 +keybind.rotate_alt.name = 회전_alt keybind.rotate.name = 회전 -keybind.weapon_1.name = 무기 단축키_1 -keybind.weapon_2.name = 무기 단축키_2 -keybind.weapon_3.name = 무기 단축키_3 -keybind.weapon_4.name = 무기 단축키_4 -keybind.weapon_5.name = 무기 단축키_5 -keybind.weapon_6.name = 무기 단축키_6 -mode.text.help.title = 모드 설명 +mode.text.help.title = 도움말 mode.waves.name = 단계 -mode.waves.description = 이것은 일반 모드입니다. 제한된 자원과 자동으로 웨이브가 시작됩니다. +mode.waves.description = 이것은 일반 모드입니다. 제한된 자원과 자동으로 다음 단계가 시작됩니다. mode.sandbox.name = 샌드박스 -mode.sandbox.description = 무한한 자원과 웨이브를 시작하는 타이머가 없습니다. -mode.freebuild.name = 자유 건설 -mode.freebuild.description = 제한된 자원을 가지고 있으며, 웨이브를 시작하기 위한 타이머가 없습니다. -upgrade.standard-mech.name = 기본 -upgrade.standard-mech.description = 그냥 기본 총. -upgrade.standard-ship.name = 기본 배 -upgrade.standard-ship.description = 그냥 기본 배 -upgrade.blaster.name = 블래스터 -upgrade.blaster.description = 그냥 느리고 약한 총알. -upgrade.triblaster.name = 3단 블래스터 -upgrade.triblaster.description = 3발을 동시에 쏘는 총. -upgrade.clustergun.name = 유탄발사기 -upgrade.clustergun.description = 적에 맞으면 폭발하거나, 일정시간 후 터집니다. -upgrade.beam.name = 레이저 캐논 -upgrade.beam.description = 적을 통과하는 장거리 레이저 빔을 쏩니다 -upgrade.vulcan.name = 벌칸 -upgrade.vulcan.description = 빠른 속도로 총을 쏩니다 -upgrade.shockgun.name = 샷건 -upgrade.shockgun.description = 충전된 총알을 산탄처럼 쏘는 총 +mode.sandbox.description = 무한한 자원과 다음 단계 시작을 위한 타이머가 없습니다. +mode.freebuild.name = 자유 건축 +mode.freebuild.description = 제한된 자원과 다음 단계 시작을 위한 타이머가 없습니다. +content.item.name = 아이템 +content.liquid.name = 액체 +content.unit-type.name = 종류 +content.recipe.name = 블록 item.stone.name = 돌 -item.iron.name = 철 +item.stone.description = 흔히 찾을 수 있는 자원. 바닥에서 돌을 캐거나 용암을 사용하여 얻을 수 있습니다. +item.tungsten.name = 텅스텐 +item.tungsten.description = 일반적이지만 매우 유용한 건축 재료. 드릴 및 생산 건물, 제련소와 같은 내열성 블록에 사용됩니다. +item.lead.name = 납 +item.lead.description = 기본적인 시작 자원. 전자 및 액체 수송 블록에서 광범위하게 사용됩니다. item.coal.name = 석탄 -item.steel.name = 강철 +item.coal.description = 일반적이고 쉽게 이용할 수 있는 연료. +item.carbide.name = 합금 +item.carbide.description = 텅스텐과 탄소로 만든 합금. 고급 운송 블록 및 상위 티어 드릴에 사용됩니다. item.titanium.name = 티타늄 -item.dirium.name = 합금 +item.titanium.description = 물 운반이나 드릴, 비행기등에서 재료로 사용되는 자원입니다. item.thorium.name = 토륨 +item.thorium.description = 건물 탄약 또는 핵연료로 사용되는 방사성 금속. +item.silicon.name = 규소 +item.silcion.description = 매우 유용한 반도체로, 태양 전지 패널과 복잡한 전자 제품에 응용할 수 있습니다. +item.plastanium.name = Plastanium +item.plastanium.description = 고급 항공기 및 분열 탄약에 사용되는 가벼운 연성 재료. +item.phase-matter.name = Phase Matter +item.surge-alloy.name = Surge Alloy +item.biomatter.name = Biomatter +item.biomatter.description = 유기농 덤불; 석유로 전환하거나 기본 연료로 사용됩니다. item.sand.name = 모래 +item.sand.description = 합금 및 플럭스 모두에서 제련시 광범위하게 사용되는 일반적인 재료. +item.blast-compound.name = Blast Compound +item.blast-compound.description = 폭탄 및 폭발물에 사용되는 휘발성 화합물. 그것이 연료로 태울 수 있지만, 이것은 권고하지 않습니다. +item.pyratite.name = Pyratite +item.pyratite.description = 방화 용 무기에 사용되는 극히 가연성 물질. liquid.water.name = 물 -liquid.plasma.name = 플라즈마 liquid.lava.name = 용암 liquid.oil.name = 석유 -block.weaponfactory.name = 무기 공장 -block.weaponfactory.fulldescription = 플레이어용 무기를 만드는 데 사용됩니다.\n클릭하여 사용하십시오.\n코어에 있는 자원을 사용합니다. -block.air.name = 공기 -block.blockpart.name = 블록파트 -block.deepwater.name = 깊은 물 -block.water.name = 물 -block.lava.name = 용암 -block.oil.name = 석유 -block.stone.name = 돌 -block.blackstone.name = 검은 돌 -block.iron.name = 철 -block.coal.name = 석탄 -block.titanium.name = 티타늄 -block.thorium.name = 토륨 -block.dirt.name = 흙 -block.sand.name = 모래 -block.ice.name = 얼음 -block.snow.name = 눈 -block.grass.name = 잔디 -block.sandblock.name = 모래블럭 -block.snowblock.name = 얼음 블럭 -block.stoneblock.name = 돌 블럭 -block.blackstoneblock.name = 검은 돌 블럭 -block.grassblock.name = 잔디 블럭 -block.mossblock.name = 이끼블럭 -block.shrub.name = 덤불 -block.rock.name = 바위 -block.icerock.name = 얼음 덩어리 -block.blackrock.name = 검은 바위 -block.dirtblock.name = 흙 블럭 -block.stonewall.name = 돌 벽 -block.stonewall.fulldescription = 가장 안좋은 그냥 돌벽. 초반에 코어와 포탑을 보호하는데 사용 할 수 있습니다. -block.ironwall.name = 철 벽 -block.ironwall.fulldescription = 기본적인 벽 블럭. 적으로부터 보호해 줍니다. -block.steelwall.name = 강철 벽 -block.steelwall.fulldescription = 나름 좋은 벽 블럭. 적으로부터의 공격을 대부분 보호해 줍니다. -block.titaniumwall.name = 티타늄 벽 -block.titaniumwall.fulldescription = 강력한 벽 블럭. 적으로부터의 공격을 거의 보호해 줍니다 -block.duriumwall.name = 합금 벽 -block.duriumwall.fulldescription = 매우 강력한 벽 블록. 게임내에서 가장 강력한 벽 입니다. -block.compositewall.name = 복합 벽 -block.steelwall-large.name = 대형 강철 벽 -block.steelwall-large.fulldescription = 좋은 벽 블럭. 2x2 크기에 큰 벽입니다. -block.titaniumwall-large.name = 대형 티타늄 벽 -block.titaniumwall-large.fulldescription = 강력한 벽 블럭. 2x2 크기에 큰 벽입니다. -block.duriumwall-large.name = 대형 디리듐 벽 -block.duriumwall-large.fulldescription = 매우 강력한 벽 블록. 2x2 크기에 큰 벽입니다. -block.titaniumshieldwall.name = 보호막 벽 -block.titaniumshieldwall.fulldescription = 보호막이 내장 된 강력한 벽 블럭입니다.\n적의 총알을 흡수할 때 에너지를 사용하기 때문에 전력이 필요합니다.\n전력을 공급할때 무선 공급기를 사용하면 편리합니다 . -block.repairturret.name = 수리포탑 -block.repairturret.fulldescription = 범위 안에 있는 손상된 블록을 느린 속도로 수리합니다. 소량의 전력을 사용합니다. -block.megarepairturret.name = 수리포탑 II -block.megarepairturret.fulldescription = 범위 안에 있는 손상된 블록을 수리합니다. 전력을 사용합니다. -block.shieldgenerator.name = 보호막 발전기 -block.shieldgenerator.fulldescription = 고급 보호 블록. 반경 내의 모든 블록을 공격으로부터 보호합니다.\n작동 중일 때는 느린 속도로 전력을 사용하지만 공격을 받았을 시 그만큼의 전력을 소모하게 됩니다. +liquid.cryofluid.name = Cryofluid +text.item.explosiveness = [LIGHT_GRAY]폭발력 : {0} +text.item.flammability = [LIGHT_GRAY]인화성 : {0} +text.item.radioactivity = [LIGHT_GRAY]방사능 : {0} +text.item.fluxiness = [LIGHT_GRAY]Flux Power : {0} +text.item.hardness = [LIGHT_GRAY]강도 : {0} +text.liquid.heatcapacity = [LIGHT_GRAY]열용량 : {0} +text.liquid.viscosity = [LIGHT_GRAY]점도 : {0} +text.liquid.temperature = [LIGHT_GRAY]온도 : {0} +block.tungsten-wall.name = 텅스텐 장벽 +block.tungsten-wall-large.name = 큰 텅스텐 벽 +block.carbide-wall.name = 합금벽 +block.carbide-wall-large.name = 대형 합금벽 +block.thorium-wall.name = 토륨 장벽 +block.thorium-wall-large.name = 대형 토륨 벽 block.door.name = 문 -block.door.fulldescription = 블록을 탭하여 열거나 닫을 수 있습니다. -block.door-large.name = 대형 문 -block.door-large.fulldescription = 블록을 탭하여 열거나 닫을 수 있습니다. 2x2 크기 입니다. -block.conduit.name = 파이프 -block.conduit.fulldescription = 기본 액체 이송 블록. 컨베이어처럼 작동하지만 액체를 운반하는데 쓰입니다.\n펌프 또는 다른 파이프와 함께 사용하는 것이 가장 좋습니다.\n적과 플레이어가 액체를 건널때 쓰일수도 있습니다. -block.pulseconduit.name = 펄스 파이프 -block.pulseconduit.fulldescription = 파이프에 상위호환, 액체를 더 빨리 운반하고 파이프보다 더 많이 저장합니다. -block.liquidrouter.name = 액체 분배기 -block.liquidrouter.fulldescription = 분배기와 유사하게 작동합니다.\n한 방향으로 액체를 입력받아 다른 방향으로 출력합니다. 하나의 파이프에서 다른 파이프들로 액체를 분배할 때 유용합니다. +block.door-large.name = 큰 문 +block.duo.name = 샷건 +block.scorch.name = 물총 +block.hail.name = 헤이스트 +block.lancer.name = 팬선 block.conveyor.name = 컨베이어 -block.conveyor.fulldescription = 기본 컨베이어.\n아이템을 앞으로 운반시켜 자동으로 포탑이나 제작기에 넣어 줍니다.\n회전이 가능하고 적과 플레이어의 다리가 될 수도 있습니다. -block.steelconveyor.name = 강철 컨베이어 -block.steelconveyor.fulldescription = 빠른 컨베이어, 표준 컨베이어보다 빠르게 아이템을 운반합니다. -block.poweredconveyor.name = 펄스 컨베이어 -block.poweredconveyor.fulldescription = 가장 빠른 컨베이어. 강철 컨베이어보다 매우 항목을 이동합니다. -block.router.name = 분배기 -block.router.fulldescription = 한 방향으로 아이템을 받아 다른 방향으로 출력합니다.\n하나의 컨베이어에서 다른 컨베이어들로 아이템을 분배할 때 유용합니다 +block.titanium-conveyor.name = 티타늄 컨베이어 block.junction.name = 교차기 -block.junction.fulldescription = 두 개의 컨베이어를 교차시키는 역할을합니다.\n교차된 위치의 다른 재료를 담고있는 2개의 컨베이어가 있는 상황에서 유용합니다. -block.conveyortunnel.name = 컨베이어 터널 -block.conveyortunnel.fulldescription = 한블럭 단위로 아이템을 전송 합니다.\n이 블럭을 사용하려면 두 터널이 한블럭을 사이로 서로 반대 방향을 향하게하세요. -block.liquidjunction.name = 액체 교차기 -block.liquidjunction.fulldescription = 교차기와 유사하며 두 개 파이프를 교차시키는 역할을합니다.\n교차된 위치의 다른 액체를 담고있는 2개의 파이프가 있는 상황에서 유용합니다. -block.liquiditemjunction.name = 액체 아이템 교차기 -block.liquiditemjunction.fulldescription = 파이프와 컨베이어를 교차시키는 역할을합니다. -block.powerbooster.name = 무선 전력 공급기 -block.powerbooster.fulldescription = 반경 내에 무선으로 전력을 공급합니다. -block.powerlaser.name = 파워 레이저 -block.powerlaser.fulldescription = 반경 내에 바라보는 블록에 전력을 전송하는 레이저를 쏩니다.\n전력을 생성하지는 않고 발전기 또는 다른 레이저와 함께 사용하는 것이 가장 좋습니다. -block.powerlaserrouter.name = 레이저 분배기 -block.powerlaserrouter.fulldescription = 한 번에 세 방향으로 전력을 전송하는 레이저를 쏩니다.\n하나의 발전기에서 여러 개의 블록에 전원을 공급해야하는 경우에 유용합니다. -block.powerlasercorner.name = 코너 레이저 -block.powerlasercorner.fulldescription = 한 번에 두 방향으로 전력을 전송하는 레이저를 쏩니다.\n하나의 발전기에서 여러 개의 블록에 전원을 공급해야하는 상황에서 유용합니다 -block.teleporter.name = 텔레포터 -block.teleporter.fulldescription = 고급 아이템 전송 블록.\n텔레포터는 동일한 색깔의 다른 텔레포터에게 아이템을 전송합니다.\n같은 색의 텔레포터가 없다면 아무것도 하지 않습니다.\n동일한 색상의 여러 텔레포터가 있는 경우 임의의 하나에게 전송됩니다.\n전원을 사용하고, 눌러서 색깔을 바꿀수 있습니다. +block.splitter.name = 쪼개는 도구 +block.splitter.description = 항목을받은 직후 두 개의 반대 방향으로 항목을 출력합니다. +block.router.name = 분배기 +block.router.description = 아이템을 넣으면 다른 방향으로 아이템을 번갈아서 내보냅니다. +block.multiplexer.name = 멀티플렉서 +block.multiplexer.description = 항목을 8 방향으로 분리 할 수있는 라우터. block.sorter.name = 필터 -block.sorter.fulldescription = 유형별로 아이템을 정렬합니다.\n통과할 아이템은 블록의 색상으로 표시됩니다.\n통과된 아이템은 앞으로 출력 되고 나머지는 왼쪽과 오른쪽으로 출력됩니다. -block.splitter.name = 분리 -block.splitter.fulldescription = 들어오는 자원을 두 방향으로 분할해서 출력합니다.\n왼쪽과 오른쪽만 출력하고 라우터와 달리 내부 저장공간이 없고 즉시 출력됩니다. -block.core.name = 코어 -block.pump.name = 펌프 -block.pump.fulldescription = 물, 용암 또는 기름과 같은 액체를 퍼올립니다.\n붙어있는 파이프에게 액체를 배출합니다. -block.fluxpump.name = 플럭스 펌프 -block.fluxpump.fulldescription = 빠른 펌프. 더 많은 액체를 저장하고 액체를 더 빠르게 퍼올립니다. +block.sorter.description = 아이템을 받아서 설정된 아이템일 경우 바로 앞으로 통과하며, 그렇지 않을 경우 옆으로 통과합니다. +block.overflowgate.name = 오버플로 게이트 +block.overflowgate.description = 정면 경로가 차단되면 왼쪽 및 오른쪽으로 만 출력하는 조합 스플리터 및 라우터. +block.bridgeconveyor.name = 터널 +block.bridgeconveyor.description = 최대 2블록을 건너 뛰고 자원을 운반하게 해 주는 블럭. block.smelter.name = 제련소 -block.smelter.fulldescription = 필수적인 제작 블록.\n1개의 철과 1개의 석탄을 연료로 넣으면 하나의 강철을 생성합니다.\n막힘을 방지하기 위해 철과 석탄을 다른 컨베이어에 넣는 것이 좋습니다. -block.crucible.name = 합금 제련소 -block.crucible.fulldescription = 고급 제작 블록.\n티타늄 1개, 강철 1개, 석탄 1개를 연료로 넣으면 하나의 합금을 출력합니다.\n막힘을 방지하기 위해 석탄, 강철 및 티타늄을 다른 컨베이어에 입력하는 것이 좋습니다. -block.coalpurifier.name = 석탄 추출기 -block.coalpurifier.fulldescription = 간단한 추출기. 다량의 물과 돌이 공급되면 석탄을 생산합니다. -block.titaniumpurifier.name = 티타늄 추출기 -block.titaniumpurifier.fulldescription = 기본 추출기 블록. 다량의 물과 철이 공급되면 티타늄을 생산합니다. -block.oilrefinery.name = 정유소 -block.oilrefinery.fulldescription = 다량의 기름을 석탄으로 정제합니다.\n석탄이 부족할 때 석탄 기반 포탑에 연료를 공급하는 데 유용합니다. -block.stoneformer.name = 돌 생산기 -block.stoneformer.fulldescription = 용암을 돌로 바꿉니다. 석탄 추출기용 돌을 대량 생산할 때 유용합니다. -block.siliconextractor.name = 실리콘 추출기 +block.arc-smelter.name = 아크 제련소 +block.silicon-smelter.name = 실리콘 제련소 +block.phase-weaver.name = 위상 위버 block.pulverizer.name = 분쇄기 -block.quartzextractor.name = 석영 추출기 -block.lavasmelter.name = 용암 제련소 -block.lavasmelter.fulldescription = 용암을 사용하여 철을 강철로 전환합니다. 제련소의 대안으로 쓸수 있습니다.\n석탄이 부족한 상황에서 유용합니다. -block.stonedrill.name = 돌 드릴 -block.stonedrill.fulldescription = 필수 적인 드릴. 돌 타일 위에 놓을 때 느린속도로 채굴합니다. -block.irondrill.name = 철 드릴 -block.irondrill.fulldescription = 기본 드릴. 철 광석 타일에 놓으면 느린속도로 철을 채굴합니다. -block.coaldrill.name = 석탄 드릴 -block.coaldrill.fulldescription = 기본 드릴. 석탄 광석 타일 위에 놓으면 느린속도로 석탄을 채굴 합니다. -block.thoriumdrill.name = 토륨 드릴 -block.thoriumdrill.fulldescription = 고급 드릴. 토륨 광석 타일에 이 블럭을 놓으면, 느린 속도로 토륨을 채굴합니다. -block.titaniumdrill.name = 티타늄 드릴 -block.titaniumdrill.fulldescription = 고급 드릴. 티타늄 광석 타일 위에 놓으면 느린속도로 티타늄을 채굴합니다. -block.omnidrill.name = 옴니 드릴 -block.omnidrill.fulldescription = 빠른 속도로 모든 광석을 채굴 할 수 있습니다. -block.coalgenerator.name = 석탄 발전기 -block.coalgenerator.fulldescription = 필수적인 발전기. 석탄으로 전력을 생산합니다. 4면에 레이저로 전력을 출력합니다. -block.thermalgenerator.name = 지열 발전기 -block.thermalgenerator.fulldescription = 용암으로부터 전력을 생산합니다. 4면에 레이저로 전력을 출력합니다. -block.combustiongenerator.name = 석유 발전기 -block.combustiongenerator.fulldescription = 기름에서 전력을 생산합니다. 4면에 레이저로 전력을 출력합니다. -block.rtgenerator.name = 우라늄 발전기 -block.rtgenerator.fulldescription = 토륨에서 나오는 방사선으로 적은 량의 전력을 생산합니다.\n4면으로 전력이 출력됩니다. -block.nuclearreactor.name = 원자로 -block.nuclearreactor.fulldescription = 우라늄 발전기의 고급 버전과 궁극의 발전기.\n토륨에서 전력을 생산합니다.\n\n냉각수가 필요하며, 높은 휘발성을 가지고 있습니다.\n이 건물을 작동시키기 위해 연료로 토륨을 필요로 합니다. -block.turret.name = 포탑 -block.turret.fulldescription = 즉석에서 만들 수 있고 가장 쓰레기인 포탑.\n탄약으로 돌을 사용합니다.\n\n이중 포탑보다 약간 넓은 범위를가집니다. -block.doubleturret.name = 2단 포탑 -block.doubleturret.fulldescription = 포탑의 상위 버전입니다.\n탄약으로 돌을 사용합니다.\n\n더 많은 데미지를 주지만 범위는 낮습니다.\n총알 2발을 동시에 발사합니다. -block.machineturret.name = 게틀링 포탑 -block.machineturret.fulldescription = 표준적인 포탑.\n탄약으로 철을 사용합니다.\n\n적당한 데미지에 빠른 발사 속도를 가지고 있습니다. -block.shotgunturret.name = 스플리터 포탑 -block.shotgunturret.fulldescription = 표준적인 포탑.\n탄약으로 철을 사용합니다.\n\n한방에 7발을 발사합니다.\n범위가 낮지만 게틀링 포탑보다 높은 데미지를 가지고 있습니다. -block.flameturret.name = 화염 포탑 -block.flameturret.fulldescription = 고급 근거리 포탑.\n탄약으로 석탄을 사용합니다.\n\n범위는 낮지만 높은 데미지를 가지고 있습니다, 벽 뒤에 두는것이 좋습니다. -block.sniperturret.name = 레일건 포탑 -block.sniperturret.fulldescription = 고급 장거리 포탑.\n탄약에 강철을 사용합니다.\n\n매우 높은 데미지를 입힐 수 있지만 발사 속도는 낮습니다.\n사용 범위에 따라 적의 공격 범위 밖에서 공격할 수 있습니다. -block.mortarturret.name = 플랭크 포탑 -block.mortarturret.fulldescription = 고급형이자 정확도가 낮은 스플래쉬 데미지 포탑.\n탄약에 석탄을 사용합니다.\n\n파편으로 폭발하는 총알을 사용하고. 많은 적군에게 유용합니다. -block.laserturret.name = 레이저 포탑 -block.laserturret.fulldescription = 고급 단일 대상 공격 포탑.\n전력을 사용합니다.\n\n중간 크기의 사거리를 가지고 있고 절대 빗나가지 않습니다. -block.waveturret.name = 테슬라 포탑 -block.waveturret.fulldescription = 고급 다중 타겟 포탑.\n전력을 사용합니다.\n\n중간크기의 사거리를 가지고 있으며. 절대 빗나가지 않으므로 걱정할 염려가 없습니다.\n데미지는 거의 없지만 연속공격으로 여러 명의 적들을 동시에 공격 할 수 있습니다. -block.plasmaturret.name = 플라즈마 포탑 -block.plasmaturret.fulldescription = 포탑의 최고급 버전.\n석탄을 탄약으로 사용합니다.\n\n근접 사격 터렛의 끝판왕이며, 엄청나게 높은 데미지와 근거리와 중거리 사이 정도의 사거리를 가지고 있습니다. -block.chainturret.name = 체인 포탑 -block.chainturret.fulldescription = 궁국의 초고속 포탑.\n토륨을 탄약으로 사용합니다.\n\n엄청나게 빠른 공격속도를 가지고 있고, 중간 크기의 사거리를 가지고 있습니다.\n2x2 크기의 포탑입니다. -block.titancannon.name = 타이탄 캐논 -block.titancannon.fulldescription = 궁극의 장거리 포탑.\n토륨을 탄약으로 사용합니다.\n중간 수준의 사격 속도를 가지고 있으며 사거리가 매우 깁니다.\n\n3x3 크기의 포탑입니다. -block.playerspawn.name = 플레이어 스폰 지점 -block.enemyspawn.name = 적 스폰 지점 +block.cryofluidmixer.name = 냉동고 혼합기 +block.melter.name = 멜터 +block.incinerator.name = 소각로 +block.biomattercompressor.name = 바이오 매터 압축기 +block.separator.name = 분리 기호 +block.centrifuge.name = 원심 분리기 +block.power-node.name = 전원 노드 +block.power-node-large.name = 대형 전원 노드 +block.battery.name = 배터리 +block.battery-large.name = 대형 배터리 +block.combustion-generator.name = 연소 발전기 +block.turbine-generator.name = 터빈 발전기 +block.tungsten-drill.name = 텅스텐 드릴 +block.carbide-drill.name = 초경 드릴 +block.laser-drill.name = 레이저 드릴 +block.water-extractor.name = 물 추출기 +block.cultivator.name = 경운기 +block.dart-ship-factory.name = 다트 선박 공장 +block.delta-mech-factory.name = 델타 메크 공장 +block.dronefactory.name = 드론 팩토리 +block.repairpoint.name = 수리 점 +block.resupplypoint.name = 재 공급 포인트 +block.conduit.name = 도관 +block.pulseconduit.name = 펄스 도관 +block.liquidrouter.name = 액체 라우터 +block.liquidtank.name = 액체 탱크 +block.liquidjunction.name = 액체 정션 +block.bridgeconduit.name = 브릿지 도관 +block.mechanical-pump.name = 기계 펌프 +block.itemsource.name = 품목 출처 +block.itemvoid.name = 아이템 무효 +block.liquidsource.name = 액체 소스 +block.powervoid.name = 무효 전력 +block.powerinfinite.name = 무한한 힘 +block.unloader.name = 언 로더 +block.sortedunloader.name = 정렬 된 언 로더 +block.vault.name = 둥근 천장 +block.wave.name = 웨이브 +block.swarmer.name = 스머머 +block.salvo.name = 살보 +block.ripple.name = 리플 +block.phase-conveyor.name = 상 컨베이어 +block.overflow-gate.name = 오버플로 게이트 +block.bridge-conveyor.name = 브릿지 컨베이어 +block.plastanium-compressor.name = 플라스터 늄 압축기 +block.pyratite-mixer.name = Pyratite 믹서 +block.blast-mixer.name = 블래스트 믹서 +block.solidifer.name = 고체 +block.solar-panel.name = 태양 전지 패널 +block.solar-panel-large.name = 대형 태양 전지판 +block.oil-extractor.name = 오일 추출기 +block.javelin-ship-factory.name = 창 던지기 선박 공장 +block.drone-factory.name = 드론 팩토리 +block.fabricator-factory.name = Fabricator 공장 +block.repair-point.name = 수리 점 +block.resupply-point.name = 재 공급 포인트 +block.pulse-conduit.name = 펄스 도관 +block.phase-conduit.name = 위상 도관 +block.liquid-router.name = 액체 라우터 +block.liquid-tank.name = 액체 탱크 +block.liquid-junction.name = 액체 정션 +block.bridge-conduit.name = 브릿지 도관 +block.rotary-pump.name = 로타리 펌프 From fa0d89b6df2386f9e15549ba1e7ce867730b8534 Mon Sep 17 00:00:00 2001 From: Anuken Date: Thu, 5 Jul 2018 17:37:24 -0400 Subject: [PATCH 02/47] Fixed crash --- core/src/Mindustry.gwt.xml | 1 + core/src/io/anuke/mindustry/entities/TileEntity.java | 4 +++- core/src/io/anuke/mindustry/world/blocks/BuildBlock.java | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/core/src/Mindustry.gwt.xml b/core/src/Mindustry.gwt.xml index c7a38eeb99..7698085253 100644 --- a/core/src/Mindustry.gwt.xml +++ b/core/src/Mindustry.gwt.xml @@ -20,5 +20,6 @@ + \ No newline at end of file diff --git a/core/src/io/anuke/mindustry/entities/TileEntity.java b/core/src/io/anuke/mindustry/entities/TileEntity.java index e53425605f..75a4d1f1e5 100644 --- a/core/src/io/anuke/mindustry/entities/TileEntity.java +++ b/core/src/io/anuke/mindustry/entities/TileEntity.java @@ -170,7 +170,9 @@ public class TileEntity extends BaseEntity implements TargetTrait { @Remote(called = Loc.server, in = In.blocks) public static void onTileDamage(Tile tile, float health){ - tile.entity.health = health; + if(tile.entity != null){ + tile.entity.health = health; + } } @Remote(called = Loc.server, in = In.blocks) diff --git a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java index 31979b1aed..bd0cf5c971 100644 --- a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java @@ -213,7 +213,7 @@ public class BuildBlock extends Block { if (amount > 0) { //if it's positive, add it to the core int accepting = core.tile.block().acceptStack(requirements[i].item, accumulated, core.tile, builder); - core.tile.block().handleStack(requirements[i].item, accumulated, core.tile, builder); + core.tile.block().handleStack(requirements[i].item, accepting, core.tile, builder); accumulator[i] -= accepting; } From 2c97a4aefee67abd69673f74a01d1ee3e52c550a Mon Sep 17 00:00:00 2001 From: Anuken Date: Fri, 6 Jul 2018 09:52:18 -0400 Subject: [PATCH 03/47] Fixed importing saves / File chooser bad size --- core/assets/bundles/bundle.properties | 2 +- core/src/Mindustry.gwt.xml | 5 +-- core/src/io/anuke/mindustry/io/SaveIO.java | 3 +- .../mindustry/ui/dialogs/FileChooser.java | 2 +- .../mindustry/world/blocks/BuildBlock.java | 31 ++++++++++--------- .../world/mapgen/WorldGenerator.java | 2 +- .../anuke/mindustry/desktop/CrashHandler.java | 15 ++++++--- 7 files changed, 33 insertions(+), 27 deletions(-) diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index a33590bc35..480b1a2213 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -141,7 +141,7 @@ text.savefail=Failed to save game! text.save.delete.confirm=Are you sure you want to delete this save? text.save.delete=Delete text.save.export=Export Save -text.save.import.invalid=[orange]This save is invalid!\n\nNote that[scarlet]importing saves with custom maps[orange]\nfrom other devices does not work! +text.save.import.invalid=[orange]This save is invalid! text.save.import.fail=[crimson]Failed to import save: [orange]{0} text.save.export.fail=[crimson]Failed to export save: [orange]{0} text.save.import=Import Save diff --git a/core/src/Mindustry.gwt.xml b/core/src/Mindustry.gwt.xml index 7698085253..9f3caa7c8c 100644 --- a/core/src/Mindustry.gwt.xml +++ b/core/src/Mindustry.gwt.xml @@ -4,20 +4,17 @@ - - - - + diff --git a/core/src/io/anuke/mindustry/io/SaveIO.java b/core/src/io/anuke/mindustry/io/SaveIO.java index 4b3add8272..3f5397ce45 100644 --- a/core/src/io/anuke/mindustry/io/SaveIO.java +++ b/core/src/io/anuke/mindustry/io/SaveIO.java @@ -74,7 +74,7 @@ public class SaveIO{ } public static boolean isSaveValid(FileHandle file){ - return isSaveValid(new DataInputStream(file.read())); + return isSaveValid(new DataInputStream(new InflaterInputStream(file.read()))); } public static boolean isSaveValid(DataInputStream stream){ @@ -85,6 +85,7 @@ public class SaveIO{ ver.getData(stream); return true; }catch (Exception e){ + e.printStackTrace(); return false; } } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/FileChooser.java b/core/src/io/anuke/mindustry/ui/dialogs/FileChooser.java index b13324ca66..2e48a60898 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/FileChooser.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/FileChooser.java @@ -145,7 +145,7 @@ public class FileChooser extends FloatingDialog { content.add(icontable).expandX().fillX(); content.row(); - content.center().add(pane).width(UIUtils.portrait() ? Gdx.graphics.getWidth() : Gdx.graphics.getWidth()/Unit.dp.scl(2)).colspan(3).grow(); + content.center().add(pane).width(UIUtils.portrait() ? Gdx.graphics.getWidth()/Unit.dp.scl(1) : Gdx.graphics.getWidth()/Unit.dp.scl(2)).colspan(3).grow(); content.row(); if(!open){ diff --git a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java index bd0cf5c971..c1a05d53ff 100644 --- a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java @@ -107,6 +107,7 @@ public class BuildBlock extends Block { @Override public void drawLayer(Tile tile) { + BuildEntity entity = tile.entity(); Shaders.blockbuild.color = Palette.accent; @@ -174,17 +175,17 @@ public class BuildBlock extends Block { * If there is no recipe for this block, as is the case with rocks, 'previous' is used.*/ public Recipe recipe; - public double progress = 0; - public double lastProgress; - public double buildCost; + public float progress = 0; + public float lastProgress; + public float buildCost; /**The block that used to be here. * If a non-recipe block is being deconstructed, this is the block that is being deconstructed.*/ public Block previous; - private double[] accumulator; + private float[] accumulator; - public void construct(Unit builder, TileEntity core, double amount){ - double maxProgress = checkRequired(core.items, amount); + public void construct(Unit builder, TileEntity core, float amount){ + float maxProgress = checkRequired(core.items, amount); for (int i = 0; i < recipe.requirements.length; i++) { accumulator[i] += recipe.requirements[i].amount*maxProgress; //add min amount progressed to the accumulator @@ -201,7 +202,7 @@ public class BuildBlock extends Block { } } - public void deconstruct(Unit builder, TileEntity core, double amount){ + public void deconstruct(Unit builder, TileEntity core, float amount){ Recipe recipe = Recipe.getByResult(previous); if(recipe != null) { @@ -227,8 +228,8 @@ public class BuildBlock extends Block { } } - private double checkRequired(InventoryModule inventory, double amount){ - double maxProgress = amount; + private float checkRequired(InventoryModule inventory, float amount){ + float maxProgress = amount; for(int i = 0; i < recipe.requirements.length; i ++){ int required = (int)(accumulator[i]); //calculate items that are required now @@ -237,7 +238,7 @@ public class BuildBlock extends Block { //calculate how many items it can actually use int maxUse = Math.min(required, inventory.getItem(recipe.requirements[i].item)); //get this as a fraction - double fraction = maxUse / (double)required; + float fraction = maxUse / (float)required; //move max progress down if this fraction is less than 1 maxProgress = Math.min(maxProgress, maxProgress*fraction); @@ -259,7 +260,7 @@ public class BuildBlock extends Block { public void setConstruct(Block previous, Recipe recipe){ this.recipe = recipe; this.previous = previous; - this.accumulator = new double[recipe.requirements.length]; + this.accumulator = new float[recipe.requirements.length]; this.buildCost = recipe.cost; } @@ -268,7 +269,7 @@ public class BuildBlock extends Block { this.progress = 1f; if(Recipe.getByResult(previous) != null){ this.recipe = Recipe.getByResult(previous); - this.accumulator = new double[Recipe.getByResult(previous).requirements.length]; + this.accumulator = new float[Recipe.getByResult(previous).requirements.length]; this.buildCost = Recipe.getByResult(previous).cost; }else{ this.buildCost = 20f; //default no-recipe build cost is 20 @@ -285,8 +286,8 @@ public class BuildBlock extends Block { stream.writeByte(-1); }else{ stream.writeByte(accumulator.length); - for(double d : accumulator){ - stream.writeFloat((float)d); + for(float d : accumulator){ + stream.writeFloat(d); } } } @@ -299,7 +300,7 @@ public class BuildBlock extends Block { byte acsize = stream.readByte(); if(acsize != -1){ - accumulator = new double[acsize]; + accumulator = new float[acsize]; for (int i = 0; i < acsize; i++) { accumulator[i] = stream.readFloat(); } diff --git a/core/src/io/anuke/mindustry/world/mapgen/WorldGenerator.java b/core/src/io/anuke/mindustry/world/mapgen/WorldGenerator.java index 27b3044e7b..ef546070fb 100644 --- a/core/src/io/anuke/mindustry/world/mapgen/WorldGenerator.java +++ b/core/src/io/anuke/mindustry/world/mapgen/WorldGenerator.java @@ -240,7 +240,7 @@ public class WorldGenerator { prepareTiles(tiles, seed, true); } - static class OreEntry{ + public static class OreEntry{ final float frequency; final Item item; final Simplex noise; diff --git a/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java b/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java index 220400590b..e388752a41 100644 --- a/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java +++ b/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java @@ -1,5 +1,6 @@ package io.anuke.mindustry.desktop; +import io.anuke.mindustry.io.Version; import io.anuke.mindustry.net.Net; import io.anuke.ucore.core.Settings; import io.anuke.ucore.util.Strings; @@ -16,8 +17,12 @@ public class CrashHandler { //TODO send full error report to server via HTTP e.printStackTrace(); + boolean netActive = false, netServer = false; + //attempt to close connections, if applicable try{ + netActive = Net.active(); + netServer = Net.server(); Net.dispose(); }catch (Throwable p){ p.printStackTrace(); @@ -26,15 +31,17 @@ public class CrashHandler { //don't create crash logs for me (anuke), as it's expected if(System.getProperty("user.name").equals("anuke")) return; - String header = ""; + String header = "--CRASH REPORT--\n"; try{ header += "--GAME INFO-- \n"; - header += "Multithreading: " + Settings.getBool("multithread")+ "\n"; - header += "Net Active: " + Net.active()+ "\n"; - header += "Net Server: " + Net.server()+ "\n"; + header += "Build: " + Version.build + "\n"; + header += "Net Active: " + netActive + "\n"; + header += "Net Server: " + netServer + "\n"; header += "OS: " + System.getProperty("os.name")+ "\n----\n"; + header += "Multithreading: " + Settings.getBool("multithread")+ "\n"; }catch (Throwable e4){ + header += "[Error getting additional game info.]\n"; e4.printStackTrace(); } From c2c837329cd9fe62a7f2e2fc918041a85ed55d53 Mon Sep 17 00:00:00 2001 From: Anuken Date: Fri, 6 Jul 2018 10:54:20 -0400 Subject: [PATCH 04/47] Fixed block construction / Added build version to crash --- core/src/io/anuke/mindustry/io/Saves.java | 3 --- .../io/anuke/mindustry/world/Consumption.java | 4 ++++ .../mindustry/world/blocks/BuildBlock.java | 24 ++++++++++--------- .../anuke/mindustry/desktop/CrashHandler.java | 2 +- 4 files changed, 18 insertions(+), 15 deletions(-) create mode 100644 core/src/io/anuke/mindustry/world/Consumption.java diff --git a/core/src/io/anuke/mindustry/io/Saves.java b/core/src/io/anuke/mindustry/io/Saves.java index 2ed06d4739..2e09aa311d 100644 --- a/core/src/io/anuke/mindustry/io/Saves.java +++ b/core/src/io/anuke/mindustry/io/Saves.java @@ -2,7 +2,6 @@ package io.anuke.mindustry.io; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.utils.Array; -import com.badlogic.gdx.utils.async.AsyncExecutor; import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.game.Difficulty; import io.anuke.mindustry.game.GameMode; @@ -21,8 +20,6 @@ public class Saves { private boolean saving; private float time; - private AsyncExecutor exec = new AsyncExecutor(1); - public void load(){ saves.clear(); for(int i = 0; i < saveSlots; i ++){ diff --git a/core/src/io/anuke/mindustry/world/Consumption.java b/core/src/io/anuke/mindustry/world/Consumption.java new file mode 100644 index 0000000000..e3d491e50b --- /dev/null +++ b/core/src/io/anuke/mindustry/world/Consumption.java @@ -0,0 +1,4 @@ +package io.anuke.mindustry.world; + +public class Consumption { +} diff --git a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java index c1a05d53ff..d20e61a56c 100644 --- a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java @@ -176,7 +176,6 @@ public class BuildBlock extends Block { public Recipe recipe; public float progress = 0; - public float lastProgress; public float buildCost; /**The block that used to be here. * If a non-recipe block is being deconstructed, this is the block that is being deconstructed.*/ @@ -185,18 +184,16 @@ public class BuildBlock extends Block { private float[] accumulator; public void construct(Unit builder, TileEntity core, float amount){ - float maxProgress = checkRequired(core.items, amount); + float maxProgress = checkRequired(core.items, amount, false); for (int i = 0; i < recipe.requirements.length; i++) { accumulator[i] += recipe.requirements[i].amount*maxProgress; //add min amount progressed to the accumulator } - maxProgress = checkRequired(core.items, maxProgress); + maxProgress = checkRequired(core.items, maxProgress, true); progress = Mathf.clamp(progress + maxProgress); - lastProgress = maxProgress; - if(progress >= 1f){ CallBlocks.onConstructFinish(tile, recipe.result, builder.getID(), tile.getRotation()); } @@ -228,13 +225,15 @@ public class BuildBlock extends Block { } } - private float checkRequired(InventoryModule inventory, float amount){ + private float checkRequired(InventoryModule inventory, float amount, boolean remove){ float maxProgress = amount; for(int i = 0; i < recipe.requirements.length; i ++){ int required = (int)(accumulator[i]); //calculate items that are required now - if(required > 0){ //if this amount is positive... + if(inventory.getItem(recipe.requirements[i].item) == 0){ + maxProgress = 0f; + }else if(required > 0){ //if this amount is positive... //calculate how many items it can actually use int maxUse = Math.min(required, inventory.getItem(recipe.requirements[i].item)); //get this as a fraction @@ -243,9 +242,12 @@ public class BuildBlock extends Block { //move max progress down if this fraction is less than 1 maxProgress = Math.min(maxProgress, maxProgress*fraction); - //remove stuff that is actually used accumulator[i] -= maxUse; - inventory.removeItem(recipe.requirements[i].item, maxUse); + + //remove stuff that is actually used + if(remove) { + inventory.removeItem(recipe.requirements[i].item, maxUse); + } } //else, no items are required yet, so just keep going } @@ -254,7 +256,7 @@ public class BuildBlock extends Block { } public float progress(){ - return (float)progress; + return progress; } public void setConstruct(Block previous, Recipe recipe){ @@ -278,7 +280,7 @@ public class BuildBlock extends Block { @Override public void write(DataOutputStream stream) throws IOException { - stream.writeFloat((float)progress); + stream.writeFloat(progress); stream.writeShort(previous == null ? -1 : previous.id); stream.writeShort(recipe == null ? -1 : recipe.result.id); diff --git a/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java b/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java index e388752a41..1680ba4434 100644 --- a/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java +++ b/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java @@ -34,7 +34,7 @@ public class CrashHandler { String header = "--CRASH REPORT--\n"; try{ - header += "--GAME INFO-- \n"; + header += "--GAME INFO--\n"; header += "Build: " + Version.build + "\n"; header += "Net Active: " + netActive + "\n"; header += "Net Server: " + netServer + "\n"; From 64f1fbe400c0c854b854a4687003625128321051 Mon Sep 17 00:00:00 2001 From: Anuken Date: Fri, 6 Jul 2018 12:04:33 -0400 Subject: [PATCH 05/47] Fixed many various crashes --- .../annotations/RemoteWriteGenerator.java | 2 +- core/src/io/anuke/mindustry/core/Control.java | 10 +-- .../io/anuke/mindustry/core/NetServer.java | 4 +- core/src/io/anuke/mindustry/core/World.java | 4 +- .../mindustry/editor/MapEditorDialog.java | 6 +- .../anuke/mindustry/entities/effect/Fire.java | 2 +- .../anuke/mindustry/input/InputHandler.java | 67 +++++++++---------- .../ui/fragments/BlockInventoryFragment.java | 12 ++-- .../mindustry/world/blocks/BuildBlock.java | 2 + .../world/blocks/production/Separator.java | 1 + 10 files changed, 56 insertions(+), 54 deletions(-) diff --git a/annotations/src/io/anuke/annotations/RemoteWriteGenerator.java b/annotations/src/io/anuke/annotations/RemoteWriteGenerator.java index 1d80dcbc67..03be5844ac 100644 --- a/annotations/src/io/anuke/annotations/RemoteWriteGenerator.java +++ b/annotations/src/io/anuke/annotations/RemoteWriteGenerator.java @@ -129,7 +129,7 @@ public class RemoteWriteGenerator { method.beginControlFlow("if("+getCheckString(methodEntry.where)+")"); //add statement to create packet from pool - method.addStatement("$1N packet = $2N.obtain($1N.class)", "io.anuke.mindustry.net.Packets.InvokePacket", "com.badlogic.gdx.utils.Pools"); + method.addStatement("$1N packet = $2N.obtain($1N.class)", "io.anuke.mindustry.net.Packets.InvokePacket", "io.anuke.ucore.util.Pooling"); //assign buffer method.addStatement("packet.writeBuffer = TEMP_BUFFER"); //assign priority diff --git a/core/src/io/anuke/mindustry/core/Control.java b/core/src/io/anuke/mindustry/core/Control.java index 0fd0d87a66..70fce441cd 100644 --- a/core/src/io/anuke/mindustry/core/Control.java +++ b/core/src/io/anuke/mindustry/core/Control.java @@ -166,12 +166,14 @@ public class Control extends Module{ Player[] old = players; players = new Player[index + 1]; System.arraycopy(old, 0, players, 0, old.length); - - InputHandler[] oldi = inputs; - inputs = new InputHandler[index + 1]; - System.arraycopy(oldi, 0, inputs, 0, oldi.length); } + if(inputs.length != index + 1){ + InputHandler[] oldi = inputs; + inputs = new InputHandler[index + 1]; + System.arraycopy(oldi, 0, inputs, 0, oldi.length); + } + Player setTo = (index == 0 ? null : players[0]); Player player = new Player(); diff --git a/core/src/io/anuke/mindustry/core/NetServer.java b/core/src/io/anuke/mindustry/core/NetServer.java index c8659d38f6..22198bc7ae 100644 --- a/core/src/io/anuke/mindustry/core/NetServer.java +++ b/core/src/io/anuke/mindustry/core/NetServer.java @@ -494,14 +494,12 @@ public class NetServer extends Module{ return; } - String ip = player.con.address; - if(action == AdminAction.wave) { //no verification is done, so admins can hypothetically spam waves //not a real issue, because server owners may want to do just that state.wavetime = 0f; }else if(action == AdminAction.ban){ - netServer.admins.banPlayerIP(ip); + netServer.admins.banPlayerIP(other.con.address); netServer.kick(other.con.id, KickReason.banned); Log.info("&lc{0} has banned {1}.", player.name, other.name); }else if(action == AdminAction.kick){ diff --git a/core/src/io/anuke/mindustry/core/World.java b/core/src/io/anuke/mindustry/core/World.java index a139ecbf39..7dfaa3977f 100644 --- a/core/src/io/anuke/mindustry/core/World.java +++ b/core/src/io/anuke/mindustry/core/World.java @@ -93,11 +93,11 @@ public class World extends Module{ } public int width(){ - return tiles.length; + return tiles == null ? 0 : tiles.length; } public int height(){ - return tiles[0].length; + return tiles == null ? 0 : tiles[0].length; } public int toPacked(int x, int y){ diff --git a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java index d8624f7ef2..f1d0fa47db 100644 --- a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java +++ b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java @@ -368,13 +368,11 @@ public class MapEditorDialog extends Dialog implements Disposable{ public void updateSelectedBlock(){ Block block = editor.getDrawBlock(); - int i = 0; for(int j = 0; j < Block.all().size; j ++){ - if(block.id == j){ - blockgroup.getButtons().get(i).setChecked(true); + if(block.id == j && j < blockgroup.getButtons().size){ + blockgroup.getButtons().get(j).setChecked(true); break; } - i++; } } diff --git a/core/src/io/anuke/mindustry/entities/effect/Fire.java b/core/src/io/anuke/mindustry/entities/effect/Fire.java index 078bb4b1a0..37b559011b 100644 --- a/core/src/io/anuke/mindustry/entities/effect/Fire.java +++ b/core/src/io/anuke/mindustry/entities/effect/Fire.java @@ -91,7 +91,7 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable time = Mathf.clamp(time + Timers.delta(), 0, lifetime()); - if(time >= lifetime()){ + if(time >= lifetime() || tile == null){ CallEntity.onFireRemoved(getID()); remove(); } diff --git a/core/src/io/anuke/mindustry/input/InputHandler.java b/core/src/io/anuke/mindustry/input/InputHandler.java index aff1520cbd..57b3a99908 100644 --- a/core/src/io/anuke/mindustry/input/InputHandler.java +++ b/core/src/io/anuke/mindustry/input/InputHandler.java @@ -283,50 +283,49 @@ public abstract class InputHandler extends InputAdapter{ throw new ValidateException(player, "Player cannot transfer an item."); } - if(player == null) return; + threads.run(() -> { + if (player == null || tile.entity == null) return; - player.isTransferring = true; + player.isTransferring = true; - ItemStack stack = player.inventory.getItem(); - int accepted = tile.block().acceptStack(stack.item, stack.amount, tile, player); + ItemStack stack = player.inventory.getItem(); + int accepted = tile.block().acceptStack(stack.item, stack.amount, tile, player); - boolean clear = stack.amount == accepted; - int sent = Mathf.clamp(accepted/4, 1, 8); - int removed = accepted/sent; - int[] remaining = {accepted, accepted}; + boolean clear = stack.amount == accepted; + int sent = Mathf.clamp(accepted / 4, 1, 8); + int removed = accepted / sent; + int[] remaining = {accepted, accepted}; - for(int i = 0; i < sent; i ++){ - boolean end = i == sent-1; - Timers.run(i * 3, () -> { - tile.block().getStackOffset(stack.item, tile, stackTrns); + for (int i = 0; i < sent; i++) { + boolean end = i == sent - 1; + Timers.run(i * 3, () -> { + tile.block().getStackOffset(stack.item, tile, stackTrns); - ItemTransfer.create(stack.item, - player.x + Angles.trnsx(player.rotation + 180f, backTrns), player.y + Angles.trnsy(player.rotation + 180f, backTrns), - new Translator(tile.drawx() + stackTrns.x, tile.drawy() + stackTrns.y), () -> { + ItemTransfer.create(stack.item, + player.x + Angles.trnsx(player.rotation + 180f, backTrns), player.y + Angles.trnsy(player.rotation + 180f, backTrns), + new Translator(tile.drawx() + stackTrns.x, tile.drawy() + stackTrns.y), () -> { - tile.block().handleStack(stack.item, removed, tile, player); - remaining[1] -= removed; + tile.block().handleStack(stack.item, removed, tile, player); + remaining[1] -= removed; - if(end && remaining[1] > 0) { - tile.block().handleStack(stack.item, remaining[1], tile, player); - } - }); + if (end && remaining[1] > 0) { + tile.block().handleStack(stack.item, remaining[1], tile, player); + } + }); - stack.amount -= removed; - remaining[0] -= removed; + stack.amount -= removed; + remaining[0] -= removed; - if(end){ - stack.amount -= remaining[0]; - if(clear){ - player.inventory.clearItem(); + if (end) { + stack.amount -= remaining[0]; + if (clear) { + player.inventory.clearItem(); + } + player.isTransferring = false; } - player.isTransferring = false; - } - }); - } - - //ItemDrop.create(player.inventory.getItem().item, player.inventory.getItem().amount, player.x, player.y, angle); - //player.inventory.clearItem(); + }); + } + }); } @Remote(targets = Loc.both, called = Loc.server, forward = true, in = In.blocks) diff --git a/core/src/io/anuke/mindustry/ui/fragments/BlockInventoryFragment.java b/core/src/io/anuke/mindustry/ui/fragments/BlockInventoryFragment.java index 4ac97e9f02..16e0dc602c 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/BlockInventoryFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/BlockInventoryFragment.java @@ -59,7 +59,7 @@ public class BlockInventoryFragment extends Fragment { public void showFor(Tile t){ this.tile = t.target(); if(tile == null || tile.entity == null || !tile.block().isAccessible() || tile.entity.items.totalItems() == 0) return; - rebuild(); + rebuild(true); } public void hide(){ @@ -71,7 +71,7 @@ public class BlockInventoryFragment extends Fragment { tile = null; } - private void rebuild(){ + private void rebuild(boolean actions){ Player player = input.player; IntSet container = new IntSet(); @@ -99,7 +99,7 @@ public class BlockInventoryFragment extends Fragment { int[] items = tile.entity.items.items; for (int i = 0; i < items.length; i++) { if ((items[i] == 0) == container.contains(i)) { - rebuild(); + rebuild(false); } } } @@ -160,8 +160,10 @@ public class BlockInventoryFragment extends Fragment { updateTablePosition(); - table.actions(Actions.scaleTo(0f, 1f), Actions.visible(true), - Actions.scaleTo(1f, 1f, 0.07f, Interpolation.pow3Out)); + if(actions){ + table.actions(Actions.scaleTo(0f, 1f), Actions.visible(true), + Actions.scaleTo(1f, 1f, 0.07f, Interpolation.pow3Out)); + } } private String round(float f){ diff --git a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java index d20e61a56c..f672045459 100644 --- a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java @@ -100,6 +100,8 @@ public class BuildBlock extends Block { return; } + if(entity.previous == null) return; + for (TextureRegion region : entity.previous.getBlockIcon()) { Draw.rect(region, tile.drawx(), tile.drawy(), entity.previous.rotate ? tile.getRotation() * 90 : 0); } diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Separator.java b/core/src/io/anuke/mindustry/world/blocks/production/Separator.java index 4c3c981d69..b150976239 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Separator.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Separator.java @@ -50,6 +50,7 @@ public class Separator extends Block { stats.add(BlockStat.liquidUse, liquidUse * 60f, StatUnit.liquidSecond); stats.add(BlockStat.inputLiquid, liquid); + stats.add(BlockStat.inputItem, item); stats.add(BlockStat.outputItem, new ItemFilterValue(item -> { for(Item i : results){ if(item == i) return true; From d988bb18213aa92b48821f54400d51d12b17612d Mon Sep 17 00:00:00 2001 From: Anuken Date: Fri, 6 Jul 2018 23:24:14 -0400 Subject: [PATCH 06/47] Massive amount of bugfixes / Multi-liquid support / Broken build --- android/AndroidManifest.xml | 2 +- core/assets/bundles/bundle.properties | 5 +- .../io/anuke/mindustry/content/Liquids.java | 7 - .../mindustry/content/blocks/Blocks.java | 3 - .../mindustry/content/blocks/DebugBlocks.java | 7 +- .../content/blocks/DefenseBlocks.java | 4 +- .../content/blocks/StorageBlocks.java | 1 + .../content/blocks/TurretBlocks.java | 8 +- core/src/io/anuke/mindustry/core/Control.java | 9 +- core/src/io/anuke/mindustry/core/Logic.java | 13 +- .../io/anuke/mindustry/entities/Player.java | 3 + .../anuke/mindustry/entities/TileEntity.java | 21 +-- .../src/io/anuke/mindustry/entities/Unit.java | 1 + .../entities/effect/ItemTransfer.java | 2 +- .../mindustry/entities/units/types/Drone.java | 4 +- .../mindustry/input/DefaultKeybinds.java | 1 - .../anuke/mindustry/input/InputHandler.java | 2 +- .../ui/fragments/BlockInventoryFragment.java | 28 ++-- .../ui/fragments/BlocksFragment.java | 4 +- .../io/anuke/mindustry/world/BaseBlock.java | 78 ++++------ core/src/io/anuke/mindustry/world/Block.java | 101 ++++-------- .../io/anuke/mindustry/world/Consumption.java | 4 - core/src/io/anuke/mindustry/world/Tile.java | 6 +- .../mindustry/world/blocks/BlendBlock.java | 30 ---- .../mindustry/world/blocks/BuildBlock.java | 15 +- .../anuke/mindustry/world/blocks/Floor.java | 2 + .../mindustry/world/blocks/LiquidBlock.java | 23 ++- .../io/anuke/mindustry/world/blocks/Rock.java | 40 ++++- .../mindustry/world/blocks/defense/Door.java | 2 +- .../blocks/defense/turrets/CooledTurret.java | 9 +- .../blocks/defense/turrets/ItemTurret.java | 2 +- .../world/blocks/defense/turrets/Turret.java | 10 +- .../distribution/BufferedItemBridge.java | 4 +- .../world/blocks/distribution/Conduit.java | 25 +-- .../world/blocks/distribution/Conveyor.java | 8 +- .../distribution/ExtendingItemBridge.java | 8 +- .../world/blocks/distribution/ItemBridge.java | 24 ++- .../world/blocks/distribution/MassDriver.java | 10 +- .../world/blocks/distribution/Router.java | 8 +- .../blocks/distribution/TunnelConduit.java | 2 +- .../world/blocks/distribution/WarpGate.java | 14 +- .../world/blocks/modules/InventoryModule.java | 110 ------------- .../world/blocks/modules/LiquidModule.java | 28 ---- .../world/blocks/power/FusionReactor.java | 2 +- .../world/blocks/power/ItemGenerator.java | 8 +- .../blocks/power/ItemLiquidGenerator.java | 2 +- .../world/blocks/power/NuclearReactor.java | 10 +- .../world/blocks/production/Compressor.java | 6 +- .../world/blocks/production/Cultivator.java | 2 +- .../world/blocks/production/Drill.java | 6 +- .../world/blocks/production/Fracker.java | 9 +- .../blocks/production/GenericCrafter.java | 8 +- .../world/blocks/production/LiquidMixer.java | 6 +- .../world/blocks/production/PhaseWeaver.java | 2 +- .../world/blocks/production/PowerCrafter.java | 6 +- .../world/blocks/production/PowerSmelter.java | 20 +-- .../world/blocks/production/Pulverizer.java | 12 +- .../world/blocks/production/Separator.java | 21 ++- .../world/blocks/production/Smelter.java | 24 +-- .../world/blocks/production/SolidPump.java | 2 +- .../world/blocks/storage/CoreBlock.java | 2 +- .../world/blocks/storage/SortedUnloader.java | 6 +- .../world/blocks/storage/Unloader.java | 6 +- .../mindustry/world/blocks/storage/Vault.java | 6 +- .../world/blocks/units/DropPoint.java | 2 +- .../world/blocks/units/Reconstructor.java | 2 +- .../world/blocks/units/ResupplyPoint.java | 2 +- .../world/blocks/units/UnitFactory.java | 8 +- .../mindustry/world/consumers/Consume.java | 8 + .../world/modules/InventoryModule.java | 145 ++++++++++++++++++ .../mindustry/world/modules/LiquidModule.java | 95 ++++++++++++ .../{blocks => }/modules/PowerModule.java | 2 +- packer/build.gradle | 9 ++ .../io/anuke/mindustry/BundleLauncher.java | 17 ++ 74 files changed, 633 insertions(+), 511 deletions(-) delete mode 100644 core/src/io/anuke/mindustry/world/Consumption.java delete mode 100644 core/src/io/anuke/mindustry/world/blocks/BlendBlock.java delete mode 100644 core/src/io/anuke/mindustry/world/blocks/modules/InventoryModule.java delete mode 100644 core/src/io/anuke/mindustry/world/blocks/modules/LiquidModule.java create mode 100644 core/src/io/anuke/mindustry/world/consumers/Consume.java create mode 100644 core/src/io/anuke/mindustry/world/modules/InventoryModule.java create mode 100644 core/src/io/anuke/mindustry/world/modules/LiquidModule.java rename core/src/io/anuke/mindustry/world/{blocks => }/modules/PowerModule.java (94%) create mode 100644 packer/src/io/anuke/mindustry/BundleLauncher.java diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 342222acf8..64fc9d218b 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -20,7 +20,7 @@ diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 480b1a2213..b8a2fb2501 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -461,8 +461,8 @@ block.multiplexer.name=Multiplexer block.multiplexer.description=A router that can split items into 8 directions. block.sorter.name=Sorter block.sorter.description=Sorts items. If an item matches the selection, it is allowed to pass. Otherwise, the item is outputted to the left and right. -block.overflowgate.name=Overflow Gate -block.overflowgate.description=A combination splitter and router that only outputs to the left and right if the front path is blocked. +block.overflow-gate.name=Overflow Gate +block.overflow-gate.description=A combination splitter and router that only outputs to the left and right if the front path is blocked. block.bridgeconveyor.name=Bridge Conveyor block.bridgeconveyor.description=A conveyor that can go over other blocks, for up to two total blocks. block.smelter.name=Smelter @@ -512,7 +512,6 @@ block.swarmer.name=Swarmer block.salvo.name=Salvo block.ripple.name=Ripple block.phase-conveyor.name=Phase Conveyor -block.overflow-gate.name=Overflow Gate block.bridge-conveyor.name=Bridge Conveyor block.plastanium-compressor.name=Plastanium Compressor block.pyratite-mixer.name=Pyratite Mixer diff --git a/core/src/io/anuke/mindustry/content/Liquids.java b/core/src/io/anuke/mindustry/content/Liquids.java index e543988bc8..097d708a30 100644 --- a/core/src/io/anuke/mindustry/content/Liquids.java +++ b/core/src/io/anuke/mindustry/content/Liquids.java @@ -12,13 +12,6 @@ public class Liquids implements ContentList { @Override public void load() { - none = new Liquid("none", Color.CLEAR){ - @Override - public boolean isHidden(){ - return true; - } - }; - water = new Liquid("water", Color.valueOf("486acd")) { { heatCapacity = 0.4f; diff --git a/core/src/io/anuke/mindustry/content/blocks/Blocks.java b/core/src/io/anuke/mindustry/content/blocks/Blocks.java index cba522baa1..86a239c042 100644 --- a/core/src/io/anuke/mindustry/content/blocks/Blocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/Blocks.java @@ -145,17 +145,14 @@ public class Blocks extends BlockList implements ContentList{ rock = new Rock("rock") {{ variants = 2; - varyShadow = true; }}; icerock = new Rock("icerock") {{ variants = 2; - varyShadow = true; }}; blackrock = new Rock("blackrock") {{ variants = 1; - varyShadow = true; }}; } } diff --git a/core/src/io/anuke/mindustry/content/blocks/DebugBlocks.java b/core/src/io/anuke/mindustry/content/blocks/DebugBlocks.java index 023e8bbc3c..ff875423f9 100644 --- a/core/src/io/anuke/mindustry/content/blocks/DebugBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/DebugBlocks.java @@ -56,7 +56,7 @@ public class DebugBlocks extends BlockList implements ContentList{ @Override public void update(Tile tile) { SorterEntity entity = tile.entity(); - entity.items.items[entity.sortItem.id] = 1; + entity.items.set(entity.sortItem, 1); tryDump(tile, entity.sortItem); } @@ -79,9 +79,8 @@ public class DebugBlocks extends BlockList implements ContentList{ public void update(Tile tile) { LiquidSourceEntity entity = tile.entity(); - tile.entity.liquids.amount = liquidCapacity; - tile.entity.liquids.liquid = entity.source; - tryDumpLiquid(tile); + tile.entity.liquids.add(entity.source, liquidCapacity); + tryDumpLiquid(tile, entity.source); } @Override diff --git a/core/src/io/anuke/mindustry/content/blocks/DefenseBlocks.java b/core/src/io/anuke/mindustry/content/blocks/DefenseBlocks.java index 709e27ffd4..761f05b7b8 100644 --- a/core/src/io/anuke/mindustry/content/blocks/DefenseBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/DefenseBlocks.java @@ -35,11 +35,11 @@ public class DefenseBlocks extends BlockList implements ContentList { }}; thoriumWall = new Wall("thorium-wall") {{ - health = 110 * wallHealthMultiplier; + health = 200 * wallHealthMultiplier; }}; thoriumWallLarge = new Wall("thorium-wall-large") {{ - health = 110 * wallHealthMultiplier*4; + health = 200 * wallHealthMultiplier*4; size = 2; }}; diff --git a/core/src/io/anuke/mindustry/content/blocks/StorageBlocks.java b/core/src/io/anuke/mindustry/content/blocks/StorageBlocks.java index 4e5a2953c9..0d736832cb 100644 --- a/core/src/io/anuke/mindustry/content/blocks/StorageBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/StorageBlocks.java @@ -19,6 +19,7 @@ public class StorageBlocks extends BlockList implements ContentList { vault = new Vault("vault") {{ size = 3; health = 600; + itemCapacity = 2000; }}; unloader = new Unloader("unloader") {{ diff --git a/core/src/io/anuke/mindustry/content/blocks/TurretBlocks.java b/core/src/io/anuke/mindustry/content/blocks/TurretBlocks.java index 616dea4e7e..c993ec8fbc 100644 --- a/core/src/io/anuke/mindustry/content/blocks/TurretBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/TurretBlocks.java @@ -73,10 +73,10 @@ public class TurretBlocks extends BlockList implements ContentList { health = 360; drawer = (tile, entity) -> { - Draw.rect(name, tile.drawx() + tr2.x, tile.drawy() + tr2.y, entity.rotation - 90); + Draw.rect(region, tile.drawx() + tr2.x, tile.drawy() + tr2.y, entity.rotation - 90); - Draw.color(entity.liquids.liquid.color); - Draw.alpha(entity.liquids.amount / liquidCapacity); + Draw.color(entity.liquids.current().color); + Draw.alpha(entity.liquids.total() / liquidCapacity); Draw.rect(name + "-liquid", tile.drawx() + tr2.x, tile.drawy() + tr2.y, entity.rotation - 90); Draw.color(); }; @@ -145,7 +145,7 @@ public class TurretBlocks extends BlockList implements ContentList { ammoUseEffect = ShootFx.shellEjectBig; drawer = (tile, entity) -> { - Draw.rect(name, tile.drawx() + tr2.x, tile.drawy() + tr2.y, entity.rotation - 90); + Draw.rect(region, tile.drawx() + tr2.x, tile.drawy() + tr2.y, entity.rotation - 90); float offsetx = (int) (Mathf.abscurve(Mathf.curve(entity.reload / reload, 0.3f, 0.2f)) * 3f); float offsety = -(int) (Mathf.abscurve(Mathf.curve(entity.reload / reload, 0.3f, 0.2f)) * 2f); diff --git a/core/src/io/anuke/mindustry/core/Control.java b/core/src/io/anuke/mindustry/core/Control.java index 70fce441cd..1c53e73208 100644 --- a/core/src/io/anuke/mindustry/core/Control.java +++ b/core/src/io/anuke/mindustry/core/Control.java @@ -20,7 +20,6 @@ import io.anuke.mindustry.input.MobileInput; import io.anuke.mindustry.io.Map; import io.anuke.mindustry.io.Saves; import io.anuke.mindustry.net.Net; -import io.anuke.mindustry.type.Item; import io.anuke.mindustry.type.Recipe; import io.anuke.mindustry.ui.dialogs.FloatingDialog; import io.anuke.ucore.core.*; @@ -264,11 +263,7 @@ public class Control extends Module{ if(entity == null) return; - for (int i = 0; i < entity.items.items.length; i++) { - if(entity.items.items[i] <= 0) continue; - Item item = Item.getByID(i); - control.database().unlockContent(item); - } + entity.items.forEach((item, amount) -> control.database().unlockContent(item)); if(players[0].inventory.hasItem()){ control.database().unlockContent(players[0].inventory.getItem().item); @@ -276,7 +271,7 @@ public class Control extends Module{ for(int i = 0 ; i < Recipe.all().size; i ++){ Recipe recipe = Recipe.all().get(i); - if(!recipe.debugOnly && entity.items.hasItems(recipe.requirements, 1.4f)){ + if(!recipe.debugOnly && entity.items.has(recipe.requirements, 1.4f)){ if(control.database().unlockContent(recipe)){ ui.hudfrag.showUnlock(recipe); } diff --git a/core/src/io/anuke/mindustry/core/Logic.java b/core/src/io/anuke/mindustry/core/Logic.java index b137d380f0..d065be9098 100644 --- a/core/src/io/anuke/mindustry/core/Logic.java +++ b/core/src/io/anuke/mindustry/core/Logic.java @@ -54,12 +54,12 @@ public class Logic extends Module { if(debug) { for (Item item : Item.all()) { if (item.type == ItemType.material) { - tile.entity.items.addItem(item, 1000); + tile.entity.items.add(item, 1000); } } }else{ - tile.entity.items.addItem(Items.tungsten, 50); - tile.entity.items.addItem(Items.lead, 20); + tile.entity.items.add(Items.tungsten, 50); + tile.entity.items.add(Items.lead, 20); } } } @@ -158,13 +158,6 @@ public class Logic extends Module { for(EntityGroup group : unitGroups){ if(!group.isEmpty()){ EntityPhysics.collideGroups(bulletGroup, group); - - /* - for(EntityGroup other : unitGroups){ - if(!other.isEmpty()){ - EntityPhysics.collideGroups(group, other); - } - }*/ } } diff --git a/core/src/io/anuke/mindustry/entities/Player.java b/core/src/io/anuke/mindustry/entities/Player.java index 42c14c2a38..612b9bd379 100644 --- a/core/src/io/anuke/mindustry/entities/Player.java +++ b/core/src/io/anuke/mindustry/entities/Player.java @@ -9,6 +9,7 @@ import com.badlogic.gdx.utils.Queue; import io.anuke.annotations.Annotations.Loc; import io.anuke.annotations.Annotations.Remote; import io.anuke.mindustry.Vars; +import io.anuke.mindustry.content.Mechs; import io.anuke.mindustry.entities.effect.ItemDrop; import io.anuke.mindustry.entities.effect.ScorchDecal; import io.anuke.mindustry.entities.traits.*; @@ -660,6 +661,8 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra dead = true; trail.clear(); health = maxHealth(); + mech = (mobile ? Mechs.starterMobile : Mechs.starterDesktop); + placeQueue.clear(); add(); } diff --git a/core/src/io/anuke/mindustry/entities/TileEntity.java b/core/src/io/anuke/mindustry/entities/TileEntity.java index 75a4d1f1e5..3347ed4ccf 100644 --- a/core/src/io/anuke/mindustry/entities/TileEntity.java +++ b/core/src/io/anuke/mindustry/entities/TileEntity.java @@ -1,6 +1,7 @@ package io.anuke.mindustry.entities; import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.Array; import io.anuke.annotations.Annotations.Loc; import io.anuke.annotations.Annotations.Remote; import io.anuke.mindustry.content.fx.Fx; @@ -10,11 +11,12 @@ import io.anuke.mindustry.game.Team; import io.anuke.mindustry.gen.CallBlocks; import io.anuke.mindustry.net.In; import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.consumers.Consume; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.Wall; -import io.anuke.mindustry.world.blocks.modules.InventoryModule; -import io.anuke.mindustry.world.blocks.modules.LiquidModule; -import io.anuke.mindustry.world.blocks.modules.PowerModule; +import io.anuke.mindustry.world.modules.InventoryModule; +import io.anuke.mindustry.world.modules.LiquidModule; +import io.anuke.mindustry.world.modules.PowerModule; import io.anuke.ucore.core.Effects; import io.anuke.ucore.core.Timers; import io.anuke.ucore.entities.EntityGroup; @@ -42,6 +44,8 @@ public class TileEntity extends BaseEntity implements TargetTrait { public InventoryModule items; public LiquidModule liquids; + public Array consumers = new Array<>(); + private boolean dead = false; private boolean sleeping; private float sleepTime; @@ -55,14 +59,10 @@ public class TileEntity extends BaseEntity implements TargetTrait { health = tile.block().health; timer = new Timer(tile.block().timers); + tile.block().setConsumers(consumers); if(added){ - //if(!tile.block().autoSleep) { //TODO only autosleep when creating a fresh block! - add(); - /*}else{ - sleeping = true; - sleepingEntities ++; - }*/ + add(); } return this; @@ -160,6 +160,9 @@ public class TileEntity extends BaseEntity implements TargetTrait { } tile.block().update(tile); + for(Consume cons : consumers){ + cons.update(this); + } } } diff --git a/core/src/io/anuke/mindustry/entities/Unit.java b/core/src/io/anuke/mindustry/entities/Unit.java index 4808b740a4..80bbd239a7 100644 --- a/core/src/io/anuke/mindustry/entities/Unit.java +++ b/core/src/io/anuke/mindustry/entities/Unit.java @@ -197,6 +197,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ } public void avoidOthers(float avoidRange){ + if(Net.client()) return; EntityPhysics.getNearby(getGroup(), x, y, avoidRange*2f, t -> { if(t == this || (t instanceof Unit && (((Unit) t).isDead() || (((Unit) t).isFlying() != isFlying()) || ((Unit) t).getCarrier() == this) || getCarrier() == t)) return; diff --git a/core/src/io/anuke/mindustry/entities/effect/ItemTransfer.java b/core/src/io/anuke/mindustry/entities/effect/ItemTransfer.java index 340073caa4..2e119b99ea 100644 --- a/core/src/io/anuke/mindustry/entities/effect/ItemTransfer.java +++ b/core/src/io/anuke/mindustry/entities/effect/ItemTransfer.java @@ -57,7 +57,7 @@ public class ItemTransfer extends TimedEntity implements DrawTrait{ for (int i = 0; i < Mathf.clamp(amount/3, 1, 8); i++) { Timers.run(i*3, () -> create(item, x, y, tile, () -> {})); } - tile.entity.items.addItem(item, amount); + tile.entity.items.add(item, amount); } public static void create(Item item, float fromx, float fromy, PosTrait to, Runnable done){ diff --git a/core/src/io/anuke/mindustry/entities/units/types/Drone.java b/core/src/io/anuke/mindustry/entities/units/types/Drone.java index 89d7885892..f26a6796df 100644 --- a/core/src/io/anuke/mindustry/entities/units/types/Drone.java +++ b/core/src/io/anuke/mindustry/entities/units/types/Drone.java @@ -181,7 +181,7 @@ public class Drone extends FlyingUnit implements BuilderTrait { if(entity == null){ return; } - targetItem = Mathf.findMin(toMine, (a, b) -> -Integer.compare(entity.items.getItem(a), entity.items.getItem(b))); + targetItem = Mathf.findMin(toMine, (a, b) -> -Integer.compare(entity.items.get(a), entity.items.get(b))); } protected boolean findItemDrop(){ @@ -247,7 +247,7 @@ public class Drone extends FlyingUnit implements BuilderTrait { //if it's missing requirements, try and mine them for(ItemStack stack : entity.recipe.requirements){ - if(!core.items.hasItem(stack.item, stack.amount) && toMine.contains(stack.item)){ + if(!core.items.has(stack.item, stack.amount) && toMine.contains(stack.item)){ targetItem = stack.item; getPlaceQueue().clear(); setState(mine); diff --git a/core/src/io/anuke/mindustry/input/DefaultKeybinds.java b/core/src/io/anuke/mindustry/input/DefaultKeybinds.java index 5c9bfafa21..4941ba130e 100644 --- a/core/src/io/anuke/mindustry/input/DefaultKeybinds.java +++ b/core/src/io/anuke/mindustry/input/DefaultKeybinds.java @@ -32,7 +32,6 @@ public class DefaultKeybinds { "menu", Gdx.app.getType() == ApplicationType.Android ? Input.BACK : Input.ESCAPE, "pause", Input.SPACE, "toggle_menus", Input.C, - "item_withdraw", Input.SHIFT_LEFT, new Category("Multiplayer"), "player_list", Input.TAB, "chat", Input.ENTER, diff --git a/core/src/io/anuke/mindustry/input/InputHandler.java b/core/src/io/anuke/mindustry/input/InputHandler.java index 57b3a99908..68e048cf3e 100644 --- a/core/src/io/anuke/mindustry/input/InputHandler.java +++ b/core/src/io/anuke/mindustry/input/InputHandler.java @@ -129,7 +129,7 @@ public abstract class InputHandler extends InputAdapter{ //consume tap event if necessary if(tile.getTeam() == player.getTeam() && tile.block().consumesTap){ consumed = true; - }else if(tile.getTeam() == player.getTeam() && tile.block().synthetic() && tile.block().hasItems && !consumed){ + }else if(tile.getTeam() == player.getTeam() && tile.block().synthetic() && tile.block().hasItems && tile.entity.items.total() > 0 && !consumed){ frag.inv.showFor(tile); consumed = true; showedInventory = true; diff --git a/core/src/io/anuke/mindustry/ui/fragments/BlockInventoryFragment.java b/core/src/io/anuke/mindustry/ui/fragments/BlockInventoryFragment.java index 16e0dc602c..bbfc861d77 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/BlockInventoryFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/BlockInventoryFragment.java @@ -16,7 +16,6 @@ import io.anuke.mindustry.type.Item; import io.anuke.mindustry.ui.ItemImage; import io.anuke.mindustry.world.Tile; import io.anuke.ucore.core.Graphics; -import io.anuke.ucore.core.Inputs; import io.anuke.ucore.core.Timers; import io.anuke.ucore.function.BooleanProvider; import io.anuke.ucore.scene.Group; @@ -29,9 +28,7 @@ import io.anuke.ucore.scene.ui.layout.Table; import io.anuke.ucore.util.Mathf; import io.anuke.ucore.util.Strings; -import static io.anuke.mindustry.Vars.mobile; -import static io.anuke.mindustry.Vars.state; -import static io.anuke.mindustry.Vars.tilesize; +import static io.anuke.mindustry.Vars.*; public class BlockInventoryFragment extends Fragment { private final static float holdWithdraw = 40f; @@ -58,7 +55,7 @@ public class BlockInventoryFragment extends Fragment { public void showFor(Tile t){ this.tile = t.target(); - if(tile == null || tile.entity == null || !tile.block().isAccessible() || tile.entity.items.totalItems() == 0) return; + if(tile == null || tile.entity == null || !tile.block().isAccessible() || tile.entity.items.total() == 0) return; rebuild(true); } @@ -80,14 +77,14 @@ public class BlockInventoryFragment extends Fragment { table.background("inventory"); table.setTouchable(Touchable.enabled); table.update(() -> { - if(tile == null || tile.entity == null || !tile.block().isAccessible() || tile.entity.items.totalItems() == 0){ + if(tile == null || tile.entity == null || !tile.block().isAccessible() || tile.entity.items.total() == 0){ hide(); }else{ if(holding && lastItem != null){ holdTime += Timers.delta(); if(holdTime >= holdWithdraw){ - int amount = Math.min(tile.entity.items.getItem(lastItem), player.inventory.itemCapacityUsed(lastItem)); + int amount = Math.min(tile.entity.items.get(lastItem), player.inventory.itemCapacityUsed(lastItem)); CallBlocks.requestItem(player, tile, lastItem, amount); holding = false; holdTime = 0f; @@ -96,9 +93,8 @@ public class BlockInventoryFragment extends Fragment { updateTablePosition(); if(tile.block().hasItems) { - int[] items = tile.entity.items.items; - for (int i = 0; i < items.length; i++) { - if ((items[i] == 0) == container.contains(i)) { + for (int i = 0; i < Item.all().size; i++) { + if ((tile.entity.items.has(Item.getByID(i))) == container.contains(i)) { rebuild(false); } } @@ -113,12 +109,10 @@ public class BlockInventoryFragment extends Fragment { table.defaults().size(mobile ? 16*3 : 16*2).space(6f); if(tile.block().hasItems) { - int[] items = tile.entity.items.items; - for (int i = 0; i < items.length; i++) { - final int f = i; - if (items[i] == 0) continue; + for (int i = 0; i < Item.all().size; i++) { Item item = Item.getByID(i); + if (!tile.entity.items.has(item)) continue; container.add(i); @@ -127,14 +121,14 @@ public class BlockInventoryFragment extends Fragment { HandCursorListener l = new HandCursorListener(); l.setEnabled(canPick); - ItemImage image = new ItemImage(item.region, () -> round(items[f])); + ItemImage image = new ItemImage(item.region, () -> round(tile.entity.items.get(item))); image.addListener(l); image.addListener(new InputListener(){ @Override public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) { - if(!canPick.get() || items[f] == 0) return false; - int amount = Math.min(Inputs.keyDown("item_withdraw") ? items[f] : 1, player.inventory.itemCapacityUsed(item)); + if(!canPick.get() || !tile.entity.items.has(item)) return false; + int amount = Math.min(1, player.inventory.itemCapacityUsed(item)); CallBlocks.requestItem(player, tile, item, amount); lastItem = item; holding = true; diff --git a/core/src/io/anuke/mindustry/ui/fragments/BlocksFragment.java b/core/src/io/anuke/mindustry/ui/fragments/BlocksFragment.java index ddff5a96ce..673ac7c2aa 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/BlocksFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/BlocksFragment.java @@ -244,7 +244,7 @@ public class BlocksFragment extends Fragment{ if(entity == null) return; for(ItemStack s : r.requirements){ - if(!entity.items.hasItem(s.item, Mathf.ceil(s.amount))){ + if(!entity.items.has(s.item, Mathf.ceil(s.amount))){ istack.setColor(Color.GRAY); return; } @@ -328,7 +328,7 @@ public class BlocksFragment extends Fragment{ TileEntity core = players[0].getClosestCore(); if(core == null) return "*/*"; - int amount = core.items.getItem(stack.item); + int amount = core.items.get(stack.item); String color = (amount < stack.amount/2f ? "[red]" : amount < stack.amount ? "[orange]" : "[white]"); return color + format(amount) + "[white]/" + stack.amount; diff --git a/core/src/io/anuke/mindustry/world/BaseBlock.java b/core/src/io/anuke/mindustry/world/BaseBlock.java index 9087faf510..2913a18222 100644 --- a/core/src/io/anuke/mindustry/world/BaseBlock.java +++ b/core/src/io/anuke/mindustry/world/BaseBlock.java @@ -31,20 +31,20 @@ public abstract class BaseBlock { } public int getMaximumAccepted(Tile tile, Item item){ - return itemCapacity - tile.entity.items.totalItems(); + return itemCapacity - tile.entity.items.total(); } /**Remove a stack from this inventory, and return the amount removed.*/ public int removeStack(Tile tile, Item item, int amount){ tile.entity.wakeUp(); - tile.entity.items.removeItem(item, amount); + tile.entity.items.remove(item, amount); return amount; } /**Handle a stack input.*/ public void handleStack(Item item, int amount, Tile tile, Unit source){ tile.entity.wakeUp(); - tile.entity.items.addItem(item, amount); + tile.entity.items.add(item, amount); } /**Returns offset for stack placement.*/ @@ -53,7 +53,7 @@ public abstract class BaseBlock { } public void handleItem(Item item, Tile tile, Tile source){ - tile.entity.items.addItem(item, 1); + tile.entity.items.add(item, 1); } public boolean acceptItem(Item item, Tile tile, Tile source){ @@ -61,17 +61,11 @@ public abstract class BaseBlock { } public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){ - return tile.entity.liquids.amount + amount < liquidCapacity - && (tile.entity.liquids.liquid == liquid || tile.entity.liquids.amount <= 0.1f); - } - - public float handleAuxLiquid(Tile tile, Tile source, Liquid liquid, float amount){ - return 0f; + return tile.entity.liquids.get(liquid) + amount < liquidCapacity; } public void handleLiquid(Tile tile, Tile source, Liquid liquid, float amount){ - tile.entity.liquids.liquid = liquid; - tile.entity.liquids.amount += amount; + tile.entity.liquids.add(liquid, amount); } public boolean acceptPower(Tile tile, Tile source, float amount){ @@ -87,9 +81,7 @@ public abstract class BaseBlock { return canAccept; } - public void tryDumpLiquid(Tile tile){ - if(tile.entity.liquids.amount < 0.001f) return; - + public void tryDumpLiquid(Tile tile, Liquid liquid){ int size = tile.block().size; GridPoint2[] nearby = Edges.getEdges(size); @@ -102,10 +94,10 @@ public abstract class BaseBlock { if(other != null) other = other.target(); if (other != null && other.block().hasLiquids) { - float ofract = other.entity.liquids.amount / other.block().liquidCapacity; - float fract = tile.entity.liquids.amount / liquidCapacity; + float ofract = other.entity.liquids.get(liquid) / other.block().liquidCapacity; + float fract = tile.entity.liquids.get(liquid) / liquidCapacity; - if(ofract < fract) tryMoveLiquid(tile, in, other, (fract - ofract) * liquidCapacity / 2f); + if(ofract < fract) tryMoveLiquid(tile, in, other, (fract - ofract) * liquidCapacity / 2f, liquid); } i = (byte) ((i + 1) % nearby.length); @@ -113,35 +105,34 @@ public abstract class BaseBlock { } - public void tryMoveLiquid(Tile tile, Tile tileSource, Tile next, float amount){ - float flow = Math.min(next.block().liquidCapacity - next.entity.liquids.amount - 0.001f, amount); + public void tryMoveLiquid(Tile tile, Tile tileSource, Tile next, float amount, Liquid liquid){ + float flow = Math.min(next.block().liquidCapacity - next.entity.liquids.get(liquid) - 0.001f, amount); - if(next.block().acceptLiquid(next, tileSource, tile.entity.liquids.liquid, flow)){ - next.block().handleLiquid(next, tileSource, tile.entity.liquids.liquid, flow); - tile.entity.liquids.amount -= flow; + if(next.block().acceptLiquid(next, tileSource, liquid, flow)){ + next.block().handleLiquid(next, tileSource, liquid, flow); + tile.entity.liquids.remove(liquid, flow); } } - public float tryMoveLiquid(Tile tile, Tile next, boolean leak){ + public float tryMoveLiquid(Tile tile, Tile next, boolean leak, Liquid liquid){ if(next == null) return 0; next = next.target(); - if(next.block().hasLiquids && tile.entity.liquids.amount > 0f){ + if(next.block().hasLiquids && tile.entity.liquids.get(liquid) > 0f){ - if((next.entity.liquids.liquid == tile.entity.liquids.liquid || next.entity.liquids.amount <= 0.01f) && - next.block().acceptLiquid(next, tile, tile.entity.liquids.liquid, 0f)) { - float ofract = next.entity.liquids.amount / next.block().liquidCapacity; - float fract = tile.entity.liquids.amount / liquidCapacity; - float flow = Math.min(Mathf.clamp((fract - ofract) * (1f)) * (liquidCapacity), tile.entity.liquids.amount); - flow = Math.min(flow, next.block().liquidCapacity - next.entity.liquids.amount - 0.001f); + if(next.block().acceptLiquid(next, tile, liquid, 0f)) { + float ofract = next.entity.liquids.get(liquid) / next.block().liquidCapacity; + float fract = tile.entity.liquids.get(liquid) / liquidCapacity; + float flow = Math.min(Mathf.clamp((fract - ofract) * (1f)) * (liquidCapacity), tile.entity.liquids.get(liquid)); + flow = Math.min(flow, next.block().liquidCapacity - next.entity.liquids.get(liquid) - 0.001f); - if (flow > 0f && ofract <= fract && next.block().acceptLiquid(next, tile, tile.entity.liquids.liquid, flow)) { - next.block().handleLiquid(next, tile, tile.entity.liquids.liquid, flow); - tile.entity.liquids.amount -= flow; + if (flow > 0f && ofract <= fract && next.block().acceptLiquid(next, tile, liquid, flow)) { + next.block().handleLiquid(next, tile, liquid, flow); + tile.entity.liquids.remove(liquid, flow); return flow; } else if (ofract > 0.1f && fract > 0.1f) { - Liquid liquid = tile.entity.liquids.liquid, other = next.entity.liquids.liquid; + Liquid other = next.entity.liquids.current(); if ((other.flammability > 0.3f && liquid.temperature > 0.7f) || (liquid.flammability > 0.3f && other.temperature > 0.7f)) { tile.entity.damage(1 * Timers.delta()); @@ -151,20 +142,17 @@ public abstract class BaseBlock { } } else if ((liquid.temperature > 0.7f && other.temperature < 0.55f) || (other.temperature > 0.7f && liquid.temperature < 0.55f)) { - tile.entity.liquids.amount -= Math.min(tile.entity.liquids.amount, 0.7f * Timers.delta()); + tile.entity.liquids.remove(liquid, Math.min(tile.entity.liquids.get(liquid), 0.7f * Timers.delta())); if (Mathf.chance(0.2f * Timers.delta())) { Effects.effect(EnvironmentFx.steam, (tile.worldx() + next.worldx()) / 2f, (tile.worldy() + next.worldy()) / 2f); } } } - }else{ - float accepted = next.block().handleAuxLiquid(next, tile, tile.entity.liquids.liquid, tile.entity.liquids.amount); - tile.entity.liquids.amount -= accepted; } }else if(leak && !next.block().solid && !next.block().hasLiquids){ - float leakAmount = Math.min(tile.entity.liquids.amount, tile.entity.liquids.amount/1.5f); - Puddle.deposit(next, tile, tile.entity.liquids.liquid, leakAmount); - tile.entity.liquids.amount -= leakAmount; + float leakAmount = tile.entity.liquids.get(liquid)/1.5f; + Puddle.deposit(next, tile, liquid, leakAmount); + tile.entity.liquids.remove(liquid, leakAmount); } return 0; } @@ -198,7 +186,7 @@ public abstract class BaseBlock { /**Try dumping a specific item near the tile.*/ public boolean tryDump(Tile tile, Item todump){ - if(tile.entity == null || !hasItems) return false; + if(tile.entity == null || !hasItems || tile.entity.items.total() == 0) return false; int size = tile.block().size; @@ -216,9 +204,9 @@ public abstract class BaseBlock { if(todump != null && item != todump) continue; - if(tile.entity.items.hasItem(item) && other != null && other.block().acceptItem(item, other, in) && canDump(tile, other, item)){ + if(tile.entity.items.has(item) && other != null && other.block().acceptItem(item, other, in) && canDump(tile, other, item)){ other.block().handleItem(item, other, in); - tile.entity.items.removeItem(item, 1); + tile.entity.items.remove(item, 1); i = (byte)((i + 1) % nearby.length); tile.setDump(i); return true; diff --git a/core/src/io/anuke/mindustry/world/Block.java b/core/src/io/anuke/mindustry/world/Block.java index 987041b034..51f5bce094 100644 --- a/core/src/io/anuke/mindustry/world/Block.java +++ b/core/src/io/anuke/mindustry/world/Block.java @@ -5,7 +5,6 @@ import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.ObjectMap; import com.badlogic.gdx.utils.reflect.ClassReflection; -import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.entities.Damage; import io.anuke.mindustry.entities.Player; import io.anuke.mindustry.entities.TileEntity; @@ -20,7 +19,7 @@ import io.anuke.mindustry.graphics.Palette; import io.anuke.mindustry.input.CursorType; import io.anuke.mindustry.type.Item; import io.anuke.mindustry.type.ItemStack; -import io.anuke.mindustry.type.Liquid; +import io.anuke.mindustry.world.consumers.Consume; import io.anuke.mindustry.world.meta.*; import io.anuke.ucore.core.Timers; import io.anuke.ucore.graphics.Draw; @@ -31,7 +30,8 @@ import io.anuke.ucore.util.Bundles; import io.anuke.ucore.util.EnumSet; import io.anuke.ucore.util.Mathf; -import static io.anuke.mindustry.Vars.*; +import static io.anuke.mindustry.Vars.tilesize; +import static io.anuke.mindustry.Vars.world; public class Block extends BaseBlock implements Content{ private static int lastid; @@ -46,6 +46,9 @@ public class Block extends BaseBlock implements Content{ protected TextureRegion[] compactIcon; protected TextureRegion editorIcon; + protected TextureRegion shadowRegion; + protected TextureRegion region; + /**internal name*/ public final String name; /**internal ID*/ @@ -72,12 +75,8 @@ public class Block extends BaseBlock implements Content{ public int health = -1; /**base block explosiveness*/ public float baseExplosiveness = 0f; - /**whether to display a different shadow per variant*/ - public boolean varyShadow = false; /**whether this block can be placed on liquids.*/ public boolean floating = true; - /**number of block variants, 0 to disable*/ - public int variants = 0; /**stuff that drops when broken*/ public ItemStack drops = null; /**multiblock size*/ @@ -110,10 +109,6 @@ public class Block extends BaseBlock implements Content{ public boolean autoSleep; /**Name of shadow region to load. Null to indicate normal shadow.*/ public String shadow = null; - /**Region used for drawing shadows.*/ - public TextureRegion shadowRegion; - /**Texture region array for drawing multiple shadows.*/ - public TextureRegion[] shadowRegions; /**Whether the block can be tapped and selected to configure.*/ public boolean configurable; /**Whether this block consumes touchDown events when tapped.*/ @@ -138,6 +133,10 @@ public class Block extends BaseBlock implements Content{ blocks.add(this); } + public void setConsumers(Array consumers){ + + } + public boolean isLayer(Tile tile){return true;} public boolean isLayer2(Tile tile){return true;} public void drawLayer(Tile tile){} @@ -173,13 +172,7 @@ public class Block extends BaseBlock implements Content{ @Override public void load() { shadowRegion = Draw.region(shadow == null ? "shadow-" + size : shadow); - - if(varyShadow && variants > 0) { - shadowRegions = new TextureRegion[variants]; - for(int i = 0; i < variants; i ++){ - shadowRegions[i] = Draw.region(name + "shadow" + (i + 1)); - } - } + region = Draw.region(name); } /**Called when the block is tapped.*/ @@ -236,8 +229,8 @@ public class Block extends BaseBlock implements Content{ //TODO make this easier to config. public void setBars(){ if(hasPower) bars.add(new BlockBar(BarType.power, true, tile -> tile.entity.power.amount / powerCapacity)); - if(hasLiquids) bars.add(new BlockBar(BarType.liquid, true, tile -> tile.entity.liquids.amount / liquidCapacity)); - if(hasItems) bars.add(new BlockBar(BarType.inventory, true, tile -> (float)tile.entity.items.totalItems() / itemCapacity)); + if(hasLiquids) bars.add(new BlockBar(BarType.liquid, true, tile -> tile.entity.liquids.total() / liquidCapacity)); + if(hasItems) bars.add(new BlockBar(BarType.inventory, true, tile -> (float)tile.entity.items.total() / itemCapacity)); } public String name(){ @@ -276,14 +269,13 @@ public class Block extends BaseBlock implements Content{ float x = tile.worldx(), y = tile.worldy(); float explosiveness = baseExplosiveness; float flammability = 0f; - float heat = 0f; float power = 0f; int units = 1; tempColor.set(Palette.darkFlame); if(hasItems){ for(Item item : Item.all()){ - int amount = tile.entity.items.getItem(item); + int amount = tile.entity.items.get(item); explosiveness += item.explosiveness*amount; flammability += item.flammability*amount; @@ -295,15 +287,8 @@ public class Block extends BaseBlock implements Content{ } if(hasLiquids){ - float amount = tile.entity.liquids.amount; - explosiveness += tile.entity.liquids.liquid.explosiveness*amount/2f; - flammability += tile.entity.liquids.liquid.flammability*amount/2f; - heat += Mathf.clamp(tile.entity.liquids.liquid.temperature-0.5f)*amount/2f; - - if(tile.entity.liquids.liquid.flammability*amount > 2f){ - units ++; - Hue.addu(tempColor, tile.entity.liquids.liquid.flameColor); - } + flammability += tile.entity.liquids.sum((liquid, amount) -> liquid.explosiveness * amount/2f); + explosiveness += tile.entity.liquids.sum((liquid, amount) -> liquid.flammability * amount/2f); } if(hasPower){ @@ -314,17 +299,18 @@ public class Block extends BaseBlock implements Content{ if(hasLiquids) { - Liquid liquid = tile.entity.liquids.liquid; - float splash = Mathf.clamp(tile.entity.liquids.amount / 4f, 0f, 10f); + tile.entity.liquids.forEach((liquid, amount) -> { + float splash = Mathf.clamp(amount / 4f, 0f, 10f); - for (int i = 0; i < Mathf.clamp(tile.entity.liquids.amount / 5, 0, 30); i++) { - Timers.run(i / 2, () -> { - Tile other = world.tile(tile.x + Mathf.range(size / 2), tile.y + Mathf.range(size / 2)); - if (other != null) { - Puddle.deposit(other, liquid, splash); - } - }); - } + for (int i = 0; i < Mathf.clamp(amount / 5, 0, 30); i++) { + Timers.run(i / 2, () -> { + Tile other = world.tile(tile.x + Mathf.range(size / 2), tile.y + Mathf.range(size / 2)); + if (other != null) { + Puddle.deposit(other, liquid, splash); + } + }); + } + }); } Damage.dynamicExplosion(x, y, flammability, explosiveness, power, tilesize * size/2f, tempColor); @@ -342,14 +328,12 @@ public class Block extends BaseBlock implements Content{ } return 0; }else{ - float result = 0f; - for (int i = 0; i < Item.all().size; i++) { - int amount = tile.entity.items.items[i]; - result += Item.getByID(i).flammability*amount; - } + float result = tile.entity.items.sum((item, amount) -> item.flammability * amount); + if(hasLiquids){ - result += tile.entity.liquids.amount * tile.entity.liquids.liquid.flammability/3f; + result += tile.entity.liquids.sum((liquid, amount) -> liquid.flammability * amount/3f); } + return result; } } @@ -411,30 +395,13 @@ public class Block extends BaseBlock implements Content{ } public void draw(Tile tile){ - //note: multiblocks do not support rotation - if(!isMultiblock()){ - Draw.rect(variants > 0 ? (name() + Mathf.randomSeed(tile.id(), 1, variants)) : name(), - tile.worldx(), tile.worldy(), rotate ? tile.getRotation() * 90 : 0); - }else{ - //if multiblock, make sure to draw even block sizes offset, since the core block is at the BOTTOM LEFT - Draw.rect(name(), tile.drawx(), tile.drawy()); - } - - //update the tile entity through the draw method, only if it's an entity without updating - if(destructible && !update && !state.is(State.paused)){ - tile.entity.update(); - } + Draw.rect(region, tile.drawx(), tile.drawy(), rotate ? tile.getRotation() * 90 : 0); } public void drawNonLayer(Tile tile){} public void drawShadow(Tile tile){ - - if(shadowRegions != null) { - Draw.rect(shadowRegions[(Mathf.randomSeed(tile.id(), 0, variants - 1))], tile.worldx(), tile.worldy()); - }else if(shadowRegion != null){ - Draw.rect(shadowRegion, tile.drawx(), tile.drawy()); - } + Draw.rect(shadowRegion, tile.drawx(), tile.drawy()); } /**Offset for placing and drawing multiblocks.*/ @@ -456,7 +423,7 @@ public class Block extends BaseBlock implements Content{ "entity.x", tile.entity.x, "entity.y", tile.entity.y, "entity.id", tile.entity.id, - "entity.items.total", hasItems ? tile.entity.items.totalItems() : null + "entity.items.total", hasItems ? tile.entity.items.total() : null ); } diff --git a/core/src/io/anuke/mindustry/world/Consumption.java b/core/src/io/anuke/mindustry/world/Consumption.java deleted file mode 100644 index e3d491e50b..0000000000 --- a/core/src/io/anuke/mindustry/world/Consumption.java +++ /dev/null @@ -1,4 +0,0 @@ -package io.anuke.mindustry.world; - -public class Consumption { -} diff --git a/core/src/io/anuke/mindustry/world/Tile.java b/core/src/io/anuke/mindustry/world/Tile.java index 3f60494762..3393a2de39 100644 --- a/core/src/io/anuke/mindustry/world/Tile.java +++ b/core/src/io/anuke/mindustry/world/Tile.java @@ -10,9 +10,9 @@ import io.anuke.mindustry.entities.traits.TargetTrait; import io.anuke.mindustry.game.Team; import io.anuke.mindustry.type.Recipe; import io.anuke.mindustry.world.blocks.Floor; -import io.anuke.mindustry.world.blocks.modules.InventoryModule; -import io.anuke.mindustry.world.blocks.modules.LiquidModule; -import io.anuke.mindustry.world.blocks.modules.PowerModule; +import io.anuke.mindustry.world.modules.InventoryModule; +import io.anuke.mindustry.world.modules.LiquidModule; +import io.anuke.mindustry.world.modules.PowerModule; import io.anuke.ucore.entities.trait.PosTrait; import io.anuke.ucore.function.Consumer; import io.anuke.ucore.util.Bits; diff --git a/core/src/io/anuke/mindustry/world/blocks/BlendBlock.java b/core/src/io/anuke/mindustry/world/blocks/BlendBlock.java deleted file mode 100644 index 7c01a052be..0000000000 --- a/core/src/io/anuke/mindustry/world/blocks/BlendBlock.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.anuke.mindustry.world.blocks; - -import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.Tile; -import io.anuke.ucore.graphics.Draw; -import io.anuke.ucore.function.Predicate; -import io.anuke.ucore.util.Mathf; - -public class BlendBlock extends Block{ - protected String edge; - protected Predicate blend = block -> block == this; - - public BlendBlock(String name) { - super(name); - edge = name + "-edge"; - } - - @Override - public void draw(Tile tile){ - Draw.rect(variants > 0 ? (name() + Mathf.randomSeed(tile.id(), 1, variants)) : name(), - tile.worldx(), tile.worldy()); - - for(int i = 0; i < 4; i ++){ - Tile near = tile.getNearby(i); - if(near != null && !blend.test(near.block())){ - Draw.rect(edge + "-" + i, tile.worldx(), tile.worldy()); - } - } - } -} diff --git a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java index f672045459..5b03df2d21 100644 --- a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java @@ -22,7 +22,7 @@ import io.anuke.mindustry.type.Recipe; import io.anuke.mindustry.world.BarType; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.blocks.modules.InventoryModule; +import io.anuke.mindustry.world.modules.InventoryModule; import io.anuke.mindustry.world.meta.BlockBar; import io.anuke.ucore.core.Effects; import io.anuke.ucore.core.Graphics; @@ -157,11 +157,10 @@ public class BuildBlock extends Block { } @Remote(called = Loc.server, in = In.blocks) - public static void onConstructFinish(Tile tile, Block block, int builderID, byte rotation){ - Team team = tile.getTeam(); + public static void onConstructFinish(Tile tile, Block block, int builderID, byte rotation, Team team){ tile.setBlock(block); - tile.setTeam(team); tile.setRotation(rotation); + tile.setTeam(team); Effects.effect(Fx.placeBlock, tile.drawx(), tile.drawy(), block.size); //last builder was this local client player, call placed() @@ -197,7 +196,7 @@ public class BuildBlock extends Block { progress = Mathf.clamp(progress + maxProgress); if(progress >= 1f){ - CallBlocks.onConstructFinish(tile, recipe.result, builder.getID(), tile.getRotation()); + CallBlocks.onConstructFinish(tile, recipe.result, builder.getID(), tile.getRotation(), tile.getTeam()); } } @@ -233,11 +232,11 @@ public class BuildBlock extends Block { for(int i = 0; i < recipe.requirements.length; i ++){ int required = (int)(accumulator[i]); //calculate items that are required now - if(inventory.getItem(recipe.requirements[i].item) == 0){ + if(inventory.get(recipe.requirements[i].item) == 0){ maxProgress = 0f; }else if(required > 0){ //if this amount is positive... //calculate how many items it can actually use - int maxUse = Math.min(required, inventory.getItem(recipe.requirements[i].item)); + int maxUse = Math.min(required, inventory.get(recipe.requirements[i].item)); //get this as a fraction float fraction = maxUse / (float)required; @@ -248,7 +247,7 @@ public class BuildBlock extends Block { //remove stuff that is actually used if(remove) { - inventory.removeItem(recipe.requirements[i].item, maxUse); + inventory.remove(recipe.requirements[i].item, maxUse); } } //else, no items are required yet, so just keep going diff --git a/core/src/io/anuke/mindustry/world/blocks/Floor.java b/core/src/io/anuke/mindustry/world/blocks/Floor.java index 435b4f99a6..37c2938516 100644 --- a/core/src/io/anuke/mindustry/world/blocks/Floor.java +++ b/core/src/io/anuke/mindustry/world/blocks/Floor.java @@ -35,6 +35,8 @@ public class Floor extends Block{ protected Predicate blends = block -> block != this && !block.blendOverride(this); protected boolean blend = true; + /**number of different variant regions to use*/ + public int variants = 0; /**edge fallback, used mainly for ores*/ public String edge = "stone"; /**Multiplies unit velocity by this when walked on.*/ diff --git a/core/src/io/anuke/mindustry/world/blocks/LiquidBlock.java b/core/src/io/anuke/mindustry/world/blocks/LiquidBlock.java index bda16d1167..9feefc53b8 100644 --- a/core/src/io/anuke/mindustry/world/blocks/LiquidBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/LiquidBlock.java @@ -4,11 +4,11 @@ import com.badlogic.gdx.graphics.g2d.TextureRegion; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.meta.BlockGroup; import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.blocks.modules.LiquidModule; +import io.anuke.mindustry.world.modules.LiquidModule; import io.anuke.ucore.graphics.Draw; public class LiquidBlock extends Block{ - protected String liquidRegion = name() + "-liquid"; + protected TextureRegion liquidRegion, bottomRegion, topRegion; public LiquidBlock(String name) { super(name); @@ -18,6 +18,15 @@ public class LiquidBlock extends Block{ group = BlockGroup.liquids; } + @Override + public void load() { + super.load(); + + liquidRegion = Draw.region(name + "-liquid"); + topRegion = Draw.region(name + "-top"); + bottomRegion = Draw.region(name + "-bottom"); + } + @Override public TextureRegion[] getIcon(){ return new TextureRegion[]{Draw.region(name() + "-bottom"), Draw.region(name() + "-top")}; @@ -29,15 +38,15 @@ public class LiquidBlock extends Block{ int rotation = rotate ? tile.getRotation() * 90 : 0; - Draw.rect(name() + "-bottom", tile.drawx(), tile.drawy(), rotation); + Draw.rect(bottomRegion, tile.drawx(), tile.drawy(), rotation); - if(mod.amount > 0.001f){ - Draw.color(mod.liquid.color); - Draw.alpha(mod.amount / liquidCapacity); + if(mod.total() > 0.001f){ + Draw.color(mod.current().color); + Draw.alpha(mod.total() / liquidCapacity); Draw.rect(liquidRegion, tile.drawx(), tile.drawy(), rotation); Draw.color(); } - Draw.rect(name() + "-top", tile.drawx(), tile.drawy(), rotation); + Draw.rect(topRegion, tile.drawx(), tile.drawy(), rotation); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/Rock.java b/core/src/io/anuke/mindustry/world/blocks/Rock.java index f6c463ece5..40066e08ec 100644 --- a/core/src/io/anuke/mindustry/world/blocks/Rock.java +++ b/core/src/io/anuke/mindustry/world/blocks/Rock.java @@ -1,13 +1,51 @@ package io.anuke.mindustry.world.blocks; +import com.badlogic.gdx.graphics.g2d.TextureRegion; import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.Tile; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.util.Mathf; public class Rock extends Block { + protected TextureRegion[] shadowRegions, regions; + protected int variants; public Rock(String name) { super(name); - varyShadow = true; breakable = true; alwaysReplace = true; } + + @Override + public void draw(Tile tile) { + if(variants > 0){ + Draw.rect(regions[Mathf.randomSeed(tile.id(), 0, Math.max(0, regions.length-1))], tile.worldx(), tile.worldy()); + }else{ + Draw.rect(region, tile.worldx(), tile.worldy()); + } + } + + @Override + public void drawShadow(Tile tile) { + if(shadowRegions != null) { + Draw.rect(shadowRegions[(Mathf.randomSeed(tile.id(), 0, variants - 1))], tile.worldx(), tile.worldy()); + }else if(shadowRegion != null){ + Draw.rect(shadowRegion, tile.drawx(), tile.drawy()); + } + } + + @Override + public void load() { + super.load(); + + if(variants > 0){ + shadowRegions = new TextureRegion[variants]; + regions = new TextureRegion[variants]; + + for (int i = 0; i < variants; i++) { + shadowRegions[i] = Draw.region(name + "-shadow" + (i+1)); + regions[i] = Draw.region(name + (i+1)); + } + } + } } diff --git a/core/src/io/anuke/mindustry/world/blocks/defense/Door.java b/core/src/io/anuke/mindustry/world/blocks/defense/Door.java index c147d97e48..824fbaa961 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/Door.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/Door.java @@ -45,7 +45,7 @@ public class Door extends Wall{ DoorEntity entity = tile.entity(); if(!entity.open){ - Draw.rect(name, tile.drawx(), tile.drawy()); + Draw.rect(region, tile.drawx(), tile.drawy()); }else{ Draw.rect(openRegion, tile.drawx(), tile.drawy()); } diff --git a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/CooledTurret.java b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/CooledTurret.java index b1588f85af..7ca7f09a9e 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/CooledTurret.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/CooledTurret.java @@ -38,17 +38,18 @@ public class CooledTurret extends Turret { super.updateShooting(tile); TurretEntity entity = tile.entity(); + Liquid liquid = entity.liquids.current(); - float used = Math.min(Math.min(entity.liquids.amount, maxUsed * Timers.delta()), Math.max(0, ((reload - entity.reload) / coolantMultiplier) / entity.liquids.liquid.heatCapacity)); - entity.reload += (used * entity.liquids.liquid.heatCapacity) / entity.liquids.liquid.heatCapacity; - entity.liquids.amount -= used; + float used = Math.min(Math.min(entity.liquids.get(liquid), maxUsed * Timers.delta()), Math.max(0, ((reload - entity.reload) / coolantMultiplier) / liquid.heatCapacity)); + entity.reload += (used * liquid.heatCapacity) / liquid.heatCapacity; + entity.liquids.remove(liquid, used); if(Mathf.chance(0.04 * used)){ Effects.effect(coolEffect, tile.drawx() + Mathf.range(size * tilesize/2f), tile.drawy() + Mathf.range(size * tilesize/2f)); } //don't use oil as coolant, thanks - if(Mathf.chance(entity.liquids.liquid.flammability / 10f * used)){ + if(Mathf.chance(liquid.flammability / 10f * used)){ Fire.create(tile); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/ItemTurret.java b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/ItemTurret.java index 1fdfbdc39d..d1b1fde9c8 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/ItemTurret.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/ItemTurret.java @@ -60,7 +60,7 @@ public class ItemTurret extends CooledTurret { AmmoType type = ammoMap.get(item); entity.totalAmmo += type.quantityMultiplier; - entity.items.addItem(item, 1); + entity.items.add(item, 1); //find ammo entry by type for(int i = 0; i < entity.ammo.size; i ++){ diff --git a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/Turret.java b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/Turret.java index 9feeb9d9a3..abebf95b80 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/Turret.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/Turret.java @@ -27,7 +27,10 @@ import io.anuke.ucore.core.Timers; import io.anuke.ucore.function.BiConsumer; import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.graphics.Lines; -import io.anuke.ucore.util.*; +import io.anuke.ucore.util.Angles; +import io.anuke.ucore.util.Mathf; +import io.anuke.ucore.util.ThreadArray; +import io.anuke.ucore.util.Translator; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -66,8 +69,9 @@ public abstract class Turret extends Block{ protected TextureRegion heatRegion; protected TextureRegion baseTopRegion; - protected BiConsumer drawer = (tile, entity) -> Draw.rect(name, tile.drawx() + tr2.x, tile.drawy() + tr2.y, entity.rotation - 90); + protected BiConsumer drawer = (tile, entity) -> Draw.rect(region, tile.drawx() + tr2.x, tile.drawy() + tr2.y, entity.rotation - 90); protected BiConsumer heatDrawer = (tile, entity) ->{ + if(entity.heat <= 0.00001f) return; Graphics.setAdditiveBlending(); Draw.color(heatColor); Draw.alpha(entity.heat); @@ -129,7 +133,7 @@ public abstract class Turret extends Block{ drawer.accept(tile, entity); - if(Draw.hasRegion(name + "-heat")){ + if(heatRegion != null){ heatDrawer.accept(tile, entity); } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/BufferedItemBridge.java b/core/src/io/anuke/mindustry/world/blocks/distribution/BufferedItemBridge.java index 2a366bf0d4..c16ae2f2bf 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/BufferedItemBridge.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/BufferedItemBridge.java @@ -26,8 +26,8 @@ public class BufferedItemBridge extends ExtendingItemBridge { public void updateTransport(Tile tile, Tile other){ BufferedItemBridgeEntity entity = tile.entity(); - if(entity.buffer.accepts() && entity.items.totalItems() > 0){ - entity.buffer.accept(entity.items.takeItem()); + if(entity.buffer.accepts() && entity.items.total() > 0){ + entity.buffer.accept(entity.items.take()); } Item item = entity.buffer.poll(); diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java index ecaf8d1cea..f20d9d80af 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java @@ -5,7 +5,7 @@ import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.type.Liquid; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.LiquidBlock; -import io.anuke.mindustry.world.blocks.modules.LiquidModule; +import io.anuke.mindustry.world.modules.LiquidModule; import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.util.Mathf; @@ -18,11 +18,17 @@ public class Conduit extends LiquidBlock { public Conduit(String name) { super(name); - liquidRegion = "conduit-liquid"; rotate = true; solid = false; } + @Override + public void load() { + super.load(); + + liquidRegion = Draw.region("conduit-liquid"); + } + @Override public void draw(Tile tile){ ConduitEntity entity = tile.entity(); @@ -30,23 +36,23 @@ public class Conduit extends LiquidBlock { int rotation = rotate ? tile.getRotation() * 90 : 0; - Draw.rect(name() + "-bottom", tile.drawx(), tile.drawy(), rotation); + Draw.rect(bottomRegion, tile.drawx(), tile.drawy(), rotation); - Draw.color(mod.liquid.color); + Draw.color(mod.current().color); Draw.alpha(entity.smoothLiquid); Draw.rect(liquidRegion, tile.drawx(), tile.drawy(), rotation); Draw.color(); - Draw.rect(name() + "-top", tile.drawx(), tile.drawy(), rotation); + Draw.rect(topRegion, tile.drawx(), tile.drawy(), rotation); } @Override public void update(Tile tile){ ConduitEntity entity = tile.entity(); - entity.smoothLiquid = Mathf.lerpDelta(entity.smoothLiquid, entity.liquids.amount/liquidCapacity, 0.05f); + entity.smoothLiquid = Mathf.lerpDelta(entity.smoothLiquid, entity.liquids.total()/liquidCapacity, 0.05f); - if(tile.entity.liquids.amount > 0.001f && tile.entity.timer.get(timerFlow, 1)){ - tryMoveLiquid(tile, tile.getNearby(tile.getRotation()), true); + if(tile.entity.liquids.total() > 0.001f && tile.entity.timer.get(timerFlow, 1)){ + tryMoveLiquid(tile, tile.getNearby(tile.getRotation()), true, tile.entity.liquids.current()); entity.wakeUp(); }else{ entity.sleep(); @@ -61,7 +67,8 @@ public class Conduit extends LiquidBlock { @Override public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount) { tile.entity.wakeUp(); - return super.acceptLiquid(tile, source, liquid, amount) && ((2 + source.relativeTo(tile.x, tile.y)) % 4 != tile.getRotation()); + return super.acceptLiquid(tile, source, liquid, amount) && (tile.entity.liquids.current() == liquid || tile.entity.liquids.get(tile.entity.liquids.current()) < 0.01f) && + ((2 + source.relativeTo(tile.x, tile.y)) % 4 != tile.getRotation()); } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/Conveyor.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Conveyor.java index 47c2de709a..d47d269d16 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Conveyor.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Conveyor.java @@ -189,7 +189,7 @@ public class Conveyor extends Block{ if (pos.y >= 0.9999f && offloadDir(tile, pos.item)) { minremove = Math.min(i, minremove); totalMoved = 1f; - tile.entity.items.removeItem(pos.item, 1); + tile.entity.items.remove(pos.item, 1); } else { value = pos.pack(); @@ -232,7 +232,7 @@ public class Conveyor extends Block{ ItemPos pos = pos1.set(val, ItemPos.drawShorts); if(pos.item == item){ entity.convey.removeValue(val); - entity.items.removeItem(item, 1); + entity.items.remove(item, 1); removed ++; break; } @@ -258,7 +258,7 @@ public class Conveyor extends Block{ long result = ItemPos.packItem(item, 0f, 0f, (byte)Mathf.random(255)); entity.convey.insert(0, result); - entity.items.addItem(item, 1); + entity.items.add(item, 1); entity.wakeUp(); } @@ -285,7 +285,7 @@ public class Conveyor extends Block{ long result = ItemPos.packItem(item, y*0.9f, pos, (byte)Mathf.random(255)); boolean inserted = false; - tile.entity.items.addItem(item, 1); + tile.entity.items.add(item, 1); for(int i = 0; i < entity.convey.size; i ++){ if(compareItems(result, entity.convey.get(i)) < 0){ diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/ExtendingItemBridge.java b/core/src/io/anuke/mindustry/world/blocks/distribution/ExtendingItemBridge.java index f427593c6d..4c7309859d 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/ExtendingItemBridge.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/ExtendingItemBridge.java @@ -33,14 +33,14 @@ public class ExtendingItemBridge extends ItemBridge { ey *= entity.uptime; Lines.stroke(8f); - Lines.line(Draw.region(name + "-bridge"), + Lines.line(bridgeRegion, tile.worldx() + Geometry.d4[i].x*tilesize/2f, tile.worldy() + Geometry.d4[i].y*tilesize/2f, tile.worldx() + ex, tile.worldy() + ey, CapStyle.none, 0f); - Draw.rect(name + "-end", tile.drawx(), tile.drawy(), i*90 + 90); - Draw.rect(name + "-end", + Draw.rect(endRegion, tile.drawx(), tile.drawy(), i*90 + 90); + Draw.rect(endRegion, tile.worldx() + ex + Geometry.d4[i].x*tilesize/2f, tile.worldy() + ey + Geometry.d4[i].y*tilesize/2f, i*90 + 270); @@ -52,7 +52,7 @@ public class ExtendingItemBridge extends ItemBridge { for(int a = 0; a < arrows; a ++){ Draw.alpha(Mathf.absin(a/(float)arrows - entity.time/100f, 0.1f, 1f) * entity.uptime); - Draw.rect(name + "-arrow", + Draw.rect(arrowRegion, tile.worldx() + Geometry.d4[i].x*(tilesize/2f + a*6f + 2) * entity.uptime, tile.worldy() + Geometry.d4[i].y*(tilesize/2f + a*6f + 2) * entity.uptime, i*90f); diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java b/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java index b07b13c57a..cfad7d944e 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java @@ -1,6 +1,7 @@ package io.anuke.mindustry.world.blocks.distribution; import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.utils.IntArray; import com.badlogic.gdx.utils.IntSet; import com.badlogic.gdx.utils.IntSet.IntSetIterator; @@ -38,6 +39,8 @@ public class ItemBridge extends Block { protected float transportTime = 2f; protected IntArray removals = new IntArray(); + protected TextureRegion endRegion, bridgeRegion, arrowRegion; + public ItemBridge(String name) { super(name); update = true; @@ -50,6 +53,13 @@ public class ItemBridge extends Block { hasItems = true; } + @Override + public void load() { + endRegion = Draw.region(name + "-end"); + bridgeRegion = Draw.region(name + "-bridge"); + arrowRegion = Draw.region(name + "-arrow"); + } + @Override public void placed(Tile tile) { Tile last = world.tile(lastPlaced); @@ -162,13 +172,13 @@ public class ItemBridge extends Block { ItemBridgeEntity entity = tile.entity(); if(entity.uptime >= 0.5f && entity.timer.get(timerTransport, transportTime)){ - Item item = entity.items.takeItem(); + Item item = entity.items.take(); if(item != null && other.block().acceptItem(item, other, tile)){ other.block().handleItem(item, other, tile); entity.cycleSpeed = Mathf.lerpDelta(entity.cycleSpeed, 4f, 0.05f); }else{ entity.cycleSpeed = Mathf.lerpDelta(entity.cycleSpeed, 1f, 0.01f); - if(item != null) entity.items.addItem(item, 1); + if(item != null) entity.items.add(item, 1); } } } @@ -185,11 +195,11 @@ public class ItemBridge extends Block { Draw.color(Color.WHITE, Color.BLACK, Mathf.absin(Timers.time(), 6f, 0.07f)); Draw.alpha(Math.max(entity.uptime, 0.25f)); - Draw.rect(name + "-end", tile.drawx(), tile.drawy(), i*90 + 90); - Draw.rect(name + "-end", other.drawx(), other.drawy(), i*90 + 270); + Draw.rect(endRegion, tile.drawx(), tile.drawy(), i*90 + 90); + Draw.rect(endRegion, other.drawx(), other.drawy(), i*90 + 270); Lines.stroke(8f); - Lines.line(Draw.region(name + "-bridge"), + Lines.line(bridgeRegion, tile.worldx(), tile.worldy(), other.worldx(), @@ -204,7 +214,7 @@ public class ItemBridge extends Block { for(int a = 0; a < arrows; a ++){ Draw.alpha(Mathf.absin(a/(float)arrows - entity.time/100f, 0.1f, 1f) * entity.uptime); - Draw.rect(name + "-arrow", + Draw.rect(arrowRegion, tile.worldx() + Geometry.d4[i].x*(tilesize/2f + a*4f + time % 4f), tile.worldy() + Geometry.d4[i].y*(tilesize/2f + a*4f + time % 4f), i*90f); @@ -214,7 +224,7 @@ public class ItemBridge extends Block { @Override public boolean acceptItem(Item item, Tile tile, Tile source) { - return tile.entity.items.totalItems() < itemCapacity; + return tile.entity.items.total() < itemCapacity; } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/MassDriver.java b/core/src/io/anuke/mindustry/world/blocks/distribution/MassDriver.java index e0a81c997e..b5492d0e5a 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/MassDriver.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/MassDriver.java @@ -64,7 +64,7 @@ public class MassDriver extends Block { if(entity.isUnloading){ tryDump(tile); - if(entity.items.totalItems() <= 0){ + if(entity.items.total() <= 0){ entity.isUnloading = false; } } @@ -79,10 +79,10 @@ public class MassDriver extends Block { Tile waiter = entity.waiting.first(); entity.rotation = Mathf.slerpDelta(entity.rotation, tile.angleTo(waiter), rotateSpeed); - }else if (tile.entity.items.totalItems() >= minDistribute && + }else if (tile.entity.items.total() >= minDistribute && linkValid(tile) && //only fire when at least at half-capacity and power tile.entity.power.amount >= powerCapacity && - link.block().itemCapacity - link.entity.items.totalItems() >= minDistribute && entity.reload <= 0.0001f) { + link.block().itemCapacity - link.entity.items.total() >= minDistribute && entity.reload <= 0.0001f) { MassDriverEntity other = link.entity(); other.waiting.add(tile); @@ -150,7 +150,7 @@ public class MassDriver extends Block { @Override public boolean acceptItem(Item item, Tile tile, Tile source) { - return tile.entity.items.totalItems() < itemCapacity; + return tile.entity.items.total() < itemCapacity; } @Override @@ -221,7 +221,7 @@ public class MassDriver extends Block { public float reload = 0f; public void handlePayload(Bullet bullet, DriverBulletData data){ - int totalItems = items.totalItems(); + int totalItems = items.total(); //add all the items possible for(int i = 0; i < data.items.length; i ++){ diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/Router.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Router.java index f34158d190..cd23e1990e 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Router.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Router.java @@ -21,10 +21,10 @@ public class Router extends Block{ @Override public void update(Tile tile){ int iterations = Math.max(1, (int) (Timers.delta() + 0.4f)); - boolean moved = tile.entity.items.totalItems() > 0; + boolean moved = tile.entity.items.total() > 0; for(int i = 0; i < iterations; i ++) { - if (tile.entity.items.totalItems() > 0) { + if (tile.entity.items.total() > 0) { tryDump(tile); moved = true; } @@ -37,7 +37,7 @@ public class Router extends Block{ @Override public boolean canDump(Tile tile, Tile to, Item item) { - return !(to.block() instanceof Router) || ((float) to.target().entity.items.totalItems() / to.target().block().itemCapacity) < ((float) tile.entity.items.totalItems() / to.target().block().itemCapacity); + return !(to.block() instanceof Router) || ((float) to.target().entity.items.total() / to.target().block().itemCapacity) < ((float) tile.entity.items.total() / to.target().block().itemCapacity); } @Override @@ -48,7 +48,7 @@ public class Router extends Block{ @Override public boolean acceptItem(Item item, Tile tile, Tile source){ - int items = tile.entity.items.totalItems(); + int items = tile.entity.items.total(); return items < itemCapacity; } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/TunnelConduit.java b/core/src/io/anuke/mindustry/world/blocks/distribution/TunnelConduit.java index 1b636cd22a..a342a41efc 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/TunnelConduit.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/TunnelConduit.java @@ -33,7 +33,7 @@ public class TunnelConduit extends LiquidBlock { @Override public void draw(Tile tile){ - Draw.rect(name, tile.drawx(), tile.drawy(), tile.getRotation() * 90); + Draw.rect(region, tile.drawx(), tile.drawy(), tile.getRotation() * 90); } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/WarpGate.java b/core/src/io/anuke/mindustry/world/blocks/distribution/WarpGate.java index 622170e01a..5d7b1488b6 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/WarpGate.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/WarpGate.java @@ -147,7 +147,7 @@ public class WarpGate extends PowerBlock{ teleporters[entity.color].add(tile); - if(entity.items.totalItems() > 0){ + if(entity.items.total() > 0){ tryDump(tile); } @@ -210,7 +210,7 @@ public class WarpGate extends PowerBlock{ entity.time += Timers.delta() * entity.speedScl; - if (!entity.teleporting && entity.items.totalItems() >= itemCapacity && entity.power.amount >= powerCapacity - 0.01f - powerUse && + if (!entity.teleporting && entity.items.total() >= itemCapacity && entity.power.amount >= powerCapacity - 0.01f - powerUse && entity.timer.get(timerTeleport, teleportMax)) { Array testLinks = findLinks(tile); @@ -226,12 +226,12 @@ public class WarpGate extends PowerBlock{ Array links = findLinks(tile); for (Tile other : links) { - int canAccept = itemCapacity - other.entity.items.totalItems(); - int total = entity.items.totalItems(); + int canAccept = itemCapacity - other.entity.items.total(); + int total = entity.items.total(); if (total == 0) break; Effects.effect(teleportOutEffect, resultColor, other.drawx(), other.drawy()); for (int i = 0; i < canAccept && i < total; i++) { - other.entity.items.addItem(entity.items.takeItem(), 1); + other.entity.items.add(entity.items.take(), 1); } } Effects.effect(teleportOutEffect, resultColor, tile.drawx(), tile.drawy()); @@ -271,7 +271,7 @@ public class WarpGate extends PowerBlock{ @Override public boolean acceptItem(Item item, Tile tile, Tile source){ TeleporterEntity entity = tile.entity(); - return entity.items.totalItems() < itemCapacity; + return entity.items.total() < itemCapacity; } @Override @@ -323,7 +323,7 @@ public class WarpGate extends PowerBlock{ if(!oe.active) continue; if(oe.color != entity.color){ removal.add(other); - }else if(other.entity.items.totalItems() == 0){ + }else if(other.entity.items.total() == 0){ returns.add(other); } }else{ diff --git a/core/src/io/anuke/mindustry/world/blocks/modules/InventoryModule.java b/core/src/io/anuke/mindustry/world/blocks/modules/InventoryModule.java deleted file mode 100644 index b989c9b952..0000000000 --- a/core/src/io/anuke/mindustry/world/blocks/modules/InventoryModule.java +++ /dev/null @@ -1,110 +0,0 @@ -package io.anuke.mindustry.world.blocks.modules; - -import io.anuke.mindustry.type.Item; -import io.anuke.mindustry.type.ItemStack; -import io.anuke.mindustry.world.blocks.BlockModule; - -import java.io.*; -import java.util.Arrays; - -public class InventoryModule extends BlockModule{ - //TODO make private! - public int[] items = new int[Item.all().size]; - - public boolean hasItems(ItemStack[] stacks){ - for(ItemStack stack : stacks){ - if(!hasItem(stack.item, stack.amount)) return false; - } - return true; - } - - public boolean hasItems(ItemStack[] stacks, float amountScaling){ - for(ItemStack stack : stacks){ - if(!hasItem(stack.item, (int)(stack.amount * amountScaling))) return false; - } - return true; - } - - /**Returns true if this entity has at least one of each item in each stack.*/ - public boolean hasAtLeastOneOfItems(ItemStack[] stacks){ - for(ItemStack stack : stacks){ - if(!hasItem(stack.item, 1)) return false; - } - return true; - } - - //TODO optimize! - public int totalItems(){ - int sum = 0; - for(int i = 0; i < items.length; i ++){ - sum += items[i]; - } - return sum; - } - - public Item takeItem(){ - for(int i = 0; i < items.length; i ++){ - if(items[i] > 0){ - items[i] --; - return Item.getByID(i); - } - } - return null; - } - - public int getItem(Item item){ - return items[item.id]; - } - - public boolean hasItem(Item item){ - return getItem(item) > 0; - } - - public boolean hasItem(Item item, int amount){ - return getItem(item) >= amount; - } - - public void addItem(Item item, int amount){ - items[item.id] += amount; - } - - public void removeItem(Item item, int amount){ - items[item.id] -= amount; - } - - public void removeItem(ItemStack stack){ - items[stack.item.id] -= stack.amount; - } - - public void clear(){ - Arrays.fill(items, 0); - } - - @Override - public void write(DataOutput stream) throws IOException { - byte amount = 0; - for(int i = 0; i < items.length; i ++){ - if(items[i] > 0) amount ++; - } - - stream.writeByte(amount); //amount of items - - for(int i = 0; i < items.length; i ++){ - if(items[i] > 0){ - stream.writeByte(i); //item ID - stream.writeInt(items[i]); //item amount - } - } - } - - @Override - public void read(DataInput stream) throws IOException { - byte count = stream.readByte(); - - for(int j = 0; j < count; j ++){ - int itemid = stream.readByte(); - int itemamount = stream.readInt(); - items[itemid] = itemamount; - } - } -} diff --git a/core/src/io/anuke/mindustry/world/blocks/modules/LiquidModule.java b/core/src/io/anuke/mindustry/world/blocks/modules/LiquidModule.java deleted file mode 100644 index 3b26a62247..0000000000 --- a/core/src/io/anuke/mindustry/world/blocks/modules/LiquidModule.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.anuke.mindustry.world.blocks.modules; - -import io.anuke.mindustry.content.Liquids; -import io.anuke.mindustry.type.Liquid; -import io.anuke.mindustry.world.blocks.BlockModule; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; - -public class LiquidModule extends BlockModule { - public float amount; - /**Should never be null.*/ - public Liquid liquid = Liquids.none; - - @Override - public void write(DataOutput stream) throws IOException { - stream.writeByte(liquid.id); - stream.writeFloat(amount); - } - - @Override - public void read(DataInput stream) throws IOException{ - byte id = stream.readByte(); - liquid = Liquid.getByID(id); - amount = stream.readFloat(); - } -} diff --git a/core/src/io/anuke/mindustry/world/blocks/power/FusionReactor.java b/core/src/io/anuke/mindustry/world/blocks/power/FusionReactor.java index f1bed4b378..a5da34207e 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/FusionReactor.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/FusionReactor.java @@ -93,7 +93,7 @@ public class FusionReactor extends PowerGenerator { Graphics.setNormalBlending(); - Draw.rect(name, tile.drawx(), tile.drawy()); + Draw.rect(region, tile.drawx(), tile.drawy()); Draw.rect(name + "-top", tile.drawx(), tile.drawy()); diff --git a/core/src/io/anuke/mindustry/world/blocks/power/ItemGenerator.java b/core/src/io/anuke/mindustry/world/blocks/power/ItemGenerator.java index 63f551c112..265ae49baf 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/ItemGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/ItemGenerator.java @@ -55,7 +55,7 @@ public abstract class ItemGenerator extends PowerGenerator { @Override public void setBars(){ super.setBars(); - bars.replace(new BlockBar(BarType.inventory, true, tile -> (float)tile.entity.items.totalItems() / itemCapacity)); + bars.replace(new BlockBar(BarType.inventory, true, tile -> (float)tile.entity.items.total() / itemCapacity)); } @Override @@ -66,7 +66,7 @@ public abstract class ItemGenerator extends PowerGenerator { if(entity.generateTime > 0){ Draw.color(heatColor); - float alpha = (entity.items.totalItems() > 0 ? 1f : Mathf.clamp(entity.generateTime)); + float alpha = (entity.items.total() > 0 ? 1f : Mathf.clamp(entity.generateTime)); alpha = alpha * 0.7f + Mathf.absin(Timers.time(), 12f, 0.3f) * alpha; Draw.alpha(alpha); Draw.rect(topRegion, tile.drawx(), tile.drawy()); @@ -76,7 +76,7 @@ public abstract class ItemGenerator extends PowerGenerator { @Override public boolean acceptItem(Item item, Tile tile, Tile source){ - return getItemEfficiency(item) >= minItemEfficiency && tile.entity.items.totalItems() < itemCapacity; + return getItemEfficiency(item) >= minItemEfficiency && tile.entity.items.total() < itemCapacity; } @Override @@ -97,7 +97,7 @@ public abstract class ItemGenerator extends PowerGenerator { } } - if(entity.generateTime <= 0f && entity.items.totalItems() > 0){ + if(entity.generateTime <= 0f && entity.items.total() > 0){ Effects.effect(generateEffect, tile.worldx() + Mathf.range(size * tilesize/2f), tile.worldy() + Mathf.range(size * tilesize/2f)); for(int i = 0; i < entity.items.items.length; i ++){ if(entity.items.items[i] > 0){ diff --git a/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java b/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java index 82e06c95d1..6adc8bf50f 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java @@ -64,7 +64,7 @@ public abstract class ItemLiquidGenerator extends ItemGenerator { } } - if (entity.generateTime <= 0f && entity.items.totalItems() > 0) { + if (entity.generateTime <= 0f && entity.items.total() > 0) { Effects.effect(generateEffect, tile.worldx() + Mathf.range(3f), tile.worldy() + Mathf.range(3f)); for (int i = 0; i < entity.items.items.length; i++) { if (entity.items.items[i] > 0) { diff --git a/core/src/io/anuke/mindustry/world/blocks/power/NuclearReactor.java b/core/src/io/anuke/mindustry/world/blocks/power/NuclearReactor.java index 55e6ce750c..1184da288b 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/NuclearReactor.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/NuclearReactor.java @@ -57,7 +57,7 @@ public class NuclearReactor extends PowerGenerator { @Override public void setBars(){ super.setBars(); - bars.replace(new BlockBar(BarType.inventory, true, tile -> (float)tile.entity.items.getItem(generateItem) / itemCapacity)); + bars.replace(new BlockBar(BarType.inventory, true, tile -> (float)tile.entity.items.get(generateItem) / itemCapacity)); bars.add(new BlockBar(BarType.heat, true, tile -> tile.entity().heat)); } @@ -73,7 +73,7 @@ public class NuclearReactor extends PowerGenerator { public void update(Tile tile){ NuclearReactorEntity entity = tile.entity(); - int fuel = entity.items.getItem(generateItem); + int fuel = entity.items.get(generateItem); float fullness = (float)fuel / itemCapacity; if(fuel > 0){ @@ -81,7 +81,7 @@ public class NuclearReactor extends PowerGenerator { entity.power.amount += powerMultiplier * fullness * Timers.delta(); entity.power.amount = Mathf.clamp(entity.power.amount, 0f, powerCapacity); if(entity.timer.get(timerFuel, fuelUseTime)){ - entity.items.removeItem(generateItem, 1); + entity.items.remove(generateItem, 1); } } @@ -123,7 +123,7 @@ public class NuclearReactor extends PowerGenerator { NuclearReactorEntity entity = tile.entity(); - int fuel = entity.items.getItem(generateItem); + int fuel = entity.items.get(generateItem); if(fuel < 5 && entity.heat < 0.5f) return; @@ -155,7 +155,7 @@ public class NuclearReactor extends PowerGenerator { @Override public boolean acceptItem(Item item, Tile tile, Tile source){ - return item == generateItem && tile.entity.items.getItem(generateItem) < itemCapacity; + return item == generateItem && tile.entity.items.get(generateItem) < itemCapacity; } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Compressor.java b/core/src/io/anuke/mindustry/world/blocks/production/Compressor.java index a7b85759f5..ffe9544a59 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Compressor.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Compressor.java @@ -23,14 +23,14 @@ public class Compressor extends PowerCrafter { float liquidAdded = Math.min(outputLiquidAmount * Timers.delta(), liquidCapacity - entity.liquids.amount); int itemsUsed = Mathf.ceil(1 + input.amount * entity.progress); - if(entity.power.amount > powerUsed && entity.items.hasItem(input.item, itemsUsed) && liquidAdded > 0.001f){ + if(entity.power.amount > powerUsed && entity.items.has(input.item, itemsUsed) && liquidAdded > 0.001f){ entity.progress += 1f/craftTime; entity.totalProgress += Timers.delta(); handleLiquid(tile, tile, outputLiquid, liquidAdded); } if(entity.progress >= 1f){ - entity.items.removeItem(input); + entity.items.remove(input); if(outputItem != null) offloadNear(tile, outputItem); entity.progress = 0f; } @@ -48,7 +48,7 @@ public class Compressor extends PowerCrafter { public void draw(Tile tile) { GenericCrafterEntity entity = tile.entity(); - Draw.rect(name, tile.drawx(), tile.drawy()); + Draw.rect(region, tile.drawx(), tile.drawy()); Draw.rect(name + "-frame" + (int) Mathf.absin(entity.totalProgress, 5f, 2.999f), tile.drawx(), tile.drawy()); Draw.color(Color.CLEAR, tile.entity.liquids.liquid.color, tile.entity.liquids.amount / liquidCapacity); Draw.rect(name + "-liquid", tile.drawx(), tile.drawy()); diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Cultivator.java b/core/src/io/anuke/mindustry/world/blocks/production/Cultivator.java index 52bf4488a4..2af65d16a4 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Cultivator.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Cultivator.java @@ -57,7 +57,7 @@ public class Cultivator extends Drill { public void draw(Tile tile) { CultivatorEntity entity = tile.entity(); - Draw.rect(name, tile.drawx(), tile.drawy()); + Draw.rect(region, tile.drawx(), tile.drawy()); Draw.color(plantColor); Draw.alpha(entity.warmup); diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Drill.java b/core/src/io/anuke/mindustry/world/blocks/production/Drill.java index 1856f63c32..0de56aea3b 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Drill.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Drill.java @@ -91,7 +91,7 @@ public class Drill extends Block{ DrillEntity entity = tile.entity(); - Draw.rect(name, tile.drawx(), tile.drawy()); + Draw.rect(region, tile.drawx(), tile.drawy()); if(drawRim) { Graphics.setAdditiveBlending(); @@ -178,7 +178,7 @@ public class Drill extends Block{ float powerUsed = Math.min(powerCapacity, powerUse * Timers.delta()); float liquidUsed = Math.min(liquidCapacity, liquidUse * Timers.delta()); - if(entity.items.totalItems() < itemCapacity && toAdd.size > 0 && + if(entity.items.total() < itemCapacity && toAdd.size > 0 && (!hasPower || entity.power.amount >= powerUsed) && (!liquidRequired || entity.liquids.amount >= liquidUsed)){ @@ -203,7 +203,7 @@ public class Drill extends Block{ } if(toAdd.size > 0 && entity.progress >= drillTime + hardnessDrillMultiplier*Math.max(totalHardness, 1f)/multiplier - && tile.entity.items.totalItems() < itemCapacity){ + && tile.entity.items.total() < itemCapacity){ int index = entity.index % toAdd.size; offloadNear(tile, toAdd.get(index)); diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Fracker.java b/core/src/io/anuke/mindustry/world/blocks/production/Fracker.java index 16465d6d45..7b7c68c50e 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Fracker.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Fracker.java @@ -10,7 +10,6 @@ import io.anuke.mindustry.world.meta.BlockStat; import io.anuke.mindustry.world.meta.StatUnit; import io.anuke.ucore.core.Timers; import io.anuke.ucore.graphics.Draw; -import io.anuke.ucore.util.Log; public class Fracker extends SolidPump { protected Liquid inputLiquid; @@ -51,7 +50,7 @@ public class Fracker extends SolidPump { public void draw(Tile tile) { FrackerEntity entity = tile.entity(); - Draw.rect(name, tile.drawx(), tile.drawy()); + Draw.rect(region, tile.drawx(), tile.drawy()); Draw.color(tile.entity.liquids.liquid.color); Draw.alpha(tile.entity.liquids.amount/liquidCapacity); @@ -71,8 +70,8 @@ public class Fracker extends SolidPump { public void update(Tile tile) { FrackerEntity entity = tile.entity(); - while(entity.accumulator > itemUseTime && entity.items.hasItem(inputItem, 1)){ - entity.items.removeItem(inputItem, 1); + while(entity.accumulator > itemUseTime && entity.items.has(inputItem, 1)){ + entity.items.remove(inputItem, 1); entity.accumulator -= itemUseTime; } @@ -87,7 +86,7 @@ public class Fracker extends SolidPump { @Override public boolean acceptItem(Item item, Tile tile, Tile source) { - return item == inputItem && tile.entity.items.totalItems() < itemCapacity; + return item == inputItem && tile.entity.items.total() < itemCapacity; } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java b/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java index 1173847e9b..7abfd25839 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java @@ -51,7 +51,7 @@ public class GenericCrafter extends Block{ super.setBars(); if(inputItem != null) bars.replace(new BlockBar(BarType.inventory, true, - tile -> (float)tile.entity.items.getItem(inputItem.item) / itemCapacity)); + tile -> (float)tile.entity.items.get(inputItem.item) / itemCapacity)); } @Override @@ -93,7 +93,7 @@ public class GenericCrafter extends Block{ if((!hasLiquids || entity.liquids.amount >= liquidUsed) && (!hasPower || entity.power.amount >= powerUsed) && - (inputItem == null || entity.items.hasItem(inputItem.item, itemsUsed))){ + (inputItem == null || entity.items.has(inputItem.item, itemsUsed))){ entity.progress += 1f / craftTime * Timers.delta(); entity.totalProgress += Timers.delta(); @@ -109,7 +109,7 @@ public class GenericCrafter extends Block{ if(entity.progress >= 1f){ - if(inputItem != null) tile.entity.items.removeItem(inputItem); + if(inputItem != null) tile.entity.items.remove(inputItem); offloadNear(tile, output); Effects.effect(craftEffect, tile.drawx(), tile.drawy()); entity.progress = 0f; @@ -128,7 +128,7 @@ public class GenericCrafter extends Block{ @Override public boolean acceptItem(Item item, Tile tile, Tile source){ TileEntity entity = tile.entity(); - return inputItem != null && item == inputItem.item && entity.items.getItem(inputItem.item) < itemCapacity; + return inputItem != null && item == inputItem.item && entity.items.get(inputItem.item) < itemCapacity; } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/production/LiquidMixer.java b/core/src/io/anuke/mindustry/world/blocks/production/LiquidMixer.java index 3f8523af20..02b889fc34 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/LiquidMixer.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/LiquidMixer.java @@ -46,21 +46,21 @@ public class LiquidMixer extends LiquidBlock{ @Override public boolean acceptItem(Item item, Tile tile, Tile source) { - return item == inputItem && tile.entity.items.getItem(item) < itemCapacity; + return item == inputItem && tile.entity.items.get(item) < itemCapacity; } @Override public float handleAuxLiquid(Tile tile, Tile source, Liquid liquid, float amount) { LiquidMixerEntity entity = tile.entity(); - if(liquid == inputLiquid && tile.entity.items.hasItem(inputItem, (int)((entity.accumulator + amount)/amount)) && + if(liquid == inputLiquid && tile.entity.items.has(inputItem, (int)((entity.accumulator + amount)/amount)) && tile.entity.power.amount >= powerUse){ amount = Math.min(liquidCapacity - tile.entity.liquids.amount, amount); entity.accumulator += amount; int items = (int)(entity.accumulator / liquidPerItem); - entity.items.removeItem(inputItem, items); + entity.items.remove(inputItem, items); entity.accumulator %= liquidPerItem; entity.liquids.liquid = outputLiquid; entity.liquids.amount += amount; diff --git a/core/src/io/anuke/mindustry/world/blocks/production/PhaseWeaver.java b/core/src/io/anuke/mindustry/world/blocks/production/PhaseWeaver.java index baa50bb952..d58545f44f 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/PhaseWeaver.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/PhaseWeaver.java @@ -64,6 +64,6 @@ public class PhaseWeaver extends PowerSmelter{ Draw.reset(); } - Draw.rect(name, tile.drawx(), tile.drawy()); + Draw.rect(region, tile.drawx(), tile.drawy()); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/production/PowerCrafter.java b/core/src/io/anuke/mindustry/world/blocks/production/PowerCrafter.java index bec6388a72..efe340ce3a 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/PowerCrafter.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/PowerCrafter.java @@ -59,13 +59,13 @@ public class PowerCrafter extends Block{ float powerUsed = Math.min(Timers.delta() * powerUse, tile.entity.power.amount); int itemsUsed = Mathf.ceil(1 + input.amount * entity.progress); - if(entity.power.amount > powerUsed && entity.items.hasItem(input.item, itemsUsed)){ + if(entity.power.amount > powerUsed && entity.items.has(input.item, itemsUsed)){ entity.progress += 1f/craftTime; entity.totalProgress += Timers.delta(); } if(entity.progress >= 1f){ - entity.items.removeItem(input); + entity.items.remove(input); if(outputItem != null) offloadNear(tile, outputItem); if(outputLiquid != null) handleLiquid(tile, tile, outputLiquid, outputLiquidAmount); entity.progress = 0f; @@ -82,7 +82,7 @@ public class PowerCrafter extends Block{ @Override public boolean acceptItem(Item item, Tile tile, Tile source) { - return item == input.item && tile.entity.items.getItem(input.item) < itemCapacity; + return item == input.item && tile.entity.items.get(input.item) < itemCapacity; } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/production/PowerSmelter.java b/core/src/io/anuke/mindustry/world/blocks/production/PowerSmelter.java index fc552c8135..dcc147c953 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/PowerSmelter.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/PowerSmelter.java @@ -71,7 +71,7 @@ public class PowerSmelter extends PowerBlock { bars.remove(BarType.inventory); for(ItemStack item : inputs){ - bars.add(new BlockBar(BarType.inventory, true, tile -> (float) tile.entity.items.getItem(item.item) / itemCapacity)); + bars.add(new BlockBar(BarType.inventory, true, tile -> (float) tile.entity.items.get(item.item) / itemCapacity)); } } @@ -93,7 +93,7 @@ public class PowerSmelter extends PowerBlock { PowerSmelterEntity entity = tile.entity(); - if(entity.timer.get(timerDump, 5) && entity.items.hasItem(result)){ + if(entity.timer.get(timerDump, 5) && entity.items.has(result)){ tryDump(tile, result); } @@ -114,12 +114,12 @@ public class PowerSmelter extends PowerBlock { //make sure it has all the items for(ItemStack item : inputs){ - if(!entity.items.hasItem(item.item, item.amount)){ + if(!entity.items.has(item.item, item.amount)){ return; } } - if(entity.items.getItem(result) >= itemCapacity //output full + if(entity.items.get(result) >= itemCapacity //output full || entity.heat <= minHeat //not burning || !entity.timer.get(timerCraft, craftTime)){ //not yet time return; @@ -130,8 +130,8 @@ public class PowerSmelter extends PowerBlock { if(useFlux){ //remove flux materials if present for(Item item : Item.all()){ - if(item.fluxiness >= minFlux && tile.entity.items.getItem(item) >= fluxNeeded){ - tile.entity.items.removeItem(item, fluxNeeded); + if(item.fluxiness >= minFlux && tile.entity.items.get(item) >= fluxNeeded){ + tile.entity.items.remove(item, fluxNeeded); //chance of not consuming inputs if flux material present consumeInputs = !Mathf.chance(item.fluxiness * baseFluxChance); @@ -142,7 +142,7 @@ public class PowerSmelter extends PowerBlock { if(consumeInputs) { for (ItemStack item : inputs) { - entity.items.removeItem(item.item, item.amount); + entity.items.remove(item.item, item.amount); } } @@ -155,12 +155,12 @@ public class PowerSmelter extends PowerBlock { for(ItemStack stack : inputs){ if(stack.item == item){ - return tile.entity.items.getItem(item) < itemCapacity; + return tile.entity.items.get(item) < itemCapacity; } } if(useFlux && item.fluxiness >= minFlux){ - return tile.entity.items.getItem(item) < itemCapacity; + return tile.entity.items.get(item) < itemCapacity; } return false; @@ -168,7 +168,7 @@ public class PowerSmelter extends PowerBlock { @Override public int getMaximumAccepted(Tile tile, Item item) { - return itemCapacity - tile.entity.items.getItem(item); + return itemCapacity - tile.entity.items.get(item); } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Pulverizer.java b/core/src/io/anuke/mindustry/world/blocks/production/Pulverizer.java index d9bdfe2930..c96c0ec054 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Pulverizer.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Pulverizer.java @@ -5,18 +5,26 @@ import io.anuke.mindustry.world.Tile; import io.anuke.ucore.graphics.Draw; public class Pulverizer extends GenericCrafter { + protected TextureRegion rotatorRegion; public Pulverizer(String name) { super(name); hasItems = true; } + @Override + public void load() { + super.load(); + + rotatorRegion = Draw.region(name + "-rotator"); + } + @Override public void draw(Tile tile) { GenericCrafterEntity entity = tile.entity(); - Draw.rect(name, tile.drawx(), tile.drawy()); - Draw.rect(name + "-rotator", tile.drawx(), tile.drawy(), entity.totalProgress * 2f); + Draw.rect(region, tile.drawx(), tile.drawy()); + Draw.rect(rotatorRegion, tile.drawx(), tile.drawy(), entity.totalProgress * 2f); } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Separator.java b/core/src/io/anuke/mindustry/world/blocks/production/Separator.java index b150976239..2ee107fb5f 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Separator.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Separator.java @@ -1,6 +1,7 @@ package io.anuke.mindustry.world.blocks.production; import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.TextureRegion; import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.type.Item; import io.anuke.mindustry.type.Liquid; @@ -30,6 +31,9 @@ public class Separator extends Block { protected float spinnerThickness = 1f; protected float spinnerSpeed = 2f; + protected Color color = Color.valueOf("858585"); + protected TextureRegion liquidRegion; + protected boolean offloading = false; public Separator(String name) { @@ -40,6 +44,13 @@ public class Separator extends Block { hasLiquids = true; } + @Override + public void load() { + super.load(); + + liquidRegion = Draw.region(name + "-liquid"); + } + @Override public void setStats() { super.setStats(); @@ -67,9 +78,9 @@ public class Separator extends Block { Draw.color(tile.entity.liquids.liquid.color); Draw.alpha(tile.entity.liquids.amount / liquidCapacity); - Draw.rect(name + "-liquid", tile.drawx(), tile.drawy()); + Draw.rect(liquidRegion, tile.drawx(), tile.drawy()); - Draw.color(Color.valueOf("858585")); + Draw.color(color); Lines.stroke(spinnerThickness); Lines.spikes(tile.drawx(), tile.drawy(), spinnerRadius, spinnerLength, 3, entity.totalProgress*spinnerSpeed); Draw.reset(); @@ -84,7 +95,7 @@ public class Separator extends Block { entity.totalProgress += entity.warmup*Timers.delta(); - if(entity.liquids.amount >= liquidUsed && entity.items.hasItem(item) && + if(entity.liquids.amount >= liquidUsed && entity.items.has(item) && (!hasPower || entity.power.amount >= powerUsed)){ entity.progress += 1f/filterTime; entity.liquids.amount -= liquidUsed; @@ -98,7 +109,7 @@ public class Separator extends Block { if(entity.progress >= 1f){ entity.progress = 0f; Item item = Mathf.select(results); - entity.items.removeItem(this.item, 1); + entity.items.remove(this.item, 1); if(item != null){ offloading = true; offloadNear(tile, item); @@ -123,7 +134,7 @@ public class Separator extends Block { @Override public boolean acceptItem(Item item, Tile tile, Tile source) { - return this.item == item && tile.entity.items.getItem(item) < itemCapacity; + return this.item == item && tile.entity.items.get(item) < itemCapacity; } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Smelter.java b/core/src/io/anuke/mindustry/world/blocks/production/Smelter.java index 303aded079..5deb96b27b 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Smelter.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Smelter.java @@ -47,7 +47,7 @@ public class Smelter extends Block{ @Override public void setBars(){ for(ItemStack item : inputs){ - bars.add(new BlockBar(BarType.inventory, true, tile -> (float)tile.entity.items.getItem(item.item)/itemCapacity)); + bars.add(new BlockBar(BarType.inventory, true, tile -> (float)tile.entity.items.get(item.item)/itemCapacity)); } } @@ -79,13 +79,13 @@ public class Smelter extends Block{ public void update(Tile tile){ SmelterEntity entity = tile.entity(); - if(entity.timer.get(timerDump, 5) && entity.items.hasItem(result)){ + if(entity.timer.get(timerDump, 5) && entity.items.has(result)){ tryDump(tile, result); } //add fuel - if(entity.items.getItem(fuel) > 0 && entity.burnTime <= 0f){ - entity.items.removeItem(fuel, 1); + if(entity.items.get(fuel) > 0 && entity.burnTime <= 0f){ + entity.items.remove(fuel, 1); entity.burnTime += burnDuration; Effects.effect(burnEffect, entity.x + Mathf.range(2f), entity.y + Mathf.range(2f)); } @@ -100,12 +100,12 @@ public class Smelter extends Block{ //make sure it has all the items for(ItemStack item : inputs){ - if(!entity.items.hasItem(item.item, item.amount)){ + if(!entity.items.has(item.item, item.amount)){ return; } } - if(entity.items.getItem(result) >= itemCapacity //output full + if(entity.items.get(result) >= itemCapacity //output full || entity.burnTime <= 0 //not burning || !entity.timer.get(timerCraft, craftTime)){ //not yet time return; @@ -116,8 +116,8 @@ public class Smelter extends Block{ if(useFlux){ //remove flux materials if present for(Item item : Item.all()){ - if(item.fluxiness >= minFlux && tile.entity.items.getItem(item) > 0){ - tile.entity.items.removeItem(item, 1); + if(item.fluxiness >= minFlux && tile.entity.items.get(item) > 0){ + tile.entity.items.remove(item, 1); //chance of not consuming inputs if flux material present consumeInputs = !Mathf.chance(item.fluxiness * baseFluxChance); @@ -128,7 +128,7 @@ public class Smelter extends Block{ if(consumeInputs) { for (ItemStack item : inputs) { - entity.items.removeItem(item.item, item.amount); + entity.items.remove(item.item, item.amount); } } @@ -138,7 +138,7 @@ public class Smelter extends Block{ @Override public int getMaximumAccepted(Tile tile, Item item) { - return itemCapacity - tile.entity.items.getItem(item); + return itemCapacity - tile.entity.items.get(item); } @Override @@ -152,8 +152,8 @@ public class Smelter extends Block{ } } - return (isInput && tile.entity.items.getItem(item) < itemCapacity) || (item == fuel && tile.entity.items.getItem(fuel) < itemCapacity) || - (useFlux && item.fluxiness >= minFlux && tile.entity.items.getItem(item) < itemCapacity); + return (isInput && tile.entity.items.get(item) < itemCapacity) || (item == fuel && tile.entity.items.get(fuel) < itemCapacity) || + (useFlux && item.fluxiness >= minFlux && tile.entity.items.get(item) < itemCapacity); } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/production/SolidPump.java b/core/src/io/anuke/mindustry/world/blocks/production/SolidPump.java index a15ff7c5ea..6a4cfd6ccc 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/SolidPump.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/SolidPump.java @@ -43,7 +43,7 @@ public class SolidPump extends Pump { public void draw(Tile tile) { SolidPumpEntity entity = tile.entity(); - Draw.rect(name, tile.drawx(), tile.drawy()); + Draw.rect(region, tile.drawx(), tile.drawy()); Draw.color(tile.entity.liquids.liquid.color); Draw.alpha(tile.entity.liquids.amount / liquidCapacity); Draw.rect(liquidRegion, tile.drawx(), tile.drawy()); diff --git a/core/src/io/anuke/mindustry/world/blocks/storage/CoreBlock.java b/core/src/io/anuke/mindustry/world/blocks/storage/CoreBlock.java index ea0189c6ef..6a64afcb31 100644 --- a/core/src/io/anuke/mindustry/world/blocks/storage/CoreBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/storage/CoreBlock.java @@ -133,7 +133,7 @@ public class CoreBlock extends StorageBlock { @Override public int acceptStack(Item item, int amount, Tile tile, Unit source){ if(acceptItem(item, tile, tile) && hasItems && source.getTeam() == tile.getTeam()){ - return Math.min(itemCapacity - tile.entity.items.getItem(item), amount); + return Math.min(itemCapacity - tile.entity.items.get(item), amount); }else{ return 0; } diff --git a/core/src/io/anuke/mindustry/world/blocks/storage/SortedUnloader.java b/core/src/io/anuke/mindustry/world/blocks/storage/SortedUnloader.java index 7d65048455..de4c79d10c 100644 --- a/core/src/io/anuke/mindustry/world/blocks/storage/SortedUnloader.java +++ b/core/src/io/anuke/mindustry/world/blocks/storage/SortedUnloader.java @@ -30,16 +30,16 @@ public class SortedUnloader extends Unloader implements SelectionTrait{ public void update(Tile tile){ SortedUnloaderEntity entity = tile.entity(); - if(entity.items.totalItems() == 0 && entity.timer.get(timerUnload, speed)){ + if(entity.items.total() == 0 && entity.timer.get(timerUnload, speed)){ tile.allNearby(other -> { - if(other.block() instanceof StorageBlock && entity.items.totalItems() == 0 && + if(other.block() instanceof StorageBlock && entity.items.total() == 0 && ((StorageBlock)other.block()).hasItem(other, entity.sortItem)){ offloadNear(tile, ((StorageBlock)other.block()).removeItem(other, entity.sortItem)); } }); } - if(entity.items.totalItems() > 0){ + if(entity.items.total() > 0){ tryDump(tile); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/storage/Unloader.java b/core/src/io/anuke/mindustry/world/blocks/storage/Unloader.java index 71d801153e..aa59e769c7 100644 --- a/core/src/io/anuke/mindustry/world/blocks/storage/Unloader.java +++ b/core/src/io/anuke/mindustry/world/blocks/storage/Unloader.java @@ -20,16 +20,16 @@ public class Unloader extends Block { @Override public void update(Tile tile){ - if(tile.entity.items.totalItems() == 0 && tile.entity.timer.get(timerUnload, speed)){ + if(tile.entity.items.total() == 0 && tile.entity.timer.get(timerUnload, speed)){ tile.allNearby(other -> { - if(other.block() instanceof StorageBlock && tile.entity.items.totalItems() == 0 && + if(other.block() instanceof StorageBlock && tile.entity.items.total() == 0 && ((StorageBlock)other.block()).hasItem(other, null)){ offloadNear(tile, ((StorageBlock)other.block()).removeItem(other, null)); } }); } - if(tile.entity.items.totalItems() > 0){ + if(tile.entity.items.total() > 0){ tryDump(tile); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/storage/Vault.java b/core/src/io/anuke/mindustry/world/blocks/storage/Vault.java index 314f22b0fc..54a9bffd28 100644 --- a/core/src/io/anuke/mindustry/world/blocks/storage/Vault.java +++ b/core/src/io/anuke/mindustry/world/blocks/storage/Vault.java @@ -18,7 +18,7 @@ public class Vault extends StorageBlock { int iterations = Math.max(1, (int) (Timers.delta() + 0.4f)); for(int i = 0; i < iterations; i ++) { - if (tile.entity.items.totalItems() > 0) { + if (tile.entity.items.total() > 0) { tryDump(tile); } } @@ -31,7 +31,7 @@ public class Vault extends StorageBlock { @Override public boolean acceptItem(Item item, Tile tile, Tile source) { - return tile.entity.items.totalItems() < itemCapacity; + return tile.entity.items.total() < itemCapacity; } @Override @@ -39,7 +39,7 @@ public class Vault extends StorageBlock { to = to.target(); if (!(to.block() instanceof StorageBlock)) return false; - return !(to.block() instanceof Vault) || (float) to.entity.items.totalItems() / to.block().itemCapacity < (float) tile.entity.items.totalItems() / itemCapacity; + return !(to.block() instanceof Vault) || (float) to.entity.items.total() / to.block().itemCapacity < (float) tile.entity.items.total() / itemCapacity; } } diff --git a/core/src/io/anuke/mindustry/world/blocks/units/DropPoint.java b/core/src/io/anuke/mindustry/world/blocks/units/DropPoint.java index 1834531712..b6a294465e 100644 --- a/core/src/io/anuke/mindustry/world/blocks/units/DropPoint.java +++ b/core/src/io/anuke/mindustry/world/blocks/units/DropPoint.java @@ -21,7 +21,7 @@ public class DropPoint extends Block { @Override public void update(Tile tile) { - if (tile.entity.items.totalItems() > 0) { + if (tile.entity.items.total() > 0) { tryDump(tile); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/units/Reconstructor.java b/core/src/io/anuke/mindustry/world/blocks/units/Reconstructor.java index 3722567476..d893038086 100644 --- a/core/src/io/anuke/mindustry/world/blocks/units/Reconstructor.java +++ b/core/src/io/anuke/mindustry/world/blocks/units/Reconstructor.java @@ -112,7 +112,7 @@ public class Reconstructor extends Block{ ReconstructorEntity entity = tile.entity(); if(entity.solid){ - Draw.rect(name, tile.drawx(), tile.drawy()); + Draw.rect(region, tile.drawx(), tile.drawy()); }else{ Draw.rect(openRegion, tile.drawx(), tile.drawy()); } diff --git a/core/src/io/anuke/mindustry/world/blocks/units/ResupplyPoint.java b/core/src/io/anuke/mindustry/world/blocks/units/ResupplyPoint.java index 7c2e6177a7..af0078b72c 100644 --- a/core/src/io/anuke/mindustry/world/blocks/units/ResupplyPoint.java +++ b/core/src/io/anuke/mindustry/world/blocks/units/ResupplyPoint.java @@ -118,7 +118,7 @@ public class ResupplyPoint extends Block{ @Override public boolean acceptItem(Item item, Tile tile, Tile source) { - return tile.entity.items.totalItems() < itemCapacity; + return tile.entity.items.total() < itemCapacity; } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/units/UnitFactory.java b/core/src/io/anuke/mindustry/world/blocks/units/UnitFactory.java index d4fc03f531..c7777c4775 100644 --- a/core/src/io/anuke/mindustry/world/blocks/units/UnitFactory.java +++ b/core/src/io/anuke/mindustry/world/blocks/units/UnitFactory.java @@ -19,7 +19,7 @@ import io.anuke.mindustry.type.ItemStack; import io.anuke.mindustry.world.BarType; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.blocks.modules.InventoryModule; +import io.anuke.mindustry.world.modules.InventoryModule; import io.anuke.mindustry.world.meta.BlockBar; import io.anuke.mindustry.world.meta.BlockStat; import io.anuke.mindustry.world.meta.StatUnit; @@ -154,7 +154,7 @@ public class UnitFactory extends Block { entity.openCountdown = openDuration; for(ItemStack stack : requirements){ - entity.items.removeItem(stack.item, stack.amount); + entity.items.remove(stack.item, stack.amount); } } } @@ -162,7 +162,7 @@ public class UnitFactory extends Block { @Override public boolean acceptItem(Item item, Tile tile, Tile source) { for(ItemStack stack : requirements){ - if(item == stack.item && tile.entity.items.getItem(item) <= stack.amount*2){ + if(item == stack.item && tile.entity.items.get(item) <= stack.amount*2){ return true; } } @@ -176,7 +176,7 @@ public class UnitFactory extends Block { protected boolean hasRequirements(InventoryModule inv, float fraction){ for(ItemStack stack : requirements){ - if(!inv.hasItem(stack.item, (int)(fraction * stack.amount))){ + if(!inv.has(stack.item, (int)(fraction * stack.amount))){ return false; } } diff --git a/core/src/io/anuke/mindustry/world/consumers/Consume.java b/core/src/io/anuke/mindustry/world/consumers/Consume.java new file mode 100644 index 0000000000..977435abae --- /dev/null +++ b/core/src/io/anuke/mindustry/world/consumers/Consume.java @@ -0,0 +1,8 @@ +package io.anuke.mindustry.world.consumers; + +import io.anuke.mindustry.entities.TileEntity; + +public interface Consume { + void update(TileEntity entity); + boolean valid(); +} diff --git a/core/src/io/anuke/mindustry/world/modules/InventoryModule.java b/core/src/io/anuke/mindustry/world/modules/InventoryModule.java new file mode 100644 index 0000000000..cc937769cd --- /dev/null +++ b/core/src/io/anuke/mindustry/world/modules/InventoryModule.java @@ -0,0 +1,145 @@ +package io.anuke.mindustry.world.modules; + +import io.anuke.mindustry.type.Item; +import io.anuke.mindustry.type.ItemStack; +import io.anuke.mindustry.world.blocks.BlockModule; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.Arrays; + +public class InventoryModule extends BlockModule{ + private int[] items = new int[Item.all().size]; + private int total; + + public void forEach(ItemConsumer cons){ + for (int i = 0; i < items.length; i++) { + if(items[i] > 0){ + cons.accept(Item.getByID(i), items[i]); + } + } + } + + public float sum(ItemCalculator calc){ + float sum = 0f; + for (int i = 0; i < items.length; i++) { + if(items[i] > 0){ + sum += calc.get(Item.getByID(i), items[i]); + } + } + return sum; + } + + public boolean has(Item item){ + return get(item) > 0; + } + + public boolean has(Item item, int amount){ + return get(item) >= amount; + } + + public boolean has(ItemStack[] stacks){ + for(ItemStack stack : stacks){ + if(!has(stack.item, stack.amount)) return false; + } + return true; + } + + public boolean has(ItemStack[] stacks, float amountScaling){ + for(ItemStack stack : stacks){ + if(!has(stack.item, (int)(stack.amount * amountScaling))) return false; + } + return true; + } + + /**Returns true if this entity has at least one of each item in each stack.*/ + public boolean hasOne(ItemStack[] stacks){ + for(ItemStack stack : stacks){ + if(!has(stack.item, 1)) return false; + } + return true; + } + + //TODO optimize! + public int total(){ + return total; + } + + public Item take(){ + for(int i = 0; i < items.length; i ++){ + if(items[i] > 0){ + items[i] --; + total --; + return Item.getByID(i); + } + } + return null; + } + + public int get(Item item){ + return items[item.id]; + } + + public void set(Item item, int amount){ + total += (amount - items[item.id]); + items[item.id] = amount; + } + + public void add(Item item, int amount){ + items[item.id] += amount; + total += amount; + } + + public void remove(Item item, int amount){ + items[item.id] -= amount; + total -= amount; + } + + public void remove(ItemStack stack){ + remove(stack.item, stack.amount); + } + + public void clear(){ + Arrays.fill(items, 0); + total = 0; + } + + @Override + public void write(DataOutput stream) throws IOException { + byte amount = 0; + for (int item : items) { + if (item > 0) amount++; + } + + stream.writeByte(amount); //amount of items + + for(int i = 0; i < items.length; i ++){ + if(items[i] > 0){ + stream.writeByte(i); //item ID + stream.writeInt(items[i]); //item amount + } + } + } + + @Override + public void read(DataInput stream) throws IOException { + byte count = stream.readByte(); + total = 0; + + for(int j = 0; j < count; j ++){ + int itemid = stream.readByte(); + int itemamount = stream.readInt(); + items[itemid] = itemamount; + total += itemamount; + } + } + + public interface ItemConsumer{ + void accept(Item item, float amount); + } + + public interface ItemCalculator{ + float get(Item item, int amount); + } +} diff --git a/core/src/io/anuke/mindustry/world/modules/LiquidModule.java b/core/src/io/anuke/mindustry/world/modules/LiquidModule.java new file mode 100644 index 0000000000..57808eee6d --- /dev/null +++ b/core/src/io/anuke/mindustry/world/modules/LiquidModule.java @@ -0,0 +1,95 @@ +package io.anuke.mindustry.world.modules; + +import io.anuke.mindustry.type.Liquid; +import io.anuke.mindustry.world.blocks.BlockModule; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +public class LiquidModule extends BlockModule { + private float[] liquids = new float[Liquid.all().size]; + private float total; + private Liquid current = Liquid.getByID(0); + + /**Returns total amount of liquids.*/ + public float total(){ + return total; + } + + /**Last recieved or loaded liquid. Only valid for liquid modules with 1 type of liquid.*/ + public Liquid current(){ + return current; + } + + public float get(Liquid liquid){ + return liquids[liquid.id]; + } + + public void add(Liquid liquid, float amount){ + liquids[liquid.id] += amount; + total += amount; + } + + public void remove(Liquid liquid, float amount){ + add(liquid, -amount); + } + + public void forEach(LiquidConsumer cons){ + for (int i = 0; i < liquids.length; i++) { + if(liquids[i] > 0){ + cons.accept(Liquid.getByID(i), liquids[i]); + } + } + } + + public float sum(LiquidCalculator calc){ + float sum = 0f; + for (int i = 0; i < liquids.length; i++) { + if(liquids[i] > 0){ + sum += calc.get(Liquid.getByID(i), liquids[i]); + } + } + return sum; + } + + @Override + public void write(DataOutput stream) throws IOException { + byte amount = 0; + for (float liquid : liquids) { + if (liquid > 0) amount++; + } + + stream.writeByte(amount); //amount of liquids + + for(int i = 0; i < liquids.length; i ++){ + if(liquids[i] > 0){ + stream.writeByte(i); //liquid ID + stream.writeFloat(liquids[i]); //item amount + } + } + } + + @Override + public void read(DataInput stream) throws IOException { + byte count = stream.readByte(); + + for(int j = 0; j < count; j ++){ + int liquidid = stream.readByte(); + float amount = stream.readFloat(); + liquids[liquidid] = amount; + if(amount > 0){ + current = Liquid.getByID(liquidid); + } + this.total += amount; + } + } + + public interface LiquidConsumer{ + void accept(Liquid liquid, float amount); + } + + public interface LiquidCalculator{ + float get(Liquid liquid, float amount); + } +} diff --git a/core/src/io/anuke/mindustry/world/blocks/modules/PowerModule.java b/core/src/io/anuke/mindustry/world/modules/PowerModule.java similarity index 94% rename from core/src/io/anuke/mindustry/world/blocks/modules/PowerModule.java rename to core/src/io/anuke/mindustry/world/modules/PowerModule.java index 91c38f521f..f46f9ca817 100644 --- a/core/src/io/anuke/mindustry/world/blocks/modules/PowerModule.java +++ b/core/src/io/anuke/mindustry/world/modules/PowerModule.java @@ -1,4 +1,4 @@ -package io.anuke.mindustry.world.blocks.modules; +package io.anuke.mindustry.world.modules; import io.anuke.mindustry.world.blocks.BlockModule; diff --git a/packer/build.gradle b/packer/build.gradle index 64b054f422..06ce5fd606 100644 --- a/packer/build.gradle +++ b/packer/build.gradle @@ -43,3 +43,12 @@ task generateSprites(dependsOn: classes, type: JavaExec) { standardInput = System.in workingDir = textureFolder } + +task fixBundles(dependsOn: classes, type: JavaExec) { + file(textureFolder).mkdirs() + + main = "io.anuke.mindustry.BundleLauncher" + classpath = sourceSets.main.runtimeClasspath + standardInput = System.in + workingDir = "../core/assets/bundles/" +} diff --git a/packer/src/io/anuke/mindustry/BundleLauncher.java b/packer/src/io/anuke/mindustry/BundleLauncher.java new file mode 100644 index 0000000000..f26ea64ad0 --- /dev/null +++ b/packer/src/io/anuke/mindustry/BundleLauncher.java @@ -0,0 +1,17 @@ +package io.anuke.mindustry; + +import io.anuke.ucore.util.Log; + +import java.io.File; +import java.nio.file.Paths; + +public class BundleLauncher { + + public static void main(String[] args){ + File file = new File("bundle.properties"); + Paths.get("").forEach(child -> { + Log.info("Directory: {0}", child); + }); + } + +} From 97b7eb0768571a16972a5f07b76d9b6713bd56bd Mon Sep 17 00:00:00 2001 From: Anuken Date: Sat, 7 Jul 2018 13:48:20 -0400 Subject: [PATCH 07/47] Bugfixes / Compile error fixes / Removed routers --- .../io/anuke/mindustry/content/Recipes.java | 6 +-- .../content/blocks/DistributionBlocks.java | 14 +++--- .../mindustry/entities/UnitInventory.java | 1 + .../mindustry/entities/units/BaseUnit.java | 4 ++ .../io/anuke/mindustry/world/BaseBlock.java | 5 ++- .../blocks/defense/turrets/LiquidTurret.java | 13 +++--- .../world/blocks/distribution/Conduit.java | 4 +- .../blocks/distribution/LiquidBridge.java | 4 +- .../distribution/LiquidExtendingBridge.java | 4 +- .../blocks/distribution/LiquidRouter.java | 4 +- .../world/blocks/distribution/MassDriver.java | 6 ++- .../world/blocks/distribution/Splitter.java | 43 +++++++------------ .../world/blocks/storage/CoreBlock.java | 8 ++-- .../world/blocks/storage/StorageBlock.java | 23 +++++----- .../world/blocks/units/ResupplyPoint.java | 10 ++--- .../world/blocks/units/UnitFactory.java | 15 ++++++- 16 files changed, 88 insertions(+), 76 deletions(-) diff --git a/core/src/io/anuke/mindustry/content/Recipes.java b/core/src/io/anuke/mindustry/content/Recipes.java index 2ff0bea050..43aa5e602b 100644 --- a/core/src/io/anuke/mindustry/content/Recipes.java +++ b/core/src/io/anuke/mindustry/content/Recipes.java @@ -44,11 +44,11 @@ public class Recipes implements ContentList{ //starter lead transporation new Recipe(distribution, DistributionBlocks.junction, new ItemStack(Items.lead, 2)); - new Recipe(distribution, DistributionBlocks.router, new ItemStack(Items.lead, 6)); + new Recipe(distribution, DistributionBlocks.splitter, new ItemStack(Items.lead, 6)); //advanced carbide transporation - new Recipe(distribution, DistributionBlocks.splitter, new ItemStack(Items.carbide, 2), new ItemStack(Items.tungsten, 2)); - new Recipe(distribution, DistributionBlocks.multiplexer, new ItemStack(Items.carbide, 8), new ItemStack(Items.tungsten, 8)); + //new Recipe(distribution, DistributionBlocks.splitter, new ItemStack(Items.carbide, 2), new ItemStack(Items.tungsten, 2)); + new Recipe(distribution, DistributionBlocks.distributor, new ItemStack(Items.carbide, 8), new ItemStack(Items.tungsten, 8)); new Recipe(distribution, DistributionBlocks.sorter, new ItemStack(Items.carbide, 4), new ItemStack(Items.tungsten, 4)); new Recipe(distribution, DistributionBlocks.overflowGate, new ItemStack(Items.carbide, 4), new ItemStack(Items.tungsten, 8)); new Recipe(distribution, DistributionBlocks.bridgeConveyor, new ItemStack(Items.carbide, 8), new ItemStack(Items.tungsten, 8)); diff --git a/core/src/io/anuke/mindustry/content/blocks/DistributionBlocks.java b/core/src/io/anuke/mindustry/content/blocks/DistributionBlocks.java index 0db8445d7c..2a7033b30d 100644 --- a/core/src/io/anuke/mindustry/content/blocks/DistributionBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/DistributionBlocks.java @@ -5,7 +5,7 @@ import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.blocks.distribution.*; public class DistributionBlocks extends BlockList implements ContentList{ - public static Block conveyor, titaniumconveyor, router, multiplexer, junction, + public static Block conveyor, titaniumconveyor, distributor, junction, bridgeConveyor, phaseConveyor, sorter, splitter, overflowGate, massDriver; @Override @@ -21,13 +21,6 @@ public class DistributionBlocks extends BlockList implements ContentList{ speed = 0.07f; }}; - router = new Router("router"); - - multiplexer = new Router("multiplexer") {{ - size = 2; - itemCapacity = 80; - }}; - junction = new Junction("junction") {{ speed = 26; capacity = 32; @@ -46,6 +39,11 @@ public class DistributionBlocks extends BlockList implements ContentList{ splitter = new Splitter("splitter"); + distributor = new Splitter("distributor") {{ + size = 2; + itemCapacity = 80; + }}; + overflowGate = new OverflowGate("overflow-gate"); massDriver = new MassDriver("mass-driver"){{ diff --git a/core/src/io/anuke/mindustry/entities/UnitInventory.java b/core/src/io/anuke/mindustry/entities/UnitInventory.java index f4739bf99b..b72543bf74 100644 --- a/core/src/io/anuke/mindustry/entities/UnitInventory.java +++ b/core/src/io/anuke/mindustry/entities/UnitInventory.java @@ -87,6 +87,7 @@ public class UnitInventory implements Saveable{ } public void addAmmo(AmmoType type){ + if(type == null) return; totalAmmo += type.quantityMultiplier; //find ammo entry by type diff --git a/core/src/io/anuke/mindustry/entities/units/BaseUnit.java b/core/src/io/anuke/mindustry/entities/units/BaseUnit.java index e78c3f07ea..85004700cb 100644 --- a/core/src/io/anuke/mindustry/entities/units/BaseUnit.java +++ b/core/src/io/anuke/mindustry/entities/units/BaseUnit.java @@ -71,6 +71,10 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{ return type; } + public Tile getSpawner(){ + return world.tile(spawner); + } + /**internal constructor used for deserialization, DO NOT USE*/ public BaseUnit(){} diff --git a/core/src/io/anuke/mindustry/world/BaseBlock.java b/core/src/io/anuke/mindustry/world/BaseBlock.java index 2913a18222..b0c91e6287 100644 --- a/core/src/io/anuke/mindustry/world/BaseBlock.java +++ b/core/src/io/anuke/mindustry/world/BaseBlock.java @@ -16,6 +16,8 @@ public abstract class BaseBlock { public boolean hasLiquids; public boolean hasPower; + public boolean singleLiquid = true; + public int itemCapacity; public float liquidCapacity = 10f; public float liquidFlowFactor = 4.9f; @@ -61,7 +63,8 @@ public abstract class BaseBlock { } public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){ - return tile.entity.liquids.get(liquid) + amount < liquidCapacity; + return tile.entity.liquids.get(liquid) + amount < liquidCapacity && + (!singleLiquid || (tile.entity.liquids.current() == liquid || tile.entity.liquids.get(tile.entity.liquids.current()) < 0.01f)); } public void handleLiquid(Tile tile, Tile source, Liquid liquid, float amount){ diff --git a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/LiquidTurret.java b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/LiquidTurret.java index 80f84af7ed..e4ac82acfb 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/LiquidTurret.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/LiquidTurret.java @@ -31,7 +31,7 @@ public abstract class LiquidTurret extends Turret { public void setBars() { super.setBars(); bars.remove(BarType.inventory); - bars.replace(new BlockBar(BarType.liquid, true, tile -> tile.entity.liquids.amount / liquidCapacity)); + bars.replace(new BlockBar(BarType.liquid, true, tile -> tile.entity.liquids.total() / liquidCapacity)); } @Override @@ -53,20 +53,20 @@ public abstract class LiquidTurret extends Turret { @Override public AmmoType useAmmo(Tile tile){ TurretEntity entity = tile.entity(); - AmmoType type = liquidAmmoMap.get(entity.liquids.liquid); - entity.liquids.amount -= type.quantityMultiplier; + AmmoType type = liquidAmmoMap.get(entity.liquids.current()); + entity.liquids.remove(type.liquid, type.quantityMultiplier); return type; } @Override public AmmoType peekAmmo(Tile tile){ - return liquidAmmoMap.get(tile.entity.liquids.liquid); + return liquidAmmoMap.get(tile.entity.liquids.current()); } @Override public boolean hasAmmo(Tile tile){ TurretEntity entity = tile.entity(); - return liquidAmmoMap.get(entity.liquids.liquid) != null && entity.liquids.amount >= liquidAmmoMap.get(entity.liquids.liquid).quantityMultiplier; + return liquidAmmoMap.get(entity.liquids.current()) != null && entity.liquids.total() >= liquidAmmoMap.get(entity.liquids.current()).quantityMultiplier; } @Override @@ -89,7 +89,8 @@ public abstract class LiquidTurret extends Turret { @Override public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){ - return super.acceptLiquid(tile, source, liquid, amount) && liquidAmmoMap.get(liquid) != null; + return super.acceptLiquid(tile, source, liquid, amount) && liquidAmmoMap.get(liquid) != null + && (tile.entity.liquids.current() == liquid || tile.entity.liquids.get(tile.entity.liquids.current()) < 0.01f); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java index f20d9d80af..f4beeb228a 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java @@ -27,6 +27,7 @@ public class Conduit extends LiquidBlock { super.load(); liquidRegion = Draw.region("conduit-liquid"); + } @Override @@ -67,8 +68,7 @@ public class Conduit extends LiquidBlock { @Override public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount) { tile.entity.wakeUp(); - return super.acceptLiquid(tile, source, liquid, amount) && (tile.entity.liquids.current() == liquid || tile.entity.liquids.get(tile.entity.liquids.current()) < 0.01f) && - ((2 + source.relativeTo(tile.x, tile.y)) % 4 != tile.getRotation()); + return super.acceptLiquid(tile, source, liquid, amount) && ((2 + source.relativeTo(tile.x, tile.y)) % 4 != tile.getRotation()); } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidBridge.java b/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidBridge.java index f014f8574a..738ece51d8 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidBridge.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidBridge.java @@ -24,7 +24,7 @@ public class LiquidBridge extends ItemBridge { Tile other = world.tile(entity.link); if(!linkValid(tile, other)){ - tryDumpLiquid(tile); + tryDumpLiquid(tile, entity.liquids.current()); }else{ float use = Math.min(powerCapacity, powerUse * Timers.delta()); @@ -37,7 +37,7 @@ public class LiquidBridge extends ItemBridge { if(entity.uptime >= 0.5f){ - if(tryMoveLiquid(tile, other, false) > 0.1f){ + if(tryMoveLiquid(tile, other, false, entity.liquids.current()) > 0.1f){ entity.cycleSpeed = Mathf.lerpDelta(entity.cycleSpeed, 4f, 0.05f); }else{ entity.cycleSpeed = Mathf.lerpDelta(entity.cycleSpeed, 1f, 0.01f); diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidExtendingBridge.java b/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidExtendingBridge.java index 7f3c908359..cf0ce206d1 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidExtendingBridge.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidExtendingBridge.java @@ -24,7 +24,7 @@ public class LiquidExtendingBridge extends ExtendingItemBridge { Tile other = world.tile(entity.link); if(!linkValid(tile, other)){ - tryDumpLiquid(tile); + tryDumpLiquid(tile, entity.liquids.current()); }else{ float use = Math.min(powerCapacity, powerUse * Timers.delta()); @@ -37,7 +37,7 @@ public class LiquidExtendingBridge extends ExtendingItemBridge { if(entity.uptime >= 0.5f){ - if(tryMoveLiquid(tile, other, false) > 0.1f){ + if(tryMoveLiquid(tile, other, false, entity.liquids.current()) > 0.1f){ entity.cycleSpeed = Mathf.lerpDelta(entity.cycleSpeed, 4f, 0.05f); }else{ entity.cycleSpeed = Mathf.lerpDelta(entity.cycleSpeed, 1f, 0.01f); diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidRouter.java b/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidRouter.java index e2c42784d7..8083564e7c 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidRouter.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidRouter.java @@ -12,8 +12,8 @@ public class LiquidRouter extends LiquidBlock{ @Override public void update(Tile tile){ - if(tile.entity.liquids.amount > 0){ - tryDumpLiquid(tile); + if(tile.entity.liquids.total() > 0.01f){ + tryDumpLiquid(tile, tile.entity.liquids.current()); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/MassDriver.java b/core/src/io/anuke/mindustry/world/blocks/distribution/MassDriver.java index b5492d0e5a..c7703e8f69 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/MassDriver.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/MassDriver.java @@ -189,7 +189,9 @@ public class MassDriver extends Block { DriverBulletData data = Pooling.obtain(DriverBulletData.class); data.from = entity; data.to = other; - System.arraycopy(entity.items.items, 0, data.items, 0, data.items.length); + for (int i = 0; i < Item.all().size; i++) { + data.items[i] = entity.items.get(Item.getByID(i)); + } entity.items.clear(); float angle = tile.angleTo(target); @@ -226,7 +228,7 @@ public class MassDriver extends Block { //add all the items possible for(int i = 0; i < data.items.length; i ++){ int maxAdd = Math.min(data.items[i], itemCapacity - totalItems); - items.items[i] += maxAdd; + items.add(Item.getByID(i), maxAdd); data.items[i] -= maxAdd; totalItems += maxAdd; diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/Splitter.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Splitter.java index 54b4a2e3b5..86301fb49c 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Splitter.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Splitter.java @@ -1,10 +1,13 @@ package io.anuke.mindustry.world.blocks.distribution; +import com.badlogic.gdx.math.GridPoint2; import io.anuke.mindustry.type.Item; import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.meta.BlockGroup; +import io.anuke.mindustry.world.Edges; import io.anuke.mindustry.world.Tile; -import io.anuke.ucore.util.Mathf; +import io.anuke.mindustry.world.meta.BlockGroup; + +import static io.anuke.mindustry.Vars.world; public class Splitter extends Block{ @@ -31,33 +34,17 @@ public class Splitter extends Block{ } Tile getTileTarget(Item item, Tile dest, Tile source, boolean flip){ - int dir = source.relativeTo(dest.x, dest.y); - if(dir == -1) return null; - Tile to; - - Tile a = dest.getNearby(Mathf.mod(dir - 1, 4)); - Tile b = dest.getNearby(Mathf.mod(dir + 1, 4)); - boolean ac = !(a.block().instantTransfer && source.block().instantTransfer) && - a.block().acceptItem(item, a, dest); - boolean bc = !(b.block().instantTransfer && source.block().instantTransfer) && - b.block().acceptItem(item, b, dest); - - if(ac && !bc){ - to = a; - }else if(bc && !ac){ - to = b; - }else{ - if(dest.getDump() == 0){ - to = a; - if(flip) - dest.setDump((byte)1); - }else{ - to = b; - if(flip) - dest.setDump((byte)0); + GridPoint2[] points = Edges.getEdges(size); + int counter = source.getDump(); + for (int i = 0; i < points.length; i++) { + GridPoint2 point = points[(i + counter++) % points.length]; + source.setDump((byte)(counter % points.length)); + Tile tile = world.tile(dest.x + point.x, dest.y + point.y); + if(tile != source && !(tile.block().instantTransfer && source.block().instantTransfer) && + tile.block().acceptItem(item, tile, dest)){ + return tile; } } - - return to; + return null; } } diff --git a/core/src/io/anuke/mindustry/world/blocks/storage/CoreBlock.java b/core/src/io/anuke/mindustry/world/blocks/storage/CoreBlock.java index 6a64afcb31..de992e1dcc 100644 --- a/core/src/io/anuke/mindustry/world/blocks/storage/CoreBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/storage/CoreBlock.java @@ -141,7 +141,7 @@ public class CoreBlock extends StorageBlock { @Override public boolean acceptItem(Item item, Tile tile, Tile source) { - return tile.entity.items.items[item.id] < itemCapacity && item.type == ItemType.material; + return tile.entity.items.get(item) < itemCapacity && item.type == ItemType.material; } @Override @@ -220,10 +220,10 @@ public class CoreBlock extends StorageBlock { Units.getNearby(tile.getTeam(), rect, unit -> { if(unit.isDead() || unit.distanceTo(tile.drawx(), tile.drawy()) > supplyRadius || unit.getGroup() == null) return; - for(int i = 0; i < tile.entity.items.items.length; i ++){ + for(int i = 0; i < Item.all().size; i ++){ Item item = Item.getByID(i); - if(tile.entity.items.items[i] > 0 && unit.acceptsAmmo(item)){ - tile.entity.items.items[i] --; + if(tile.entity.items.get(item) > 0 && unit.acceptsAmmo(item)){ + tile.entity.items.remove(item, 1); unit.addAmmo(item); CallEntity.transferAmmo(item, tile.drawx(), tile.drawy(), unit); return; diff --git a/core/src/io/anuke/mindustry/world/blocks/storage/StorageBlock.java b/core/src/io/anuke/mindustry/world/blocks/storage/StorageBlock.java index f65cf710c6..31d24e1190 100644 --- a/core/src/io/anuke/mindustry/world/blocks/storage/StorageBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/storage/StorageBlock.java @@ -16,24 +16,27 @@ public abstract class StorageBlock extends Block { * Returns null if no items are there.*/ public Item removeItem(Tile tile, Item item){ TileEntity entity = tile.entity; - for(int i = 0; i < entity.items.items.length; i ++){ - if(entity.items.items[i] > 0 && (item == null || i == item.id)){ - entity.items.items[i] --; - return Item.getByID(i); + + if(item == null){ + return entity.items.take(); + }else{ + if(entity.items.has(item)){ + entity.items.remove(item, 1); + return item; } + + return null; } - return null; } /**Returns whether this storage block has the specified item. * If the item is null, it should return whether it has ANY items.*/ public boolean hasItem(Tile tile, Item item){ TileEntity entity = tile.entity; - for(int i = 0; i < entity.items.items.length; i ++){ - if(entity.items.items[i] > 0 && (item == null || i == item.id)){ - return true; - } + if(item == null){ + return entity.items.total() > 0; + }else{ + return entity.items.has(item); } - return false; } } diff --git a/core/src/io/anuke/mindustry/world/blocks/units/ResupplyPoint.java b/core/src/io/anuke/mindustry/world/blocks/units/ResupplyPoint.java index af0078b72c..7f5053a199 100644 --- a/core/src/io/anuke/mindustry/world/blocks/units/ResupplyPoint.java +++ b/core/src/io/anuke/mindustry/world/blocks/units/ResupplyPoint.java @@ -85,10 +85,10 @@ public class ResupplyPoint extends Block{ }else if(entity.target != null && entity.strength > 0.5f){ if(entity.timer.get(timerSupply, supplyInterval)) { - for (int i = 0; i < tile.entity.items.items.length; i++) { + for (int i = 0; i < Item.all().size; i++) { Item item = Item.getByID(i); - if (tile.entity.items.items[i] > 0 && entity.target.acceptsAmmo(item)) { - tile.entity.items.items[i]--; + if (tile.entity.items.has(item) && entity.target.acceptsAmmo(item)) { + tile.entity.items.remove(item, 1); entity.target.addAmmo(item); break; } @@ -130,9 +130,9 @@ public class ResupplyPoint extends Block{ if(unit == null || unit.inventory.totalAmmo() >= unit.inventory.ammoCapacity() || unit.isDead()) return false; - for(int i = 0; i < entity.items.items.length; i ++) { + for (int i = 0; i < Item.all().size; i++) { Item item = Item.getByID(i); - if (entity.items.items[i] > 0 && unit.acceptsAmmo(item)) { + if (entity.items.has(item) && unit.acceptsAmmo(item)) { return true; } } diff --git a/core/src/io/anuke/mindustry/world/blocks/units/UnitFactory.java b/core/src/io/anuke/mindustry/world/blocks/units/UnitFactory.java index c7777c4775..3015c88970 100644 --- a/core/src/io/anuke/mindustry/world/blocks/units/UnitFactory.java +++ b/core/src/io/anuke/mindustry/world/blocks/units/UnitFactory.java @@ -19,11 +19,11 @@ import io.anuke.mindustry.type.ItemStack; import io.anuke.mindustry.world.BarType; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.modules.InventoryModule; import io.anuke.mindustry.world.meta.BlockBar; import io.anuke.mindustry.world.meta.BlockStat; import io.anuke.mindustry.world.meta.StatUnit; import io.anuke.mindustry.world.meta.values.ItemListValue; +import io.anuke.mindustry.world.modules.InventoryModule; import io.anuke.ucore.core.Effects; import io.anuke.ucore.core.Graphics; import io.anuke.ucore.core.Timers; @@ -136,6 +136,17 @@ public class UnitFactory extends Block { } } + /* + if(!entity.hasSpawned){ + for(BaseUnit unit : unitGroups[tile.getTeamID()].all()){ + if(unit.getType() == type && unit.getSpawner() == null){ + entity.hasSpawned = true; + unit.setSpawner(tile); + break; + } + } + }*/ + if(!entity.hasSpawned && hasRequirements(entity.items, entity.buildTime/produceTime) && entity.power.amount >= used && !entity.open){ @@ -214,11 +225,13 @@ public class UnitFactory extends Block { @Override public void write(DataOutputStream stream) throws IOException { stream.writeFloat(buildTime); + stream.writeBoolean(hasSpawned); } @Override public void read(DataInputStream stream) throws IOException { buildTime = stream.readFloat(); + hasSpawned = stream.readBoolean(); } } } From c06152de0738cedfd1e60590f0e3c9ef9544c856 Mon Sep 17 00:00:00 2001 From: Anuken Date: Sun, 8 Jul 2018 00:04:41 -0400 Subject: [PATCH 08/47] Consumption system more or less complete --- .../io/anuke/mindustry/content/Liquids.java | 2 +- core/src/io/anuke/mindustry/core/UI.java | 2 +- .../anuke/mindustry/entities/TileEntity.java | 16 ++--- .../anuke/mindustry/io/versions/Save16.java | 2 + .../src/io/anuke/mindustry/net/NetworkIO.java | 2 + .../io/anuke/mindustry/world/BaseBlock.java | 13 +++- core/src/io/anuke/mindustry/world/Block.java | 7 +- core/src/io/anuke/mindustry/world/Tile.java | 14 +--- .../world/blocks/production/Drill.java | 36 ++-------- .../world/blocks/production/LiquidMixer.java | 58 +++++---------- .../world/blocks/production/PowerCrafter.java | 2 +- .../world/blocks/production/Pump.java | 34 ++++----- .../world/blocks/production/Separator.java | 45 +++--------- .../world/blocks/production/SolidPump.java | 29 ++++---- .../world/blocks/units/MechFactory.java | 1 + .../mindustry/world/consumers/Consume.java | 27 ++++++- .../world/consumers/ConsumeItem.java | 34 +++++++++ .../world/consumers/ConsumeLiquid.java | 46 ++++++++++++ .../world/consumers/ConsumePower.java | 35 +++++++++ .../mindustry/world/consumers/Consumers.java | 71 +++++++++++++++++++ .../anuke/mindustry/world/consumers/Uses.java | 7 ++ .../{blocks => modules}/BlockModule.java | 2 +- .../world/modules/ConsumeModule.java | 51 +++++++++++++ .../world/modules/InventoryModule.java | 1 - .../mindustry/world/modules/LiquidModule.java | 1 - .../mindustry/world/modules/PowerModule.java | 2 - 26 files changed, 359 insertions(+), 181 deletions(-) create mode 100644 core/src/io/anuke/mindustry/world/consumers/ConsumeItem.java create mode 100644 core/src/io/anuke/mindustry/world/consumers/ConsumeLiquid.java create mode 100644 core/src/io/anuke/mindustry/world/consumers/ConsumePower.java create mode 100644 core/src/io/anuke/mindustry/world/consumers/Consumers.java create mode 100644 core/src/io/anuke/mindustry/world/consumers/Uses.java rename core/src/io/anuke/mindustry/world/{blocks => modules}/BlockModule.java (86%) create mode 100644 core/src/io/anuke/mindustry/world/modules/ConsumeModule.java diff --git a/core/src/io/anuke/mindustry/content/Liquids.java b/core/src/io/anuke/mindustry/content/Liquids.java index 097d708a30..86adf3cf85 100644 --- a/core/src/io/anuke/mindustry/content/Liquids.java +++ b/core/src/io/anuke/mindustry/content/Liquids.java @@ -7,7 +7,7 @@ import io.anuke.mindustry.type.ContentList; import io.anuke.mindustry.type.Liquid; public class Liquids implements ContentList { - public static Liquid none, water, lava, oil, cryofluid; + public static Liquid water, lava, oil, cryofluid; @Override public void load() { diff --git a/core/src/io/anuke/mindustry/core/UI.java b/core/src/io/anuke/mindustry/core/UI.java index be54f94247..53e5497a1b 100644 --- a/core/src/io/anuke/mindustry/core/UI.java +++ b/core/src/io/anuke/mindustry/core/UI.java @@ -147,7 +147,7 @@ public class UI extends SceneModule{ } Graphics.end(); - + Draw.color(); } @Override diff --git a/core/src/io/anuke/mindustry/entities/TileEntity.java b/core/src/io/anuke/mindustry/entities/TileEntity.java index 3347ed4ccf..46b66c389e 100644 --- a/core/src/io/anuke/mindustry/entities/TileEntity.java +++ b/core/src/io/anuke/mindustry/entities/TileEntity.java @@ -1,7 +1,6 @@ package io.anuke.mindustry.entities; import com.badlogic.gdx.math.Vector2; -import com.badlogic.gdx.utils.Array; import io.anuke.annotations.Annotations.Loc; import io.anuke.annotations.Annotations.Remote; import io.anuke.mindustry.content.fx.Fx; @@ -11,9 +10,10 @@ import io.anuke.mindustry.game.Team; import io.anuke.mindustry.gen.CallBlocks; import io.anuke.mindustry.net.In; import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.consumers.Consume; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.Wall; +import io.anuke.mindustry.world.consumers.Uses; +import io.anuke.mindustry.world.modules.ConsumeModule; import io.anuke.mindustry.world.modules.InventoryModule; import io.anuke.mindustry.world.modules.LiquidModule; import io.anuke.mindustry.world.modules.PowerModule; @@ -43,8 +43,7 @@ public class TileEntity extends BaseEntity implements TargetTrait { public PowerModule power; public InventoryModule items; public LiquidModule liquids; - - public Array consumers = new Array<>(); + public ConsumeModule cons; private boolean dead = false; private boolean sleeping; @@ -59,7 +58,6 @@ public class TileEntity extends BaseEntity implements TargetTrait { health = tile.block().health; timer = new Timer(tile.block().timers); - tile.block().setConsumers(consumers); if(added){ add(); @@ -135,6 +133,10 @@ public class TileEntity extends BaseEntity implements TargetTrait { return tile; } + public boolean consumed(Uses uses){ + return tile.block().consumes.get(uses).valid(tile.block(), this); + } + @Override public Team getTeam() { return tile.getTeam(); @@ -160,9 +162,7 @@ public class TileEntity extends BaseEntity implements TargetTrait { } tile.block().update(tile); - for(Consume cons : consumers){ - cons.update(this); - } + cons.update(this); } } diff --git a/core/src/io/anuke/mindustry/io/versions/Save16.java b/core/src/io/anuke/mindustry/io/versions/Save16.java index 263ff28e06..b116b627cc 100644 --- a/core/src/io/anuke/mindustry/io/versions/Save16.java +++ b/core/src/io/anuke/mindustry/io/versions/Save16.java @@ -121,6 +121,7 @@ public class Save16 extends SaveFileVersion { if (tile.entity.items != null) tile.entity.items.read(stream); if (tile.entity.power != null) tile.entity.power.read(stream); if (tile.entity.liquids != null) tile.entity.liquids.read(stream); + if (tile.entity.cons != null) tile.entity.cons.read(stream); tile.entity.read(stream); @@ -220,6 +221,7 @@ public class Save16 extends SaveFileVersion { if(tile.entity.items != null) tile.entity.items.write(stream); if(tile.entity.power != null) tile.entity.power.write(stream); if(tile.entity.liquids != null) tile.entity.liquids.write(stream); + if(tile.entity.cons != null) tile.entity.cons.write(stream); tile.entity.write(stream); }else if(tile.getWallID() == 0){ diff --git a/core/src/io/anuke/mindustry/net/NetworkIO.java b/core/src/io/anuke/mindustry/net/NetworkIO.java index 035decf9e3..1625079e2e 100644 --- a/core/src/io/anuke/mindustry/net/NetworkIO.java +++ b/core/src/io/anuke/mindustry/net/NetworkIO.java @@ -75,6 +75,7 @@ public class NetworkIO { if(tile.entity.items != null) tile.entity.items.write(stream); if(tile.entity.power != null) tile.entity.power.write(stream); if(tile.entity.liquids != null) tile.entity.liquids.write(stream); + if(tile.entity.cons != null) tile.entity.cons.write(stream); tile.entity.write(stream); }else if(tile.getWallID() == 0){ @@ -193,6 +194,7 @@ public class NetworkIO { if (tile.entity.items != null) tile.entity.items.read(stream); if (tile.entity.power != null) tile.entity.power.read(stream); if (tile.entity.liquids != null) tile.entity.liquids.read(stream); + if (tile.entity.cons != null) tile.entity.cons.read(stream); tile.entity.read(stream); }else if(wallid == 0){ diff --git a/core/src/io/anuke/mindustry/world/BaseBlock.java b/core/src/io/anuke/mindustry/world/BaseBlock.java index b0c91e6287..fec0728ac0 100644 --- a/core/src/io/anuke/mindustry/world/BaseBlock.java +++ b/core/src/io/anuke/mindustry/world/BaseBlock.java @@ -6,6 +6,8 @@ import io.anuke.mindustry.entities.Unit; import io.anuke.mindustry.entities.effect.Puddle; import io.anuke.mindustry.type.Item; import io.anuke.mindustry.type.Liquid; +import io.anuke.mindustry.world.consumers.Consumers; +import io.anuke.mindustry.world.consumers.Uses; import io.anuke.ucore.core.Effects; import io.anuke.ucore.core.Timers; import io.anuke.ucore.util.Mathf; @@ -23,6 +25,12 @@ public abstract class BaseBlock { public float liquidFlowFactor = 4.9f; public float powerCapacity = 10f; + public Consumers consumes = new Consumers(); + + public boolean shouldConsume(Tile tile){ + return true; + } + /**Returns the amount of items this block can accept.*/ public int acceptStack(Item item, int amount, Tile tile, Unit source){ if(acceptItem(item, tile, tile) && hasItems && source.getTeam() == tile.getTeam()){ @@ -59,12 +67,13 @@ public abstract class BaseBlock { } public boolean acceptItem(Item item, Tile tile, Tile source){ - return false; + return tile.entity != null && consumes.item() == item && tile.entity.items.total() < itemCapacity; } public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){ return tile.entity.liquids.get(liquid) + amount < liquidCapacity && - (!singleLiquid || (tile.entity.liquids.current() == liquid || tile.entity.liquids.get(tile.entity.liquids.current()) < 0.01f)); + (!singleLiquid || (tile.entity.liquids.current() == liquid || tile.entity.liquids.get(tile.entity.liquids.current()) < 0.01f)) + && (!consumes.has(Uses.liquid) || consumes.liquid() == liquid); } public void handleLiquid(Tile tile, Tile source, Liquid liquid, float amount){ diff --git a/core/src/io/anuke/mindustry/world/Block.java b/core/src/io/anuke/mindustry/world/Block.java index 51f5bce094..93ecd50975 100644 --- a/core/src/io/anuke/mindustry/world/Block.java +++ b/core/src/io/anuke/mindustry/world/Block.java @@ -19,7 +19,6 @@ import io.anuke.mindustry.graphics.Palette; import io.anuke.mindustry.input.CursorType; import io.anuke.mindustry.type.Item; import io.anuke.mindustry.type.ItemStack; -import io.anuke.mindustry.world.consumers.Consume; import io.anuke.mindustry.world.meta.*; import io.anuke.ucore.core.Timers; import io.anuke.ucore.graphics.Draw; @@ -133,10 +132,6 @@ public class Block extends BaseBlock implements Content{ blocks.add(this); } - public void setConsumers(Array consumers){ - - } - public boolean isLayer(Tile tile){return true;} public boolean isLayer2(Tile tile){return true;} public void drawLayer(Tile tile){} @@ -221,6 +216,8 @@ public class Block extends BaseBlock implements Content{ stats.add(BlockStat.size, "{0}x{0}", size); stats.add(BlockStat.health, health, StatUnit.none); + consumes.forEach(cons -> cons.display(stats)); + if(hasPower) stats.add(BlockStat.powerCapacity, powerCapacity, StatUnit.powerUnits); if(hasLiquids) stats.add(BlockStat.liquidCapacity, liquidCapacity, StatUnit.liquidUnits); if(hasItems) stats.add(BlockStat.itemCapacity, itemCapacity, StatUnit.items); diff --git a/core/src/io/anuke/mindustry/world/Tile.java b/core/src/io/anuke/mindustry/world/Tile.java index 3393a2de39..0f0c27a8d9 100644 --- a/core/src/io/anuke/mindustry/world/Tile.java +++ b/core/src/io/anuke/mindustry/world/Tile.java @@ -352,28 +352,15 @@ public class Tile implements PosTrait, TargetTrait { GridPoint2 pc = Geometry.d4[i]; GridPoint2 pcprev = Geometry.d4[Mathf.mod(i - 1, 4)]; GridPoint2 pcnext = Geometry.d4[(i + 1) % 4]; - GridPoint2 pe = Geometry.d8edge[i]; Tile tc = world.tile(x + pc.x, y + pc.y); Tile tprev = world.tile(x + pcprev.x, y + pcprev.y); Tile tnext = world.tile(x + pcnext.x, y + pcnext.y); - Tile te = world.tile(x + pe.x, y + pe.y); - Tile tex = world.tile(x, y + pe.y); - Tile tey = world.tile(x + pe.x, y); //check for cardinal direction elevation changes and bitmask that if(tc != null && tprev != null && tnext != null && ((tc.elevation < elevation && tc.elevation != -1))){ cliffs |= (1 << (i*2)); } - - //00S - //0X0 - //010 - - //check for corner bitmasking: doesn't even get checked so it doesn't matter - /*if(te != null && tex != null && tey != null && te.elevation == -1 && elevation > 0){ - cliffs |= (1 << (((i+1)%4)*2)); - }*/ } if(occluded){ cost += 1; @@ -394,6 +381,7 @@ public class Tile implements PosTrait, TargetTrait { if (block.hasEntity()) { entity = block.getEntity().init(this, block.update); + block.consumes.addAll(entity.cons.all()); if(block.hasItems) entity.items = new InventoryModule(); if(block.hasLiquids) entity.liquids = new LiquidModule(); if(block.hasPower) entity.power = new PowerModule(); diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Drill.java b/core/src/io/anuke/mindustry/world/blocks/production/Drill.java index 0de56aea3b..0c315c51bd 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Drill.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Drill.java @@ -8,9 +8,9 @@ import io.anuke.mindustry.content.fx.BlockFx; import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.graphics.Layer; import io.anuke.mindustry.type.Item; -import io.anuke.mindustry.type.Liquid; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.consumers.Uses; import io.anuke.mindustry.world.meta.BlockGroup; import io.anuke.mindustry.world.meta.BlockStat; import io.anuke.mindustry.world.meta.StatUnit; @@ -35,12 +35,6 @@ public class Drill extends Block{ protected int tier; /**Base time to drill one ore, in frames.*/ protected float drillTime = 300; - /**power use per frame.*/ - public float powerUse = 0.08f; - /**liquid use per frame.*/ - protected float liquidUse = 0.05f; - /**Input liquid. Set hasLiquids to true so this is used.*/ - protected Liquid inputLiquid = Liquids.water; /**Whether the liquid is required to drill. If false, then it will be used as a speed booster.*/ protected boolean liquidRequired = false; /**How many times faster the drill will progress when boosted by liquid.*/ @@ -74,6 +68,8 @@ public class Drill extends Block{ hasLiquids = true; liquidCapacity = 5f; hasItems = true; + + consumes.liquid(Liquids.water, 0.01f).optional(true); } @Override @@ -141,14 +137,6 @@ public class Drill extends Block{ }); stats.add(BlockStat.drillSpeed, 60f/drillTime, StatUnit.itemsSecond); - - if(inputLiquid != null){ - stats.add(BlockStat.inputLiquid, inputLiquid); - } - - if(hasPower){ - stats.add(BlockStat.powerUse, powerUse*60f, StatUnit.powerSecond); - } } @Override @@ -175,20 +163,11 @@ public class Drill extends Block{ entity.drillTime += entity.warmup * Timers.delta(); - float powerUsed = Math.min(powerCapacity, powerUse * Timers.delta()); - float liquidUsed = Math.min(liquidCapacity, liquidUse * Timers.delta()); - - if(entity.items.total() < itemCapacity && toAdd.size > 0 && - (!hasPower || entity.power.amount >= powerUsed) && - (!liquidRequired || entity.liquids.amount >= liquidUsed)){ - - if(hasPower) entity.power.amount -= powerUsed; - if(liquidRequired) entity.liquids.amount -= liquidUsed; + if(entity.items.total() < itemCapacity && toAdd.size > 0 && entity.cons.valid()){ float speed = 1f; - if(entity.liquids.amount >= liquidUsed && !liquidRequired){ - entity.liquids.amount -= liquidUsed; + if(entity.consumed(Uses.liquid) && !liquidRequired){ speed = liquidBoostIntensity; } @@ -235,11 +214,6 @@ public class Drill extends Block{ } } - @Override - public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount) { - return super.acceptLiquid(tile, source, liquid, amount) && liquid == inputLiquid; - } - @Override public TileEntity getEntity() { return new DrillEntity(); diff --git a/core/src/io/anuke/mindustry/world/blocks/production/LiquidMixer.java b/core/src/io/anuke/mindustry/world/blocks/production/LiquidMixer.java index 02b889fc34..0611b7c727 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/LiquidMixer.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/LiquidMixer.java @@ -1,28 +1,22 @@ package io.anuke.mindustry.world.blocks.production; -import io.anuke.mindustry.content.Liquids; import io.anuke.mindustry.entities.TileEntity; -import io.anuke.mindustry.type.Item; import io.anuke.mindustry.type.Liquid; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.LiquidBlock; +import io.anuke.mindustry.world.consumers.ConsumeLiquid; +import io.anuke.mindustry.world.consumers.Uses; import io.anuke.mindustry.world.meta.BlockStat; -import io.anuke.mindustry.world.meta.StatUnit; import io.anuke.ucore.core.Timers; public class LiquidMixer extends LiquidBlock{ - protected Liquid inputLiquid = Liquids.none; - protected Liquid outputLiquid = Liquids.none; - protected Item inputItem = null; + protected Liquid outputLiquid; protected float liquidPerItem = 50f; - protected float powerUse = 0f; public LiquidMixer(String name) { super(name); hasItems = true; - hasPower = true; rotate = false; - liquidRegion = name() + "-liquid"; solid = true; } @@ -31,43 +25,29 @@ public class LiquidMixer extends LiquidBlock{ super.setStats(); stats.add(BlockStat.liquidOutput, outputLiquid); - stats.add(BlockStat.powerUse, powerUse * 60f, StatUnit.powerSecond); - stats.add(BlockStat.inputItem, inputItem); + } + + @Override + public boolean shouldConsume(Tile tile){ + return tile.entity.liquids.get(outputLiquid) < liquidCapacity; } @Override public void update(Tile tile){ - float used = Math.min(Timers.delta() * powerUse, tile.entity.power.amount); - - tryDumpLiquid(tile); - - if(tile.entity.power.amount > used) tile.entity.power.amount -= used; - } - - @Override - public boolean acceptItem(Item item, Tile tile, Tile source) { - return item == inputItem && tile.entity.items.get(item) < itemCapacity; - } - - @Override - public float handleAuxLiquid(Tile tile, Tile source, Liquid liquid, float amount) { LiquidMixerEntity entity = tile.entity(); - if(liquid == inputLiquid && tile.entity.items.has(inputItem, (int)((entity.accumulator + amount)/amount)) && - tile.entity.power.amount >= powerUse){ - - amount = Math.min(liquidCapacity - tile.entity.liquids.amount, amount); - - entity.accumulator += amount; - int items = (int)(entity.accumulator / liquidPerItem); - entity.items.remove(inputItem, items); - entity.accumulator %= liquidPerItem; - entity.liquids.liquid = outputLiquid; - entity.liquids.amount += amount; - return amount; - }else{ - return 0; + if(tile.entity.cons.valid()){ + float use = Math.min(consumes.get(Uses.liquid).used() * Timers.delta(), liquidCapacity - entity.liquids.get(outputLiquid)); + entity.accumulator += use; + entity.liquids.add(outputLiquid, use); + for (int i = 0; i < (int)(entity.accumulator / liquidPerItem); i++) { + if(!entity.items.has(consumes.item())) break; + entity.items.remove(consumes.item(), 1); + entity.accumulator --; + } } + + tryDumpLiquid(tile, outputLiquid); } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/production/PowerCrafter.java b/core/src/io/anuke/mindustry/world/blocks/production/PowerCrafter.java index efe340ce3a..f2cbe29263 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/PowerCrafter.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/PowerCrafter.java @@ -76,7 +76,7 @@ public class PowerCrafter extends Block{ } if(outputLiquid != null){ - tryDumpLiquid(tile); + tryDumpLiquid(tile, entity.liquids.current()); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Pump.java b/core/src/io/anuke/mindustry/world/blocks/production/Pump.java index 7e98c47e3d..cbfff62e71 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Pump.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Pump.java @@ -11,7 +11,6 @@ import io.anuke.mindustry.world.meta.BlockStat; import io.anuke.mindustry.world.meta.StatUnit; import io.anuke.ucore.core.Timers; import io.anuke.ucore.graphics.Draw; -import io.anuke.ucore.util.Mathf; public class Pump extends LiquidBlock{ protected final Array drawTiles = new Array<>(); @@ -19,8 +18,6 @@ public class Pump extends LiquidBlock{ /**Pump amount per tile this block is on.*/ protected float pumpAmount = 1f; - /**Power used per frame per tile this block is on.*/ - protected float powerUse = 0f; /**Maximum liquid tier this pump can use.*/ protected int tier = 0; @@ -29,10 +26,16 @@ public class Pump extends LiquidBlock{ layer = Layer.overlay; liquidFlowFactor = 3f; group = BlockGroup.liquids; - liquidRegion = "pump-liquid"; floating = true; } + @Override + public void load() { + super.load(); + + liquidRegion = Draw.region("pump-liquid"); + } + @Override public void setStats(){ super.setStats(); @@ -48,8 +51,8 @@ public class Pump extends LiquidBlock{ public void draw(Tile tile){ Draw.rect(name(), tile.drawx(), tile.drawy()); - Draw.color(tile.entity.liquids.liquid.color); - Draw.alpha(tile.entity.liquids.amount / liquidCapacity); + Draw.color(tile.entity.liquids.current().color); + Draw.alpha(tile.entity.liquids.total() / liquidCapacity); Draw.rect(liquidRegion, tile.drawx(), tile.drawy()); Draw.color(); } @@ -96,23 +99,12 @@ public class Pump extends LiquidBlock{ liquidDrop = tile.floor().liquidDrop; } - if(hasPower){ - float used = Math.min(powerCapacity, tiles * powerUse * Timers.delta()); - - //multiply liquid obtained by the fraction of power this pump has to pump it - //e.g. only has 50% power required = only pumps 50% of liquid that it can - tiles *= Mathf.clamp(tile.entity.power.amount / used); - - tile.entity.power.amount -= Math.min(tile.entity.power.amount, used); + if(tile.entity.cons.valid() && liquidDrop != null){ + float maxPump = Math.min(liquidCapacity - tile.entity.liquids.total(), tiles * pumpAmount * Timers.delta()); + tile.entity.liquids.add(liquidDrop, maxPump); } - if(liquidDrop != null){ - float maxPump = Math.min(liquidCapacity - tile.entity.liquids.amount, tiles * pumpAmount * Timers.delta()); - tile.entity.liquids.liquid = liquidDrop; - tile.entity.liquids.amount += maxPump; - } - - tryDumpLiquid(tile); + tryDumpLiquid(tile, tile.entity.liquids.current()); } protected boolean isValid(Tile tile){ diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Separator.java b/core/src/io/anuke/mindustry/world/blocks/production/Separator.java index 2ee107fb5f..3fbaab0ef2 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Separator.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Separator.java @@ -2,14 +2,14 @@ package io.anuke.mindustry.world.blocks.production; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.TextureRegion; +import io.anuke.mindustry.content.Items; +import io.anuke.mindustry.content.Liquids; import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.type.Item; -import io.anuke.mindustry.type.Liquid; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.production.GenericCrafter.GenericCrafterEntity; import io.anuke.mindustry.world.meta.BlockStat; -import io.anuke.mindustry.world.meta.StatUnit; import io.anuke.mindustry.world.meta.values.ItemFilterValue; import io.anuke.ucore.core.Timers; import io.anuke.ucore.graphics.Draw; @@ -20,11 +20,7 @@ import io.anuke.ucore.util.Mathf; public class Separator extends Block { protected final int timerDump = timers ++; - protected Liquid liquid; - protected Item item; protected Item[] results; - protected float liquidUse; - protected float powerUse; protected float filterTime; protected float spinnerRadius = 2.5f; protected float spinnerLength = 1f; @@ -42,6 +38,9 @@ public class Separator extends Block { solid = true; hasItems = true; hasLiquids = true; + + consumes.item(Items.stone); + consumes.liquid(Liquids.water, 0.1f); } @Override @@ -55,13 +54,6 @@ public class Separator extends Block { public void setStats() { super.setStats(); - if(hasPower){ - stats.add(BlockStat.powerUse, powerUse * 60f, StatUnit.powerSecond); - } - - stats.add(BlockStat.liquidUse, liquidUse * 60f, StatUnit.liquidSecond); - stats.add(BlockStat.inputLiquid, liquid); - stats.add(BlockStat.inputItem, item); stats.add(BlockStat.outputItem, new ItemFilterValue(item -> { for(Item i : results){ if(item == i) return true; @@ -76,8 +68,8 @@ public class Separator extends Block { GenericCrafterEntity entity = tile.entity(); - Draw.color(tile.entity.liquids.liquid.color); - Draw.alpha(tile.entity.liquids.amount / liquidCapacity); + Draw.color(tile.entity.liquids.current().color); + Draw.alpha(tile.entity.liquids.total() / liquidCapacity); Draw.rect(liquidRegion, tile.drawx(), tile.drawy()); Draw.color(color); @@ -90,17 +82,10 @@ public class Separator extends Block { public void update(Tile tile) { GenericCrafterEntity entity = tile.entity(); - float liquidUsed = Math.min(liquidCapacity, liquidUse * Timers.delta()); - float powerUsed = Math.min(powerCapacity, powerUse * Timers.delta()); - entity.totalProgress += entity.warmup*Timers.delta(); - if(entity.liquids.amount >= liquidUsed && entity.items.has(item) && - (!hasPower || entity.power.amount >= powerUsed)){ + if(entity.cons.valid()){ entity.progress += 1f/filterTime; - entity.liquids.amount -= liquidUsed; - if(hasPower) entity.power.amount -= powerUsed; - entity.warmup = Mathf.lerpDelta(entity.warmup, 1f, 0.02f); }else{ entity.warmup = Mathf.lerpDelta(entity.warmup, 0f, 0.02f); @@ -109,7 +94,7 @@ public class Separator extends Block { if(entity.progress >= 1f){ entity.progress = 0f; Item item = Mathf.select(results); - entity.items.remove(this.item, 1); + entity.items.remove(consumes.item(), 1); if(item != null){ offloading = true; offloadNear(tile, item); @@ -124,17 +109,7 @@ public class Separator extends Block { @Override public boolean canDump(Tile tile, Tile to, Item item) { - return offloading || item != this.item; - } - - @Override - public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount) { - return super.acceptLiquid(tile, source, liquid, amount) && this.liquid == liquid; - } - - @Override - public boolean acceptItem(Item item, Tile tile, Tile source) { - return this.item == item && tile.entity.items.get(item) < itemCapacity; + return offloading || item != consumes.item(); } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/production/SolidPump.java b/core/src/io/anuke/mindustry/world/blocks/production/SolidPump.java index 6a4cfd6ccc..ac852972e0 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/SolidPump.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/SolidPump.java @@ -7,7 +7,6 @@ import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.type.Liquid; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.meta.BlockStat; -import io.anuke.mindustry.world.meta.StatUnit; import io.anuke.ucore.core.Effects; import io.anuke.ucore.core.Effects.Effect; import io.anuke.ucore.core.Timers; @@ -17,8 +16,6 @@ import io.anuke.ucore.util.Mathf; /**Pump that makes liquid from solids and takes in power. Only works on solid floor blocks.*/ public class SolidPump extends Pump { protected Liquid result = Liquids.water; - /**Power use per liquid unit.*/ - protected float powerUse = 0.1f; protected Effect updateEffect = Fx.none; protected float updateEffectChance = 0.02f; protected float rotateSpeed = 1f; @@ -26,7 +23,13 @@ public class SolidPump extends Pump { public SolidPump(String name){ super(name); hasPower = true; - liquidRegion = name + "-liquid"; + } + + @Override + public void load() { + super.load(); + + liquidRegion = Draw.region(name + "-liquid"); } @Override @@ -34,8 +37,6 @@ public class SolidPump extends Pump { super.setStats(); stats.remove(BlockStat.liquidOutput); - - stats.add(BlockStat.powerUse, powerUse * 60f, StatUnit.powerSecond); stats.add(BlockStat.liquidOutput, result); } @@ -44,8 +45,8 @@ public class SolidPump extends Pump { SolidPumpEntity entity = tile.entity(); Draw.rect(region, tile.drawx(), tile.drawy()); - Draw.color(tile.entity.liquids.liquid.color); - Draw.alpha(tile.entity.liquids.amount / liquidCapacity); + Draw.color(tile.entity.liquids.current().color); + Draw.alpha(tile.entity.liquids.total() / liquidCapacity); Draw.rect(liquidRegion, tile.drawx(), tile.drawy()); Draw.color(); Draw.rect(name + "-rotator", tile.drawx(), tile.drawy(), entity.pumpTime * rotateSpeed); @@ -61,8 +62,6 @@ public class SolidPump extends Pump { public void update(Tile tile){ SolidPumpEntity entity = tile.entity(); - float used = Math.min(powerUse * Timers.delta(), powerCapacity); - float fraction = 0f; if(isMultiblock()){ @@ -75,11 +74,9 @@ public class SolidPump extends Pump { if(isValid(tile)) fraction = 1f; } - if(tile.entity.power.amount >= used && tile.entity.liquids.amount < liquidCapacity - 0.001f){ - float maxPump = Math.min(liquidCapacity - tile.entity.liquids.amount, pumpAmount * Timers.delta() * fraction); - tile.entity.liquids.liquid = result; - tile.entity.liquids.amount += maxPump; - tile.entity.power.amount -= used; + if(tile.entity.cons.valid() && tile.entity.liquids.total() < liquidCapacity - 0.001f){ + float maxPump = Math.min(liquidCapacity - tile.entity.liquids.total(), pumpAmount * Timers.delta() * fraction); + tile.entity.liquids.add(result, maxPump); entity.warmup = Mathf.lerpDelta(entity.warmup, 1f, 0.02f); if(Mathf.chance(Timers.delta() * updateEffectChance)) Effects.effect(updateEffect, entity.x + Mathf.range(size*2f), entity.y + Mathf.range(size*2f)); @@ -89,7 +86,7 @@ public class SolidPump extends Pump { entity.pumpTime += entity.warmup * Timers.delta(); - tryDumpLiquid(tile); + tryDumpLiquid(tile, entity.liquids.current()); } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/units/MechFactory.java b/core/src/io/anuke/mindustry/world/blocks/units/MechFactory.java index 99c54704cf..deff90de86 100644 --- a/core/src/io/anuke/mindustry/world/blocks/units/MechFactory.java +++ b/core/src/io/anuke/mindustry/world/blocks/units/MechFactory.java @@ -52,6 +52,7 @@ public class MechFactory extends Block{ @Override public void tapped(Tile tile, Player player) { + if(mobile && !mech.flying) return; if(checkValidTap(tile, player)){ CallBlocks.onMechFactoryTap(player, tile); diff --git a/core/src/io/anuke/mindustry/world/consumers/Consume.java b/core/src/io/anuke/mindustry/world/consumers/Consume.java index 977435abae..f920ebf888 100644 --- a/core/src/io/anuke/mindustry/world/consumers/Consume.java +++ b/core/src/io/anuke/mindustry/world/consumers/Consume.java @@ -1,8 +1,29 @@ package io.anuke.mindustry.world.consumers; import io.anuke.mindustry.entities.TileEntity; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.meta.BlockStats; -public interface Consume { - void update(TileEntity entity); - boolean valid(); +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +public abstract class Consume { + private boolean optional; + + public void optional(boolean optional) { + this.optional = optional; + } + + public boolean isOptional() { + return optional; + } + + public abstract void update(Block block, TileEntity entity); + public abstract boolean valid(Block block, TileEntity entity); + public abstract void display(BlockStats stats); + + public Consume copy(){ return this; } + public void write(DataOutput stream) throws IOException{} + public void read(DataInput stream) throws IOException{} } diff --git a/core/src/io/anuke/mindustry/world/consumers/ConsumeItem.java b/core/src/io/anuke/mindustry/world/consumers/ConsumeItem.java new file mode 100644 index 0000000000..e1d3db4e64 --- /dev/null +++ b/core/src/io/anuke/mindustry/world/consumers/ConsumeItem.java @@ -0,0 +1,34 @@ +package io.anuke.mindustry.world.consumers; + +import io.anuke.mindustry.entities.TileEntity; +import io.anuke.mindustry.type.Item; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.meta.BlockStat; +import io.anuke.mindustry.world.meta.BlockStats; + +public class ConsumeItem extends Consume { + private final Item item; + + public ConsumeItem(Item item) { + this.item = item; + } + + public Item get() { + return item; + } + + @Override + public void update(Block block, TileEntity entity) { + //doesn't update because consuming items is very specific + } + + @Override + public boolean valid(Block block, TileEntity entity) { + return entity.items.has(item); + } + + @Override + public void display(BlockStats stats) { + stats.add(BlockStat.inputItem, item); + } +} diff --git a/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquid.java b/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquid.java new file mode 100644 index 0000000000..2fa7e36404 --- /dev/null +++ b/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquid.java @@ -0,0 +1,46 @@ +package io.anuke.mindustry.world.consumers; + +import io.anuke.mindustry.entities.TileEntity; +import io.anuke.mindustry.type.Liquid; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.meta.BlockStat; +import io.anuke.mindustry.world.meta.BlockStats; +import io.anuke.mindustry.world.meta.StatUnit; + +public class ConsumeLiquid extends Consume { + private final float use; + private final Liquid liquid; + + public ConsumeLiquid(Liquid liquid, float use) { + this.liquid = liquid; + this.use = use; + } + + public float used() { + return use; + } + + public Liquid get() { + return liquid; + } + + @Override + public void update(Block block, TileEntity entity) { + entity.liquids.remove(liquid, Math.min(use(block), entity.liquids.get(liquid))); + } + + @Override + public boolean valid(Block block, TileEntity entity) { + return entity.liquids.get(liquid) >= use(block); + } + + @Override + public void display(BlockStats stats) { + stats.add(BlockStat.liquidUse, use * 60f, StatUnit.liquidSecond); + stats.add(BlockStat.inputLiquid, liquid); + } + + float use(Block block) { + return Math.min(use, block.liquidCapacity); + } +} diff --git a/core/src/io/anuke/mindustry/world/consumers/ConsumePower.java b/core/src/io/anuke/mindustry/world/consumers/ConsumePower.java new file mode 100644 index 0000000000..84f8636b90 --- /dev/null +++ b/core/src/io/anuke/mindustry/world/consumers/ConsumePower.java @@ -0,0 +1,35 @@ +package io.anuke.mindustry.world.consumers; + +import io.anuke.mindustry.entities.TileEntity; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.meta.BlockStat; +import io.anuke.mindustry.world.meta.BlockStats; +import io.anuke.mindustry.world.meta.StatUnit; +import io.anuke.ucore.core.Timers; + +public class ConsumePower extends Consume { + private final float use; + + public ConsumePower(float use) { + this.use = use; + } + + @Override + public void update(Block block, TileEntity entity) { + entity.power.amount -= Math.min(use(block), entity.power.amount); + } + + @Override + public boolean valid(Block block, TileEntity entity) { + return entity.power.amount >= use(block); + } + + @Override + public void display(BlockStats stats) { + stats.add(BlockStat.powerUse, use * 60f, StatUnit.powerSecond); + } + + float use(Block block){ + return Math.min(use * Timers.delta(), block.powerCapacity); + } +} diff --git a/core/src/io/anuke/mindustry/world/consumers/Consumers.java b/core/src/io/anuke/mindustry/world/consumers/Consumers.java new file mode 100644 index 0000000000..02590d85c4 --- /dev/null +++ b/core/src/io/anuke/mindustry/world/consumers/Consumers.java @@ -0,0 +1,71 @@ +package io.anuke.mindustry.world.consumers; + +import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.type.Item; +import io.anuke.mindustry.type.Liquid; +import io.anuke.ucore.function.Consumer; + +public class Consumers { + private Consume[] consumeMap = new Consume[Uses.values().length]; + + public ConsumePower power(float amount){ + ConsumePower p = new ConsumePower(amount); + add(Uses.power, p); + return p; + } + + public ConsumeLiquid liquid(Liquid liquid, float amount){ + ConsumeLiquid c = new ConsumeLiquid(liquid, amount); + add(Uses.liquid, c); + return c; + } + + public ConsumeItem item(Item item){ + ConsumeItem i = new ConsumeItem(item); + add(Uses.items, i); + return i; + } + + public Item item(){ + return this.get(Uses.items).get(); + } + + public Liquid liquid(){ + return this.get(Uses.liquid).get(); + } + + public void add(Uses type, Consume consume){ + consumeMap[type.ordinal()] = consume; + } + + public void remove(Uses type){ + consumeMap[type.ordinal()] = null; + } + + public boolean has(Uses type){ + return consumeMap[type.ordinal()] != null; + } + + public T get(Uses type){ + if(consumeMap[type.ordinal()] == null){ + throw new IllegalArgumentException("Block does not contain consumer of type '" + type + "'!"); + } + return (T)consumeMap[type.ordinal()]; + } + + public void forEach(Consumer cons){ + for (Consume c : consumeMap) { + if (c != null) { + cons.accept(c); + } + } + } + + public void addAll(Array result){ + for (Consume c : consumeMap) { + if (c != null) { + result.add(c.copy()); + } + } + } +} diff --git a/core/src/io/anuke/mindustry/world/consumers/Uses.java b/core/src/io/anuke/mindustry/world/consumers/Uses.java new file mode 100644 index 0000000000..c1bb49632a --- /dev/null +++ b/core/src/io/anuke/mindustry/world/consumers/Uses.java @@ -0,0 +1,7 @@ +package io.anuke.mindustry.world.consumers; + +public enum Uses { + power, + liquid, + items, +} diff --git a/core/src/io/anuke/mindustry/world/blocks/BlockModule.java b/core/src/io/anuke/mindustry/world/modules/BlockModule.java similarity index 86% rename from core/src/io/anuke/mindustry/world/blocks/BlockModule.java rename to core/src/io/anuke/mindustry/world/modules/BlockModule.java index c30e571dd8..0b74bfb5e5 100644 --- a/core/src/io/anuke/mindustry/world/blocks/BlockModule.java +++ b/core/src/io/anuke/mindustry/world/modules/BlockModule.java @@ -1,4 +1,4 @@ -package io.anuke.mindustry.world.blocks; +package io.anuke.mindustry.world.modules; import java.io.DataInput; import java.io.DataOutput; diff --git a/core/src/io/anuke/mindustry/world/modules/ConsumeModule.java b/core/src/io/anuke/mindustry/world/modules/ConsumeModule.java new file mode 100644 index 0000000000..39bf00dc5c --- /dev/null +++ b/core/src/io/anuke/mindustry/world/modules/ConsumeModule.java @@ -0,0 +1,51 @@ +package io.anuke.mindustry.world.modules; + +import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.entities.TileEntity; +import io.anuke.mindustry.world.consumers.Consume; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +public class ConsumeModule extends BlockModule{ + private Array consumers = new Array<>(); + private boolean valid; + + public void update(TileEntity entity){ + boolean prevValid = valid; + valid = true; + + for(Consume cons : consumers){ + if(prevValid && entity.tile.block().shouldConsume(entity.tile)){ + cons.update(entity.getTile().block(), entity); + } + + if(!cons.isOptional()){ + valid &= cons.valid(entity.getTile().block(), entity); + } + } + } + + public boolean valid(){ + return valid; + } + + public Array all() { + return consumers; + } + + @Override + public void write(DataOutput stream) throws IOException { + for(Consume cons : consumers){ + cons.write(stream); + } + } + + @Override + public void read(DataInput stream) throws IOException { + for(Consume cons : consumers){ + cons.read(stream); + } + } +} diff --git a/core/src/io/anuke/mindustry/world/modules/InventoryModule.java b/core/src/io/anuke/mindustry/world/modules/InventoryModule.java index cc937769cd..9fb13a9a3c 100644 --- a/core/src/io/anuke/mindustry/world/modules/InventoryModule.java +++ b/core/src/io/anuke/mindustry/world/modules/InventoryModule.java @@ -2,7 +2,6 @@ package io.anuke.mindustry.world.modules; import io.anuke.mindustry.type.Item; import io.anuke.mindustry.type.ItemStack; -import io.anuke.mindustry.world.blocks.BlockModule; import java.io.DataInput; import java.io.DataOutput; diff --git a/core/src/io/anuke/mindustry/world/modules/LiquidModule.java b/core/src/io/anuke/mindustry/world/modules/LiquidModule.java index 57808eee6d..564bed0512 100644 --- a/core/src/io/anuke/mindustry/world/modules/LiquidModule.java +++ b/core/src/io/anuke/mindustry/world/modules/LiquidModule.java @@ -1,7 +1,6 @@ package io.anuke.mindustry.world.modules; import io.anuke.mindustry.type.Liquid; -import io.anuke.mindustry.world.blocks.BlockModule; import java.io.DataInput; import java.io.DataOutput; diff --git a/core/src/io/anuke/mindustry/world/modules/PowerModule.java b/core/src/io/anuke/mindustry/world/modules/PowerModule.java index f46f9ca817..fe35aa0d90 100644 --- a/core/src/io/anuke/mindustry/world/modules/PowerModule.java +++ b/core/src/io/anuke/mindustry/world/modules/PowerModule.java @@ -1,7 +1,5 @@ package io.anuke.mindustry.world.modules; -import io.anuke.mindustry.world.blocks.BlockModule; - import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; From 63a0fde1211c27e31b925d8af220d19d5b11474c Mon Sep 17 00:00:00 2001 From: Anuken Date: Sun, 8 Jul 2018 16:38:29 -0400 Subject: [PATCH 09/47] Offload optimizations / Compile errors fixed / Massive refactor --- .../content/blocks/CraftingBlocks.java | 103 +++++++++--------- .../content/blocks/LiquidBlocks.java | 4 +- .../mindustry/content/blocks/PowerBlocks.java | 3 +- .../content/blocks/ProductionBlocks.java | 24 ++-- .../mindustry/content/blocks/UnitBlocks.java | 11 +- .../anuke/mindustry/entities/TileEntity.java | 53 ++++++++- .../io/anuke/mindustry/world/BaseBlock.java | 97 +++++++++-------- core/src/io/anuke/mindustry/world/Block.java | 4 +- core/src/io/anuke/mindustry/world/Edges.java | 8 ++ core/src/io/anuke/mindustry/world/Tile.java | 14 ++- .../blocks/defense/turrets/CooledTurret.java | 10 +- .../world/blocks/distribution/ItemBridge.java | 5 +- .../blocks/distribution/LiquidBridge.java | 6 +- .../distribution/LiquidExtendingBridge.java | 5 +- .../blocks/distribution/LiquidJunction.java | 3 +- .../world/blocks/distribution/WarpGate.java | 26 ++--- .../world/blocks/power/FusionReactor.java | 28 ++--- .../world/blocks/power/ItemGenerator.java | 22 ++-- .../blocks/power/ItemLiquidGenerator.java | 33 ++---- .../world/blocks/power/LiquidGenerator.java | 24 ++-- .../world/blocks/power/NuclearReactor.java | 43 ++++---- .../world/blocks/power/TurbineGenerator.java | 51 +-------- .../world/blocks/production/Compressor.java | 43 +++----- .../world/blocks/production/Cultivator.java | 16 ++- .../world/blocks/production/Drill.java | 4 +- .../world/blocks/production/Fracker.java | 48 ++------ .../blocks/production/GenericCrafter.java | 47 ++------ .../world/blocks/production/Incinerator.java | 17 +-- .../world/blocks/production/LiquidMixer.java | 3 +- .../world/blocks/production/PowerCrafter.java | 24 +--- .../world/blocks/production/PowerSmelter.java | 27 +---- .../world/blocks/production/Smelter.java | 34 +++--- .../world/blocks/units/Projector.java | 6 +- .../world/blocks/units/UnitFactory.java | 19 ++-- .../mindustry/world/consumers/Consume.java | 21 ++-- .../world/consumers/ConsumeItem.java | 13 ++- .../world/consumers/ConsumeItemFilter.java | 38 +++++++ .../world/consumers/ConsumeItems.java | 37 +++++++ .../world/consumers/ConsumeLiquid.java | 3 +- .../world/consumers/ConsumeLiquidFilter.java | 41 +++++++ .../mindustry/world/consumers/Consumers.java | 88 ++++++++++----- .../anuke/mindustry/world/consumers/Uses.java | 7 -- .../anuke/mindustry/world/meta/BlockStat.java | 1 - .../mindustry/world/meta/BlockStats.java | 12 +- .../world/modules/ConsumeModule.java | 18 +-- .../mindustry/world/modules/LiquidModule.java | 4 + 46 files changed, 583 insertions(+), 565 deletions(-) create mode 100644 core/src/io/anuke/mindustry/world/consumers/ConsumeItemFilter.java create mode 100644 core/src/io/anuke/mindustry/world/consumers/ConsumeItems.java create mode 100644 core/src/io/anuke/mindustry/world/consumers/ConsumeLiquidFilter.java delete mode 100644 core/src/io/anuke/mindustry/world/consumers/Uses.java diff --git a/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java b/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java index e0f8e9e4bd..2c76f2638b 100644 --- a/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java @@ -13,52 +13,51 @@ import io.anuke.mindustry.world.blocks.production.*; public class CraftingBlocks extends BlockList implements ContentList { public static Block smelter, arcsmelter, siliconsmelter, plastaniumCompressor, phaseWeaver, alloysmelter, alloyfuser, pyratiteMixer, blastMixer, - cryofluidmixer, melter, separator, centrifuge, biomatterCompressor, pulverizer, oilRefinery, solidifier, incinerator; + cryofluidmixer, melter, separator, centrifuge, biomatterCompressor, pulverizer, solidifier, incinerator; @Override public void load() { smelter = new Smelter("smelter") {{ health = 70; - inputs = new ItemStack[]{new ItemStack(Items.tungsten, 3)}; - fuel = Items.coal; result = Items.carbide; craftTime = 45f; burnDuration = 35f; useFlux = true; + + consumes.items(new ItemStack[]{new ItemStack(Items.tungsten, 3)}); + consumes.item(Items.coal); }}; arcsmelter = new PowerSmelter("arc-smelter") {{ health = 90; craftEffect = BlockFx.smeltsmoke; - inputs = new ItemStack[]{new ItemStack(Items.coal, 1), new ItemStack(Items.tungsten, 2)}; result = Items.carbide; - powerUse = 0.1f; craftTime = 30f; size = 2; useFlux = true; fluxNeeded = 2; + + consumes.items(new ItemStack[]{new ItemStack(Items.coal, 1), new ItemStack(Items.tungsten, 2)}); + consumes.power(0.1f); }}; siliconsmelter = new PowerSmelter("silicon-smelter") {{ health = 90; craftEffect = BlockFx.smeltsmoke; - inputs = new ItemStack[]{new ItemStack(Items.coal, 1), new ItemStack(Items.sand, 2)}; result = Items.silicon; - powerUse = 0.05f; craftTime = 40f; size = 2; hasLiquids = false; flameColor = Color.valueOf("ffef99"); + + consumes.items(new ItemStack[]{new ItemStack(Items.coal, 1), new ItemStack(Items.sand, 2)}); + consumes.power(0.05f); }}; plastaniumCompressor = new PlastaniumCompressor("plastanium-compressor") {{ - inputLiquid = Liquids.oil; - inputItem = new ItemStack(Items.titanium, 2); hasItems = true; - liquidUse = 0.3f; liquidCapacity = 60f; - powerUse = 0.5f; craftTime = 80f; output = Items.plastanium; itemCapacity = 30; @@ -67,66 +66,73 @@ public class CraftingBlocks extends BlockList implements ContentList { hasPower = hasLiquids = true; craftEffect = BlockFx.formsmoke; updateEffect = BlockFx.plasticburn; + + consumes.liquid(Liquids.oil, 0.3f); + consumes.power(0.4f); + consumes.item(Items.titanium, 2); }}; phaseWeaver = new PhaseWeaver("phase-weaver") {{ health = 90; craftEffect = BlockFx.smeltsmoke; - inputs = new ItemStack[]{new ItemStack(Items.thorium, 4), new ItemStack(Items.sand, 10)}; result = Items.phasematter; - powerUse = 0.4f; craftTime = 120f; size = 2; + + consumes.items(new ItemStack[]{new ItemStack(Items.thorium, 4), new ItemStack(Items.sand, 10)}); + consumes.power(0.5f); }}; alloysmelter = new PowerSmelter("alloy-smelter") {{ health = 90; craftEffect = BlockFx.smeltsmoke; - inputs = new ItemStack[]{new ItemStack(Items.titanium, 2), new ItemStack(Items.lead, 4), new ItemStack(Items.silicon, 3), new ItemStack(Items.plastanium, 2)}; result = Items.surgealloy; - powerUse = 0.3f; craftTime = 50f; size = 2; useFlux = true; fluxNeeded = 4; + + consumes.power(0.3f); + consumes.items(new ItemStack[]{new ItemStack(Items.titanium, 2), new ItemStack(Items.lead, 4), new ItemStack(Items.silicon, 3), new ItemStack(Items.plastanium, 2)}); }}; alloyfuser = new PowerSmelter("alloy-fuser") {{ health = 90; craftEffect = BlockFx.smeltsmoke; - inputs = new ItemStack[]{new ItemStack(Items.titanium, 3), new ItemStack(Items.lead, 4), new ItemStack(Items.silicon, 3), new ItemStack(Items.plastanium, 2)}; result = Items.surgealloy; - powerUse = 0.4f; craftTime = 30f; size = 3; useFlux = true; fluxNeeded = 4; + + consumes.items(new ItemStack[]{new ItemStack(Items.titanium, 3), new ItemStack(Items.lead, 4), new ItemStack(Items.silicon, 3), new ItemStack(Items.plastanium, 2)}); + consumes.power(0.4f); }}; cryofluidmixer = new LiquidMixer("cryofluidmixer") {{ health = 200; - inputLiquid = Liquids.water; outputLiquid = Liquids.cryofluid; - inputItem = Items.titanium; liquidPerItem = 50f; itemCapacity = 50; - powerUse = 0.1f; size = 2; + + consumes.power(0.1f); + consumes.item(Items.titanium); + consumes.liquid(Liquids.water, 0.2f); }}; blastMixer = new GenericCrafter("blast-mixer") {{ itemCapacity = 20; hasItems = true; hasPower = true; - inputLiquid = Liquids.oil; - liquidUse = 0.05f; - inputItem = new ItemStack(Items.pyratite, 1); output = Items.blastCompound; - powerUse = 0.04f; - size = 2; + + consumes.liquid(Liquids.oil, 0.05f); + consumes.item(Items.pyratite, 1); + consumes.power(0.04f); }}; pyratiteMixer = new PowerSmelter("pyratite-mixer") {{ @@ -134,27 +140,27 @@ public class CraftingBlocks extends BlockList implements ContentList { itemCapacity = 20; hasItems = true; hasPower = true; - inputs = new ItemStack[]{new ItemStack(Items.coal, 1), new ItemStack(Items.lead, 2), new ItemStack(Items.sand, 2)}; result = Items.pyratite; - powerUse = 0.02f; size = 2; + + consumes.power(0.02f); + consumes.items(new ItemStack[]{new ItemStack(Items.coal, 1), new ItemStack(Items.lead, 2), new ItemStack(Items.sand, 2)}); }}; melter = new PowerCrafter("melter") {{ health = 200; outputLiquid = Liquids.lava; outputLiquidAmount = 0.05f; - input = new ItemStack(Items.stone, 1); itemCapacity = 50; craftTime = 10f; - powerUse = 0.1f; hasLiquids = hasPower = true; + + consumes.power(0.1f); + consumes.item(Items.stone, 2); }}; separator = new Separator("separator") {{ - liquid = Liquids.water; - item = Items.stone; results = new Item[]{ null, null, null, null, null, null, null, null, null, null, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, @@ -164,15 +170,15 @@ public class CraftingBlocks extends BlockList implements ContentList { Items.coal, Items.coal, Items.titanium }; - liquidUse = 0.2f; filterTime = 40f; itemCapacity = 40; health = 50; + + consumes.item(Items.stone); + consumes.liquid(Liquids.water, 0.2f); }}; centrifuge = new Separator("centrifuge") {{ - liquid = Liquids.water; - item = Items.stone; results = new Item[]{ null, null, null, null, null, null, null, null, null, null, null, null, null, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, @@ -184,9 +190,7 @@ public class CraftingBlocks extends BlockList implements ContentList { Items.thorium, }; - liquidUse = 0.3f; hasPower = true; - powerUse = 0.2f; filterTime = 15f; itemCapacity = 60; health = 50 * 4; @@ -195,53 +199,48 @@ public class CraftingBlocks extends BlockList implements ContentList { spinnerThickness = 1.5f; spinnerSpeed = 3f; size = 2; + + consumes.item(Items.stone); + consumes.power(0.2f); + consumes.liquid(Liquids.water, 0.35f); }}; biomatterCompressor = new Compressor("biomattercompressor") {{ - input = new ItemStack(Items.biomatter, 1); liquidCapacity = 60f; itemCapacity = 50; - powerUse = 0.06f; craftTime = 25f; outputLiquid = Liquids.oil; outputLiquidAmount = 0.1f; size = 2; health = 320; hasLiquids = true; + + consumes.item(Items.biomatter, 1); + consumes.power(0.06f); }}; pulverizer = new Pulverizer("pulverizer") {{ - inputItem = new ItemStack(Items.stone, 2); itemCapacity = 40; - powerUse = 0.2f; output = Items.sand; health = 80; craftEffect = BlockFx.pulverize; craftTime = 60f; updateEffect = BlockFx.pulverizeSmall; hasItems = hasPower = true; - }}; - oilRefinery = new GenericCrafter("oilrefinery") {{ - inputLiquid = Liquids.oil; - powerUse = 0.05f; - liquidUse = 0.1f; - liquidCapacity = 56f; - output = Items.coal; - health = 80; - craftEffect = BlockFx.purifyoil; - hasItems = hasLiquids = hasPower = true; + consumes.item(Items.stone, 2); + consumes.power(0.2f); }}; solidifier = new GenericCrafter("solidifer") {{ - inputLiquid = Liquids.lava; - liquidUse = 1f; liquidCapacity = 21f; craftTime = 14; output = Items.stone; health = 80; craftEffect = BlockFx.purifystone; hasLiquids = hasItems = true; + + consumes.liquid(Liquids.lava, 1f); }}; incinerator = new Incinerator("incinerator") {{ diff --git a/core/src/io/anuke/mindustry/content/blocks/LiquidBlocks.java b/core/src/io/anuke/mindustry/content/blocks/LiquidBlocks.java index 6b47cb80d4..c6330d2e09 100644 --- a/core/src/io/anuke/mindustry/content/blocks/LiquidBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/LiquidBlocks.java @@ -20,7 +20,7 @@ public class LiquidBlocks extends BlockList implements ContentList{ rotaryPump = new Pump("rotary-pump") {{ shadow = "shadow-rounded-2"; pumpAmount = 0.25f; - powerUse = 0.015f; + consumes.power(0.015f); liquidCapacity = 30f; size = 2; tier = 1; @@ -28,7 +28,7 @@ public class LiquidBlocks extends BlockList implements ContentList{ thermalPump = new Pump("thermal-pump") {{ pumpAmount = 0.3f; - powerUse = 0.02f; + consumes.power(0.05f); liquidCapacity = 40f; size = 2; tier = 2; diff --git a/core/src/io/anuke/mindustry/content/blocks/PowerBlocks.java b/core/src/io/anuke/mindustry/content/blocks/PowerBlocks.java index e8533325f7..223a88efdb 100644 --- a/core/src/io/anuke/mindustry/content/blocks/PowerBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/PowerBlocks.java @@ -1,5 +1,6 @@ package io.anuke.mindustry.content.blocks; +import io.anuke.mindustry.content.Liquids; import io.anuke.mindustry.content.fx.BlockFx; import io.anuke.mindustry.type.ContentList; import io.anuke.mindustry.world.Block; @@ -32,7 +33,7 @@ public class PowerBlocks extends BlockList implements ContentList { powerCapacity = 40f; itemDuration = 30f; powerPerLiquid = 0.7f; - auxLiquidUse = 0.05f; + consumes.liquid(Liquids.water, 0.05f); size = 2; }}; diff --git a/core/src/io/anuke/mindustry/content/blocks/ProductionBlocks.java b/core/src/io/anuke/mindustry/content/blocks/ProductionBlocks.java index 6a7c9f8a18..8bbdba1f1e 100644 --- a/core/src/io/anuke/mindustry/content/blocks/ProductionBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/ProductionBlocks.java @@ -29,17 +29,17 @@ public class ProductionBlocks extends BlockList implements ContentList { laserdrill = new Drill("laser-drill") {{ drillTime = 180; size = 2; - powerUse = 0.2f; hasPower = true; tier = 4; updateEffect = BlockFx.pulverizeMedium; drillEffect = BlockFx.mineBig; + + consumes.power(0.2f); }}; blastdrill = new Drill("blast-drill") {{ drillTime = 120; size = 3; - powerUse = 0.5f; drawRim = true; hasPower = true; tier = 5; @@ -48,13 +48,14 @@ public class ProductionBlocks extends BlockList implements ContentList { drillEffect = BlockFx.mineHuge; rotateSpeed = 6f; warmupSpeed = 0.01f; + + consumes.power(0.5f); }}; plasmadrill = new Drill("plasma-drill") {{ heatColor = Color.valueOf("ff461b"); drillTime = 90; size = 4; - powerUse = 0.7f; hasLiquids = true; hasPower = true; tier = 5; @@ -64,38 +65,43 @@ public class ProductionBlocks extends BlockList implements ContentList { updateEffectChance = 0.04f; drillEffect = BlockFx.mineHuge; warmupSpeed = 0.005f; + + consumes.power(0.7f); }}; waterextractor = new SolidPump("water-extractor") {{ result = Liquids.water; - powerUse = 0.2f; pumpAmount = 0.1f; size = 2; liquidCapacity = 30f; rotateSpeed = 1.4f; + + consumes.power(0.2f); }}; oilextractor = new Fracker("oil-extractor") {{ result = Liquids.oil; - inputLiquid = Liquids.water; updateEffect = BlockFx.pulverize; liquidCapacity = 50f; updateEffectChance = 0.05f; - inputLiquidUse = 0.3f; - powerUse = 0.6f; pumpAmount = 0.06f; size = 3; liquidCapacity = 30f; + + consumes.item(Items.sand); + consumes.power(0.6f); + consumes.liquid(Liquids.water, 0.3f); }}; cultivator = new Cultivator("cultivator") {{ result = Items.biomatter; - inputLiquid = Liquids.water; - liquidUse = 0.2f; drillTime = 260; size = 2; hasLiquids = true; hasPower = true; + + consumes.power(0.08f); + consumes.liquid(Liquids.water, 0.2f); }}; } diff --git a/core/src/io/anuke/mindustry/content/blocks/UnitBlocks.java b/core/src/io/anuke/mindustry/content/blocks/UnitBlocks.java index 397e70cba3..5abf5ceb51 100644 --- a/core/src/io/anuke/mindustry/content/blocks/UnitBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/UnitBlocks.java @@ -16,19 +16,16 @@ public class UnitBlocks extends BlockList implements ContentList { type = UnitTypes.drone; produceTime = 800; size = 2; - requirements = new ItemStack[]{ - new ItemStack(Items.silicon, 30), new ItemStack(Items.lead, 30) - }; + consumes.power(0.08f); + consumes.items(new ItemStack[]{new ItemStack(Items.silicon, 30), new ItemStack(Items.lead, 30)}); }}; fabricatorFactory = new UnitFactory("fabricator-factory") {{ type = UnitTypes.fabricator; produceTime = 1600; size = 2; - powerUse = 0.2f; - requirements = new ItemStack[]{ - new ItemStack(Items.silicon, 70), new ItemStack(Items.lead, 80), new ItemStack(Items.titanium, 80) - }; + consumes.power(0.2f); + consumes.items(new ItemStack[]{new ItemStack(Items.silicon, 70), new ItemStack(Items.lead, 80), new ItemStack(Items.titanium, 80)}); }}; resupplyPoint = new ResupplyPoint("resupply-point") {{ diff --git a/core/src/io/anuke/mindustry/entities/TileEntity.java b/core/src/io/anuke/mindustry/entities/TileEntity.java index 46b66c389e..f7df09b001 100644 --- a/core/src/io/anuke/mindustry/entities/TileEntity.java +++ b/core/src/io/anuke/mindustry/entities/TileEntity.java @@ -1,6 +1,9 @@ package io.anuke.mindustry.entities; +import com.badlogic.gdx.math.GridPoint2; import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.ObjectSet; import io.anuke.annotations.Annotations.Loc; import io.anuke.annotations.Annotations.Remote; import io.anuke.mindustry.content.fx.Fx; @@ -10,9 +13,10 @@ import io.anuke.mindustry.game.Team; import io.anuke.mindustry.gen.CallBlocks; import io.anuke.mindustry.net.In; import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.Edges; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.Wall; -import io.anuke.mindustry.world.consumers.Uses; +import io.anuke.mindustry.world.consumers.Consume; import io.anuke.mindustry.world.modules.ConsumeModule; import io.anuke.mindustry.world.modules.InventoryModule; import io.anuke.mindustry.world.modules.LiquidModule; @@ -36,6 +40,8 @@ public class TileEntity extends BaseEntity implements TargetTrait { /**This value is only used for debugging.*/ public static int sleepingEntities = 0; + private static final ObjectSet tmpTiles = new ObjectSet<>(); + public Tile tile; public Timer timer; public float health; @@ -45,6 +51,9 @@ public class TileEntity extends BaseEntity implements TargetTrait { public LiquidModule liquids; public ConsumeModule cons; + //list of (cached) tiles with entities in proximity, used for outputting to + //TODO implement + private Array proximity = new Array<>(8); private boolean dead = false; private boolean sleeping; private float sleepTime; @@ -133,8 +142,46 @@ public class TileEntity extends BaseEntity implements TargetTrait { return tile; } - public boolean consumed(Uses uses){ - return tile.block().consumes.get(uses).valid(tile.block(), this); + public boolean consumed(Class type){ + return tile.block().consumes.get(type).valid(tile.block(), this); + } + + public void removeFromProximity(){ + GridPoint2[] nearby = Edges.getEdges(tile.block().size); + for (GridPoint2 point : nearby) { + Tile other = world.tile(tile.x + point.x, tile.y + point.y); + //remove this tile from all nearby tile's proximities + if(other != null && other.entity != null){ + other.entity.proximity.removeValue(tile, true); + } + } + } + + public void updateProximity(){ + tmpTiles.clear(); + proximity.clear(); + + GridPoint2[] nearby = Edges.getEdges(tile.block().size); + for (GridPoint2 point : nearby) { + Tile other = world.tile(tile.x + point.x, tile.y + point.y); + if(other != null && other.entity != null){ + tmpTiles.add(other); + + //add this tile to proximity of nearby tiles + if(!other.entity.proximity.contains(tile, true)){ + other.entity.proximity.add(tile); + } + } + } + + //using a set to prevent duplicates + for(Tile tile : tmpTiles){ + proximity.add(tile); + } + } + + public Array proximity(){ + return proximity; } @Override diff --git a/core/src/io/anuke/mindustry/world/BaseBlock.java b/core/src/io/anuke/mindustry/world/BaseBlock.java index fec0728ac0..70634e45c6 100644 --- a/core/src/io/anuke/mindustry/world/BaseBlock.java +++ b/core/src/io/anuke/mindustry/world/BaseBlock.java @@ -1,13 +1,14 @@ package io.anuke.mindustry.world; -import com.badlogic.gdx.math.GridPoint2; +import com.badlogic.gdx.utils.Array; import io.anuke.mindustry.content.fx.EnvironmentFx; +import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.entities.Unit; import io.anuke.mindustry.entities.effect.Puddle; import io.anuke.mindustry.type.Item; import io.anuke.mindustry.type.Liquid; +import io.anuke.mindustry.world.consumers.ConsumeLiquid; import io.anuke.mindustry.world.consumers.Consumers; -import io.anuke.mindustry.world.consumers.Uses; import io.anuke.ucore.core.Effects; import io.anuke.ucore.core.Timers; import io.anuke.ucore.util.Mathf; @@ -71,9 +72,9 @@ public abstract class BaseBlock { } public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){ - return tile.entity.liquids.get(liquid) + amount < liquidCapacity && + return hasLiquids && tile.entity.liquids.get(liquid) + amount < liquidCapacity && (!singleLiquid || (tile.entity.liquids.current() == liquid || tile.entity.liquids.get(tile.entity.liquids.current()) < 0.01f)) - && (!consumes.has(Uses.liquid) || consumes.liquid() == liquid); + && (!consumes.has(ConsumeLiquid.class) || consumes.liquid() == liquid); } public void handleLiquid(Tile tile, Tile source, Liquid liquid, float amount){ @@ -94,25 +95,20 @@ public abstract class BaseBlock { } public void tryDumpLiquid(Tile tile, Liquid liquid){ - int size = tile.block().size; + Array proximity = tile.entity.proximity(); + int dump = tile.getDump(); - GridPoint2[] nearby = Edges.getEdges(size); - byte i = tile.getDump(); + for (int i = 0; i < proximity.size; i ++) { + incrementDump(tile, proximity.size); + Tile other = proximity.get((i + dump) % proximity.size); + Tile in = Edges.getFacingEdge(tile, other); - for (int j = 0; j < nearby.length; j ++) { - Tile other = tile.getNearby(nearby[i]); - Tile in = tile.getNearby(Edges.getInsideEdges(size)[i]); - - if(other != null) other = other.target(); - - if (other != null && other.block().hasLiquids) { + if (other.block().hasLiquids) { float ofract = other.entity.liquids.get(liquid) / other.block().liquidCapacity; float fract = tile.entity.liquids.get(liquid) / liquidCapacity; - if(ofract < fract) tryMoveLiquid(tile, in, other, (fract - ofract) * liquidCapacity / 2f, liquid); + if (ofract < fract) tryMoveLiquid(tile, in, other, (fract - ofract) * liquidCapacity / 2f, liquid); } - - i = (byte) ((i + 1) % nearby.length); } } @@ -173,16 +169,14 @@ public abstract class BaseBlock { * Tries to put this item into a nearby container, if there are no available * containers, it gets added to the block's inventory.*/ public void offloadNear(Tile tile, Item item){ - int size = tile.block().size; + Array proximity = tile.entity.proximity(); + int dump = tile.getDump(); - GridPoint2[] nearby = Edges.getEdges(size); - byte i = (byte)(tile.getDump() % nearby.length); - - for(int j = 0; j < nearby.length; j ++){ - tile.setDump((byte)((i + 1) % nearby.length)); - Tile other = tile.getNearby(nearby[i]); - Tile in = tile.getNearby(Edges.getInsideEdges(size)[i]); - if(other != null && other.block().acceptItem(item, other, in) && canDump(tile, other, item)){ + for(int i = 0; i < proximity.size; i ++){ + incrementDump(tile, proximity.size); + Tile other = proximity.get((i + dump) % proximity.size); + Tile in = Edges.getFacingEdge(tile, other); + if(other.block().acceptItem(item, other, in) && canDump(tile, other, item)){ other.block().handleItem(item, other, in); return; } @@ -196,42 +190,53 @@ public abstract class BaseBlock { return tryDump(tile, null); } - /**Try dumping a specific item near the tile.*/ + /**Try dumping a specific item near the tile. + * @param todump Item to dump. Can be null to dump anything.*/ public boolean tryDump(Tile tile, Item todump){ - if(tile.entity == null || !hasItems || tile.entity.items.total() == 0) return false; + TileEntity entity = tile.entity; + if(entity == null || !hasItems || tile.entity.items.total() == 0 || (todump != null && !entity.items.has(todump))) return false; - int size = tile.block().size; + Array proximity = entity.proximity(); + int dump = tile.getDump(); - GridPoint2[] nearby = Edges.getEdges(size); - byte i = (byte)(tile.getDump() % nearby.length); + if(proximity.size == 0) return false; - for(int j = 0; j < nearby.length; j ++){ - Tile other; - Tile in; + for(int i = 0; i < proximity.size; i ++){ + Tile other = proximity.get((i + dump) % proximity.size); + Tile in = Edges.getFacingEdge(tile, other); - for(int ii = 0; ii < Item.all().size; ii ++){ - Item item = Item.getByID(ii); - other = tile.getNearby(nearby[i]); - in = tile.getNearby(Edges.getInsideEdges(size)[i]); + if(todump == null) { - if(todump != null && item != todump) continue; + for (int ii = 0; ii < Item.all().size; ii++) { + Item item = Item.getByID(ii); - if(tile.entity.items.has(item) && other != null && other.block().acceptItem(item, other, in) && canDump(tile, other, item)){ - other.block().handleItem(item, other, in); - tile.entity.items.remove(item, 1); - i = (byte)((i + 1) % nearby.length); - tile.setDump(i); + if (other.block().acceptItem(item, other, in) && canDump(tile, other, item)) { + other.block().handleItem(item, other, in); + tile.entity.items.remove(item, 1); + incrementDump(tile, proximity.size); + return true; + } + } + }else{ + + if (other.block().acceptItem(todump, other, in) && canDump(tile, other, todump)) { + other.block().handleItem(todump, other, in); + tile.entity.items.remove(todump, 1); + incrementDump(tile, proximity.size); return true; } } - i = (byte)((i + 1) % nearby.length); - tile.setDump(i); + incrementDump(tile, proximity.size); } return false; } + private void incrementDump(Tile tile, int prox){ + tile.setDump((byte)((tile.getDump() + 1) % prox)); + } + /**Used for dumping items.*/ public boolean canDump(Tile tile, Tile to, Item item){ return true; diff --git a/core/src/io/anuke/mindustry/world/Block.java b/core/src/io/anuke/mindustry/world/Block.java index 93ecd50975..5e2b2631a5 100644 --- a/core/src/io/anuke/mindustry/world/Block.java +++ b/core/src/io/anuke/mindustry/world/Block.java @@ -101,7 +101,7 @@ public class Block extends BaseBlock implements Content{ /**list of displayed block status bars. Defaults to health bar.*/ public BlockBars bars = new BlockBars(); /**List of block stats.*/ - public BlockStats stats = new BlockStats(); + public BlockStats stats = new BlockStats(this); /**List of block flags. Used for AI indexing.*/ public EnumSet flags; /**Whether to automatically set the entity to 'sleeping' when created.*/ @@ -162,6 +162,8 @@ public class Block extends BaseBlock implements Content{ setStats(); setBars(); + + consumes.checkRequired(this); } @Override diff --git a/core/src/io/anuke/mindustry/world/Edges.java b/core/src/io/anuke/mindustry/world/Edges.java index 61575ba381..f8f0fe045b 100644 --- a/core/src/io/anuke/mindustry/world/Edges.java +++ b/core/src/io/anuke/mindustry/world/Edges.java @@ -7,6 +7,8 @@ import io.anuke.ucore.util.Mathf; import java.util.Arrays; +import static io.anuke.mindustry.Vars.world; + public class Edges { private static final int maxSize = 11; private static final int maxRadius = 12; @@ -49,6 +51,12 @@ public class Edges { } } + public static Tile getFacingEdge(Tile tile, Tile other){ + int size = tile.block().size; + return world.tile(tile.x + Mathf.clamp(other.x - tile.x, -(size-1) / 2, (size / 2)), + tile.y + Mathf.clamp(other.y - tile.y, -(size-1) / 2, (size / 2))); + } + public static Vector2[] getPixelPolygon(float radius){ if(radius < 1 || radius > maxRadius) throw new RuntimeException("Polygon size must be between 1 and " + maxRadius); return polygons[(int)(radius*2) - 1]; diff --git a/core/src/io/anuke/mindustry/world/Tile.java b/core/src/io/anuke/mindustry/world/Tile.java index 0f0c27a8d9..99cd9d987b 100644 --- a/core/src/io/anuke/mindustry/world/Tile.java +++ b/core/src/io/anuke/mindustry/world/Tile.java @@ -161,6 +161,7 @@ public class Tile implements PosTrait, TargetTrait { public void setBlock(Block type, int rotation){ synchronized (tileSetLock) { + preChanged(); if(rotation < 0) rotation = (-rotation + 2); this.wall = type; this.link = 0; @@ -171,6 +172,7 @@ public class Tile implements PosTrait, TargetTrait { public void setBlock(Block type){ synchronized (tileSetLock) { + preChanged(); this.wall = type; this.link = 0; changed(); @@ -366,8 +368,16 @@ public class Tile implements PosTrait, TargetTrait { cost += 1; } } + + private void preChanged(){ + synchronized (tileSetLock) { + if (entity != null) { + entity.removeFromProximity(); + } + } + } - public void changed(){ + private void changed(){ synchronized (tileSetLock) { if (entity != null) { @@ -381,12 +391,12 @@ public class Tile implements PosTrait, TargetTrait { if (block.hasEntity()) { entity = block.getEntity().init(this, block.update); - block.consumes.addAll(entity.cons.all()); if(block.hasItems) entity.items = new InventoryModule(); if(block.hasLiquids) entity.liquids = new LiquidModule(); if(block.hasPower) entity.power = new PowerModule(); } + entity.updateProximity(); updateOcclusion(); } diff --git a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/CooledTurret.java b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/CooledTurret.java index 7ca7f09a9e..d7f0e8dd89 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/CooledTurret.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/CooledTurret.java @@ -4,8 +4,7 @@ import io.anuke.mindustry.content.fx.BlockFx; import io.anuke.mindustry.entities.effect.Fire; import io.anuke.mindustry.type.Liquid; import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.meta.BlockStat; -import io.anuke.mindustry.world.meta.values.LiquidFilterValue; +import io.anuke.mindustry.world.consumers.ConsumeLiquidFilter; import io.anuke.ucore.core.Effects; import io.anuke.ucore.core.Effects.Effect; import io.anuke.ucore.core.Timers; @@ -24,13 +23,8 @@ public class CooledTurret extends Turret { super(name); hasLiquids = true; liquidCapacity = 20f; - } - @Override - public void setStats() { - super.setStats(); - - stats.add(BlockStat.inputLiquidAux, new LiquidFilterValue(liquid -> liquid.temperature <= 0.5f && liquid.flammability < 0.2f)); + consumes.add(new ConsumeLiquidFilter(liquid -> liquid.temperature <= 0.5f, 0.01f)).update(false).optional(true); } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java b/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java index cfad7d944e..6f5b4b2e30 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java @@ -35,7 +35,6 @@ public class ItemBridge extends Block { protected int timerTransport = timers++; protected int range; - protected float powerUse = 0.05f; protected float transportTime = 2f; protected IntArray removals = new IntArray(); @@ -155,11 +154,9 @@ public class ItemBridge extends Block { tryDump(tile); entity.uptime = 0f; }else{ - float use = Math.min(powerCapacity, powerUse * Timers.delta()); - if(!hasPower || entity.power.amount >= use){ + if(entity.cons.valid()){ entity.uptime = Mathf.lerpDelta(entity.uptime, 1f, 0.04f); - if(hasPower) entity.power.amount -= use; }else{ entity.uptime = Mathf.lerpDelta(entity.uptime, 0f, 0.02f); } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidBridge.java b/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidBridge.java index 738ece51d8..a16c0b6b7f 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidBridge.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidBridge.java @@ -26,15 +26,13 @@ public class LiquidBridge extends ItemBridge { if(!linkValid(tile, other)){ tryDumpLiquid(tile, entity.liquids.current()); }else{ - float use = Math.min(powerCapacity, powerUse * Timers.delta()); - - if(entity.power.amount >= use){ + if(entity.cons.valid()){ entity.uptime = Mathf.lerpDelta(entity.uptime, 1f, 0.04f); - entity.power.amount -= use; }else{ entity.uptime = Mathf.lerpDelta(entity.uptime, 0f, 0.02f); } + if(entity.uptime >= 0.5f){ if(tryMoveLiquid(tile, other, false, entity.liquids.current()) > 0.1f){ diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidExtendingBridge.java b/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidExtendingBridge.java index cf0ce206d1..d8fc185adc 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidExtendingBridge.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidExtendingBridge.java @@ -26,11 +26,8 @@ public class LiquidExtendingBridge extends ExtendingItemBridge { if(!linkValid(tile, other)){ tryDumpLiquid(tile, entity.liquids.current()); }else{ - float use = Math.min(powerCapacity, powerUse * Timers.delta()); - - if(!hasPower || entity.power.amount >= use){ + if(entity.cons.valid()){ entity.uptime = Mathf.lerpDelta(entity.uptime, 1f, 0.04f); - if(hasPower) entity.power.amount -= use; }else{ entity.uptime = Mathf.lerpDelta(entity.uptime, 0f, 0.02f); } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidJunction.java b/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidJunction.java index 3d1a47f8c3..3f99bce32b 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidJunction.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidJunction.java @@ -6,11 +6,12 @@ import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.LiquidBlock; import io.anuke.ucore.graphics.Draw; +//TODO fix public class LiquidJunction extends LiquidBlock{ public LiquidJunction(String name) { super(name); - hasLiquids = false; + hasLiquids = true; } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/WarpGate.java b/core/src/io/anuke/mindustry/world/blocks/distribution/WarpGate.java index 5d7b1488b6..4a1b8e849e 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/WarpGate.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/WarpGate.java @@ -34,6 +34,7 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.tilesize; +//TODO implement public class WarpGate extends PowerBlock{ public static final Color[] colorArray = {Color.ROYAL, Color.ORANGE, Color.SCARLET, Color.LIME, Color.PURPLE, Color.GOLD, Color.PINK, Color.LIGHT_GRAY}; @@ -52,8 +53,6 @@ public class WarpGate extends PowerBlock{ //time between teleports protected float teleportMax = 400f; protected float teleportLiquidUse = 0.3f; - protected float liquidUse = 0.1f; - protected float powerUse = 0.3f; protected Liquid inputLiquid = Liquids.cryofluid; protected Effect activateEffect = BlockFx.teleportActivate; protected Effect teleportEffect = BlockFx.teleport; @@ -162,10 +161,9 @@ public class WarpGate extends PowerBlock{ entity.power.amount = 0f; Effects.effect(activateEffect, resultColor, tile.drawx(), tile.drawy()); } - }else { + }else{ entity.activeScl = Mathf.lerpDelta(entity.activeScl, 1f, 0.015f); - - float powerUsed = Math.min(powerCapacity, powerUse * Timers.delta()); + /* if (entity.power.amount >= powerUsed) { entity.power.amount -= powerUsed; @@ -179,15 +177,13 @@ public class WarpGate extends PowerBlock{ catastrophicFailure(tile); } - float liquidUsed = Math.min(liquidCapacity, liquidUse * Timers.delta()); - if (entity.liquids.amount >= liquidUsed) { entity.liquids.amount -= liquidUsed; entity.liquidLackScl = Mathf.lerpDelta(entity.liquidLackScl, 0f, 0.1f); }else{ entity.liquids.amount = 0f; entity.liquidLackScl = Mathf.lerpDelta(entity.liquidLackScl, 1f, 0.1f); - } + }*/ if(entity.liquidLackScl >= 0.999f){ catastrophicFailure(tile); @@ -197,19 +193,19 @@ public class WarpGate extends PowerBlock{ if (entity.teleporting) { entity.speedScl = Mathf.lerpDelta(entity.speedScl, 2f, 0.01f); - liquidUsed = Math.min(liquidCapacity, teleportLiquidUse * Timers.delta()); + //liquidUsed = Math.min(liquidCapacity, teleportLiquidUse * Timers.delta()); - if (entity.liquids.amount >= liquidUsed) { - entity.liquids.amount -= liquidUsed; - } else { + //if (entity.liquids.amount >= liquidUsed) { + // entity.liquids.amount -= liquidUsed; + //} else { catastrophicFailure(tile); - } + //} } else { entity.speedScl = Mathf.lerpDelta(entity.speedScl, 1f, 0.04f); } entity.time += Timers.delta() * entity.speedScl; - +/* if (!entity.teleporting && entity.items.total() >= itemCapacity && entity.power.amount >= powerCapacity - 0.01f - powerUse && entity.timer.get(timerTeleport, teleportMax)) { Array testLinks = findLinks(tile); @@ -238,7 +234,7 @@ public class WarpGate extends PowerBlock{ entity.power.amount = 0f; entity.teleporting = false; }); - } + }*/ } } diff --git a/core/src/io/anuke/mindustry/world/blocks/power/FusionReactor.java b/core/src/io/anuke/mindustry/world/blocks/power/FusionReactor.java index a5da34207e..b4c36a3f5c 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/FusionReactor.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/FusionReactor.java @@ -2,11 +2,11 @@ package io.anuke.mindustry.world.blocks.power; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.TextureRegion; -import io.anuke.mindustry.content.Liquids; import io.anuke.mindustry.entities.TileEntity; -import io.anuke.mindustry.type.Liquid; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.production.GenericCrafter.GenericCrafterEntity; +import io.anuke.mindustry.world.meta.BlockStat; +import io.anuke.mindustry.world.meta.StatUnit; import io.anuke.ucore.core.Graphics; import io.anuke.ucore.core.Timers; import io.anuke.ucore.graphics.Draw; @@ -14,10 +14,7 @@ import io.anuke.ucore.util.Mathf; public class FusionReactor extends PowerGenerator { protected int plasmas = 4; - protected float powerUsage = 0.5f; - protected float maxPowerProduced = 1.5f; - protected float liquidUsage = 1f; - protected Liquid inputLiquid = Liquids.water; + protected float maxPowerProduced = 2f; protected float warmupSpeed = 0.001f; protected Color plasma1 = Color.valueOf("ffd06b"), plasma2 = Color.valueOf("ff361b"); @@ -32,16 +29,18 @@ public class FusionReactor extends PowerGenerator { hasItems = true; } + @Override + public void setStats() { + super.setStats(); + + stats.add(BlockStat.maxPowerGeneration, maxPowerProduced * 60f, StatUnit.powerSecond); + } + @Override public void update(Tile tile){ FusionReactorEntity entity = tile.entity(); - float powerUse = Math.min(powerCapacity, powerUsage * Timers.delta()); - float liquidUse = Math.min(liquidCapacity, liquidUsage * Timers.delta()); - - if(entity.power.amount >= powerUse && entity.liquids.amount >= liquidUse){ - entity.power.amount -= powerUse; - entity.liquids.amount -= liquidUse; + if(entity.cons.valid()){ entity.warmup = Mathf.lerpDelta(entity.warmup, 1f, warmupSpeed); }else{ entity.warmup = Mathf.lerpDelta(entity.warmup, 0f, 0.01f); @@ -113,11 +112,6 @@ public class FusionReactor extends PowerGenerator { return new FusionReactorEntity(); } - @Override - public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount) { - return super.acceptLiquid(tile, source, liquid, amount) && liquid == inputLiquid; - } - @Override public void onDestroyed(Tile tile) { super.onDestroyed(tile); diff --git a/core/src/io/anuke/mindustry/world/blocks/power/ItemGenerator.java b/core/src/io/anuke/mindustry/world/blocks/power/ItemGenerator.java index 265ae49baf..e1973d64d2 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/ItemGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/ItemGenerator.java @@ -7,10 +7,10 @@ import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.type.Item; import io.anuke.mindustry.world.BarType; import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.consumers.ConsumeItemFilter; import io.anuke.mindustry.world.meta.BlockBar; import io.anuke.mindustry.world.meta.BlockStat; import io.anuke.mindustry.world.meta.StatUnit; -import io.anuke.mindustry.world.meta.values.ItemFilterValue; import io.anuke.ucore.core.Effects; import io.anuke.ucore.core.Effects.Effect; import io.anuke.ucore.core.Timers; @@ -36,6 +36,8 @@ public abstract class ItemGenerator extends PowerGenerator { super(name); itemCapacity = 20; hasItems = true; + + consumes.add(new ConsumeItemFilter(item -> getItemEfficiency(item) >= minItemEfficiency)).update(false); } @Override @@ -48,7 +50,6 @@ public abstract class ItemGenerator extends PowerGenerator { public void setStats() { super.setStats(); - stats.add(BlockStat.inputItems, new ItemFilterValue(item -> getItemEfficiency(item) >= minItemEfficiency)); stats.add(BlockStat.maxPowerGeneration, powerOutput * 60f, StatUnit.powerSecond); } @@ -96,17 +97,12 @@ public abstract class ItemGenerator extends PowerGenerator { Effects.effect(explodeEffect, tile.worldx() + Mathf.range(size * tilesize/2f), tile.worldy() + Mathf.range(size * tilesize/2f)); } } - - if(entity.generateTime <= 0f && entity.items.total() > 0){ - Effects.effect(generateEffect, tile.worldx() + Mathf.range(size * tilesize/2f), tile.worldy() + Mathf.range(size * tilesize/2f)); - for(int i = 0; i < entity.items.items.length; i ++){ - if(entity.items.items[i] > 0){ - entity.items.items[i] --; - entity.efficiency = getItemEfficiency(Item.getByID(i)); - entity.explosiveness = Item.getByID(i).explosiveness; - break; - } - } + + if (entity.generateTime <= 0f && entity.items.total() > 0) { + Effects.effect(generateEffect, tile.worldx() + Mathf.range(3f), tile.worldy() + Mathf.range(3f)); + Item item = entity.items.take(); + entity.efficiency = getItemEfficiency(item); + entity.explosiveness = item.explosiveness; entity.generateTime = 1f; } diff --git a/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java b/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java index 6adc8bf50f..5b9d3f32d1 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java @@ -4,8 +4,7 @@ import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.type.Item; import io.anuke.mindustry.type.Liquid; import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.meta.BlockStat; -import io.anuke.mindustry.world.meta.values.LiquidFilterValue; +import io.anuke.mindustry.world.consumers.ConsumeLiquidFilter; import io.anuke.ucore.core.Effects; import io.anuke.ucore.core.Timers; import io.anuke.ucore.graphics.Draw; @@ -23,13 +22,8 @@ public abstract class ItemLiquidGenerator extends ItemGenerator { super(name); hasLiquids = true; liquidCapacity = 10f; - } - @Override - public void setStats() { - super.setStats(); - - stats.add(BlockStat.inputLiquid, new LiquidFilterValue(item -> getLiquidEfficiency(item) >= minLiquidEfficiency)); + consumes.add(new ConsumeLiquidFilter(liquid -> getLiquidEfficiency(liquid) >= minLiquidEfficiency, 0.001f)).update(false); } @Override @@ -37,12 +31,12 @@ public abstract class ItemLiquidGenerator extends ItemGenerator { ItemGeneratorEntity entity = tile.entity(); //liquid takes priority over solids - if(entity.liquids.amount >= 0.001f){ - float powerPerLiquid = getLiquidEfficiency(entity.liquids.liquid)*this.powerPerLiquid; - float used = Math.min(entity.liquids.amount, maxLiquidGenerate * Timers.delta()); + if(entity.liquids.currentAmount() >= 0.001f){ + float powerPerLiquid = getLiquidEfficiency(entity.liquids.current())*this.powerPerLiquid; + float used = Math.min(entity.liquids.currentAmount(), maxLiquidGenerate * Timers.delta()); used = Math.min(used, (powerCapacity - entity.power.amount)/powerPerLiquid); - entity.liquids.amount -= used; + entity.liquids.remove(entity.liquids.current(), used); entity.power.amount += used * powerPerLiquid; if(used > 0.001f && Mathf.chance(0.05 * Timers.delta())){ @@ -66,14 +60,9 @@ public abstract class ItemLiquidGenerator extends ItemGenerator { if (entity.generateTime <= 0f && entity.items.total() > 0) { Effects.effect(generateEffect, tile.worldx() + Mathf.range(3f), tile.worldy() + Mathf.range(3f)); - for (int i = 0; i < entity.items.items.length; i++) { - if (entity.items.items[i] > 0) { - entity.items.items[i]--; - entity.efficiency = getItemEfficiency(Item.getByID(i)); - entity.explosiveness = Item.getByID(i).explosiveness; - break; - } - } + Item item = entity.items.take(); + entity.efficiency = getItemEfficiency(item); + entity.explosiveness = item.explosiveness; entity.generateTime = 1f; } } @@ -87,8 +76,8 @@ public abstract class ItemLiquidGenerator extends ItemGenerator { TileEntity entity = tile.entity(); - Draw.color(entity.liquids.liquid.color); - Draw.alpha(entity.liquids.amount / liquidCapacity); + Draw.color(entity.liquids.current().color); + Draw.alpha(entity.liquids.currentAmount() / liquidCapacity); drawLiquidCenter(tile); Draw.color(); } diff --git a/core/src/io/anuke/mindustry/world/blocks/power/LiquidGenerator.java b/core/src/io/anuke/mindustry/world/blocks/power/LiquidGenerator.java index ff276b057e..ddb092e181 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/LiquidGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/LiquidGenerator.java @@ -5,8 +5,7 @@ import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.type.Liquid; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.power.ItemGenerator.ItemGeneratorEntity; -import io.anuke.mindustry.world.meta.BlockStat; -import io.anuke.mindustry.world.meta.values.LiquidFilterValue; +import io.anuke.mindustry.world.consumers.ConsumeLiquidFilter; import io.anuke.ucore.core.Effects; import io.anuke.ucore.core.Effects.Effect; import io.anuke.ucore.core.Timers; @@ -24,13 +23,8 @@ public abstract class LiquidGenerator extends PowerGenerator { super(name); liquidCapacity = 30f; hasLiquids = true; - } - @Override - public void setStats() { - super.setStats(); - - stats.add(BlockStat.inputLiquid, new LiquidFilterValue(item -> getEfficiency(item) >= minEfficiency)); + consumes.add(new ConsumeLiquidFilter(liquid -> getEfficiency(liquid) >= minEfficiency, 0.001f)).update(false); } @Override @@ -39,8 +33,8 @@ public abstract class LiquidGenerator extends PowerGenerator { TileEntity entity = tile.entity(); - Draw.color(entity.liquids.liquid.color); - Draw.alpha(entity.liquids.amount / liquidCapacity); + Draw.color(entity.liquids.current().color); + Draw.alpha(entity.liquids.total() / liquidCapacity); drawLiquidCenter(tile); Draw.color(); } @@ -53,12 +47,12 @@ public abstract class LiquidGenerator extends PowerGenerator { public void update(Tile tile){ TileEntity entity = tile.entity(); - if(entity.liquids.amount > 0){ - float powerPerLiquid = getEfficiency(entity.liquids.liquid)*this.powerPerLiquid; - float used = Math.min(entity.liquids.amount, maxLiquidGenerate * Timers.delta()); + if(entity.liquids.get(entity.liquids.current()) >= 0.001f){ + float powerPerLiquid = getEfficiency(entity.liquids.current())*this.powerPerLiquid; + float used = Math.min(entity.liquids.currentAmount(), maxLiquidGenerate * Timers.delta()); used = Math.min(used, (powerCapacity - entity.power.amount)/powerPerLiquid); - - entity.liquids.amount -= used; + + entity.liquids.remove(entity.liquids.current(), used); entity.power.amount += used * powerPerLiquid; if(used > 0.001f && Mathf.chance(0.05 * Timers.delta())){ diff --git a/core/src/io/anuke/mindustry/world/blocks/power/NuclearReactor.java b/core/src/io/anuke/mindustry/world/blocks/power/NuclearReactor.java index 1184da288b..c5acb2c935 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/NuclearReactor.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/NuclearReactor.java @@ -6,7 +6,6 @@ import io.anuke.mindustry.content.fx.BlockFx; import io.anuke.mindustry.content.fx.ExplosionFx; import io.anuke.mindustry.entities.Damage; import io.anuke.mindustry.entities.TileEntity; -import io.anuke.mindustry.type.Item; import io.anuke.mindustry.type.Liquid; import io.anuke.mindustry.world.BarType; import io.anuke.mindustry.world.Tile; @@ -26,12 +25,12 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.tilesize; +//TODO refactor to use consumers public class NuclearReactor extends PowerGenerator { protected final int timerFuel = timers++; protected final Translator tr = new Translator(); - protected Item generateItem; protected Color coolColor = new Color(1, 1, 1, 0f); protected Color hotColor = Color.valueOf("ff9575a3"); protected int fuelUseTime = 120; //time to consume 1 fuel @@ -39,32 +38,32 @@ public class NuclearReactor extends PowerGenerator { protected float heating = 0.009f; //heating per frame protected float coolantPower = 0.015f; //how much heat decreases per coolant unit protected float smokeThreshold = 0.3f; //threshold at which block starts smoking - protected float maxLiquidUse = 1f; //max liquid use per frame + protected float maxLiquidUse = 4f; //max liquid use per frame protected int explosionRadius = 19; protected int explosionDamage = 135; protected float flashThreshold = 0.46f; //heat threshold at which the lights start flashing public NuclearReactor(String name) { super(name); - generateItem = Items.thorium; itemCapacity = 30; liquidCapacity = 50; powerCapacity = 80f; hasItems = true; hasLiquids = true; + + consumes.item(Items.thorium); } @Override public void setBars(){ super.setBars(); - bars.replace(new BlockBar(BarType.inventory, true, tile -> (float)tile.entity.items.get(generateItem) / itemCapacity)); + bars.replace(new BlockBar(BarType.inventory, true, tile -> (float)tile.entity.items.get(consumes.item()) / itemCapacity)); bars.add(new BlockBar(BarType.heat, true, tile -> tile.entity().heat)); } @Override public void setStats(){ super.setStats(); - stats.add(BlockStat.inputItem, generateItem); stats.add(BlockStat.inputLiquid, new LiquidFilterValue(liquid -> liquid.temperature <= 0.5f)); stats.add(BlockStat.maxPowerGeneration, powerMultiplier*60f, StatUnit.powerSecond); } @@ -73,7 +72,7 @@ public class NuclearReactor extends PowerGenerator { public void update(Tile tile){ NuclearReactorEntity entity = tile.entity(); - int fuel = entity.items.get(generateItem); + int fuel = entity.items.get(consumes.item()); float fullness = (float)fuel / itemCapacity; if(fuel > 0){ @@ -81,22 +80,23 @@ public class NuclearReactor extends PowerGenerator { entity.power.amount += powerMultiplier * fullness * Timers.delta(); entity.power.amount = Mathf.clamp(entity.power.amount, 0f, powerCapacity); if(entity.timer.get(timerFuel, fuelUseTime)){ - entity.items.remove(generateItem, 1); + entity.items.remove(consumes.item(), 1); } } - if(entity.liquids.amount > 0){ + if(entity.liquids.total() > 0){ + Liquid liquid = entity.liquids.current(); - if(entity.liquids.liquid.temperature <= 0.5f){ //is coolant - float pow = coolantPower * entity.liquids.liquid.heatCapacity; //heat depleted per unit of liquid - float maxUsed = Math.min(Math.min(entity.liquids.amount, entity.heat / pow), maxLiquidUse * Timers.delta()); //max that can be cooled in terms of liquid + if(liquid.temperature <= 0.5f){ //is coolant + float pow = coolantPower * liquid.heatCapacity; //heat depleted per unit of liquid + float maxUsed = Math.min(Math.min(entity.liquids.get(liquid), entity.heat / pow), maxLiquidUse * Timers.delta()); //max that can be cooled in terms of liquid entity.heat -= maxUsed * pow; - entity.liquids.amount -= maxUsed; + entity.liquids.remove(liquid, maxUsed); }else{ //is heater - float heat = coolantPower * entity.liquids.liquid.heatCapacity / 4f; //heat created per unit of liquid - float maxUsed = Math.min(Math.min(entity.liquids.amount, (1f - entity.heat) / heat), maxLiquidUse * Timers.delta()); //max liquid used + float heat = coolantPower * liquid.heatCapacity / 4f; //heat created per unit of liquid + float maxUsed = Math.min(Math.min(entity.liquids.get(liquid), (1f - entity.heat) / heat), maxLiquidUse * Timers.delta()); //max liquid used entity.heat += maxUsed * heat; - entity.liquids.amount -= maxUsed; + entity.liquids.remove(liquid, maxUsed); } } @@ -123,7 +123,7 @@ public class NuclearReactor extends PowerGenerator { NuclearReactorEntity entity = tile.entity(); - int fuel = entity.items.get(generateItem); + int fuel = entity.items.get(consumes.item()); if(fuel < 5 && entity.heat < 0.5f) return; @@ -153,15 +153,10 @@ public class NuclearReactor extends PowerGenerator { } } - @Override - public boolean acceptItem(Item item, Tile tile, Tile source){ - return item == generateItem && tile.entity.items.get(generateItem) < itemCapacity; - } - @Override public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){ - return tile.entity.liquids.amount + amount < liquidCapacity - && (tile.entity.liquids.liquid == liquid || tile.entity.liquids.amount <= 0.001f); + return tile.entity.liquids.get(liquid) + amount < liquidCapacity && liquid.temperature <= 0.5f && + (tile.entity.liquids.current() == liquid || tile.entity.liquids.get(tile.entity.liquids.current()) < 0.01f); } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/power/TurbineGenerator.java b/core/src/io/anuke/mindustry/world/blocks/power/TurbineGenerator.java index d29d5bb67e..8efd90fbc9 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/TurbineGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/TurbineGenerator.java @@ -1,57 +1,10 @@ package io.anuke.mindustry.world.blocks.power; -import io.anuke.mindustry.content.Liquids; -import io.anuke.mindustry.entities.TileEntity; -import io.anuke.mindustry.type.Liquid; -import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.meta.BlockStat; -import io.anuke.ucore.core.Timers; - +//TODO public class TurbineGenerator extends BurnerGenerator { - protected float auxLiquidUse = 0.1f; - protected Liquid auxLiquid = Liquids.water; - protected float auxLiquidCapacity = 10; public TurbineGenerator(String name) { super(name); - } - - @Override - public void setStats() { - super.setStats(); - - stats.add(BlockStat.inputLiquidAux, auxLiquid); - } - - @Override - public void update(Tile tile) { - TurbineEntity entity = tile.entity(); - float used = Math.min(auxLiquidUse * Timers.delta(), auxLiquidCapacity); - - if(entity.aux >= used){ - super.update(tile); - entity.aux -= used; - } - } - - @Override - public float handleAuxLiquid(Tile tile, Tile source, Liquid liquid, float amount) { - TurbineEntity entity = tile.entity(); - if(liquid == auxLiquid){ - float accepted = Math.min(auxLiquidCapacity - entity.aux, amount); - entity.aux += accepted; - return accepted; - }else { - return 0; - } - } - - @Override - public TileEntity getEntity() { - return new TurbineEntity(); - } - - public class TurbineEntity extends ItemGeneratorEntity{ - public float aux; + singleLiquid = false; } } diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Compressor.java b/core/src/io/anuke/mindustry/world/blocks/production/Compressor.java index ffe9544a59..4fde97444e 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Compressor.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Compressor.java @@ -4,11 +4,12 @@ import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.TextureRegion; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.production.GenericCrafter.GenericCrafterEntity; -import io.anuke.ucore.core.Timers; import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.util.Mathf; public class Compressor extends PowerCrafter { + protected TextureRegion liquidRegion, topRegion; + protected TextureRegion[] frameRegions; public Compressor(String name) { super(name); @@ -16,32 +17,16 @@ public class Compressor extends PowerCrafter { } @Override - public void update(Tile tile) { - GenericCrafterEntity entity = tile.entity(); + public void load() { + super.load(); - float powerUsed = Math.min(Timers.delta() * powerUse, tile.entity.power.amount); - float liquidAdded = Math.min(outputLiquidAmount * Timers.delta(), liquidCapacity - entity.liquids.amount); - int itemsUsed = Mathf.ceil(1 + input.amount * entity.progress); - - if(entity.power.amount > powerUsed && entity.items.has(input.item, itemsUsed) && liquidAdded > 0.001f){ - entity.progress += 1f/craftTime; - entity.totalProgress += Timers.delta(); - handleLiquid(tile, tile, outputLiquid, liquidAdded); - } - - if(entity.progress >= 1f){ - entity.items.remove(input); - if(outputItem != null) offloadNear(tile, outputItem); - entity.progress = 0f; - } - - if(outputItem != null && entity.timer.get(timerDump, 5)){ - tryDump(tile, outputItem); - } - - if(outputLiquid != null){ - tryDumpLiquid(tile); + frameRegions = new TextureRegion[3]; + for (int i = 0; i < 3; i++) { + frameRegions[i] = Draw.region(name + "-frame" + i); } + + liquidRegion = Draw.region(name + "-liquid"); + topRegion = Draw.region(name + "-top"); } @Override @@ -49,11 +34,11 @@ public class Compressor extends PowerCrafter { GenericCrafterEntity entity = tile.entity(); Draw.rect(region, tile.drawx(), tile.drawy()); - Draw.rect(name + "-frame" + (int) Mathf.absin(entity.totalProgress, 5f, 2.999f), tile.drawx(), tile.drawy()); - Draw.color(Color.CLEAR, tile.entity.liquids.liquid.color, tile.entity.liquids.amount / liquidCapacity); - Draw.rect(name + "-liquid", tile.drawx(), tile.drawy()); + Draw.rect(frameRegions[(int) Mathf.absin(entity.totalProgress, 5f, 2.999f)], tile.drawx(), tile.drawy()); + Draw.color(Color.CLEAR, tile.entity.liquids.current().color, tile.entity.liquids.total() / liquidCapacity); + Draw.rect(liquidRegion, tile.drawx(), tile.drawy()); Draw.color(); - Draw.rect(name + "-top", tile.drawx(), tile.drawy()); + Draw.rect(topRegion, tile.drawx(), tile.drawy()); } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Cultivator.java b/core/src/io/anuke/mindustry/world/blocks/production/Cultivator.java index 2af65d16a4..c0f473e4cd 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Cultivator.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Cultivator.java @@ -23,6 +23,7 @@ public class Cultivator extends Drill { protected Color plantColor = Color.valueOf("648b55"); protected Color plantColorLight = Color.valueOf("73a75f"); protected Color bottomColor = Color.valueOf("474747"); + protected TextureRegion middleRegion, topRegion; protected Item result; @@ -44,13 +45,20 @@ public class Cultivator extends Drill { }); } + @Override + public void load() { + super.load(); + + middleRegion = Draw.region(name + "-middle"); + topRegion = Draw.region(name + "-top"); + } + @Override public void update(Tile tile) { super.update(tile); CultivatorEntity entity = tile.entity(); - entity.warmup = Mathf.lerpDelta(entity.warmup, - tile.entity.liquids.amount > liquidUse ? 1f : 0f, 0.015f); + entity.warmup = Mathf.lerpDelta(entity.warmup, entity.cons.valid() ? 1f : 0f, 0.015f); } @Override @@ -61,7 +69,7 @@ public class Cultivator extends Drill { Draw.color(plantColor); Draw.alpha(entity.warmup); - Draw.rect(name + "-middle", tile.drawx(), tile.drawy()); + Draw.rect(middleRegion, tile.drawx(), tile.drawy()); Draw.color(bottomColor, plantColorLight, entity.warmup); @@ -78,7 +86,7 @@ public class Cultivator extends Drill { } Draw.color(); - Draw.rect(name + "-top", tile.drawx(), tile.drawy()); + Draw.rect(topRegion, tile.drawx(), tile.drawy()); } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Drill.java b/core/src/io/anuke/mindustry/world/blocks/production/Drill.java index 0c315c51bd..28aaffbefb 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Drill.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Drill.java @@ -10,7 +10,7 @@ import io.anuke.mindustry.graphics.Layer; import io.anuke.mindustry.type.Item; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.consumers.Uses; +import io.anuke.mindustry.world.consumers.ConsumeLiquid; import io.anuke.mindustry.world.meta.BlockGroup; import io.anuke.mindustry.world.meta.BlockStat; import io.anuke.mindustry.world.meta.StatUnit; @@ -167,7 +167,7 @@ public class Drill extends Block{ float speed = 1f; - if(entity.consumed(Uses.liquid) && !liquidRequired){ + if(entity.consumed(ConsumeLiquid.class) && !liquidRequired){ speed = liquidBoostIntensity; } diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Fracker.java b/core/src/io/anuke/mindustry/world/blocks/production/Fracker.java index 7b7c68c50e..5a2700bcb1 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Fracker.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Fracker.java @@ -1,21 +1,14 @@ package io.anuke.mindustry.world.blocks.production; import com.badlogic.gdx.graphics.g2d.TextureRegion; -import io.anuke.mindustry.content.Items; import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.type.Item; -import io.anuke.mindustry.type.Liquid; import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.meta.BlockStat; -import io.anuke.mindustry.world.meta.StatUnit; +import io.anuke.mindustry.world.consumers.ConsumeItem; import io.anuke.ucore.core.Timers; import io.anuke.ucore.graphics.Draw; public class Fracker extends SolidPump { - protected Liquid inputLiquid; - protected float inputLiquidUse; - protected float inputCapacity = 20f; - protected Item inputItem = Items.sand; protected float itemUseTime = 100f; protected TextureRegion liquidRegion; @@ -26,6 +19,9 @@ public class Fracker extends SolidPump { super(name); hasItems = true; itemCapacity = 20; + singleLiquid = false; + + consumes.require(ConsumeItem.class); } @Override @@ -40,10 +36,6 @@ public class Fracker extends SolidPump { @Override public void setStats() { super.setStats(); - - stats.add(BlockStat.inputItem, inputItem); - stats.add(BlockStat.inputLiquid, inputLiquid); - stats.add(BlockStat.liquidUse, 60f *inputLiquidUse, StatUnit.liquidSecond); } @Override @@ -52,8 +44,8 @@ public class Fracker extends SolidPump { Draw.rect(region, tile.drawx(), tile.drawy()); - Draw.color(tile.entity.liquids.liquid.color); - Draw.alpha(tile.entity.liquids.amount/liquidCapacity); + Draw.color(result.color); + Draw.alpha(tile.entity.liquids.get(result)/liquidCapacity); Draw.rect(liquidRegion, tile.drawx(), tile.drawy()); Draw.color(); @@ -69,35 +61,18 @@ public class Fracker extends SolidPump { @Override public void update(Tile tile) { FrackerEntity entity = tile.entity(); + Item item = consumes.item(); - while(entity.accumulator > itemUseTime && entity.items.has(inputItem, 1)){ - entity.items.remove(inputItem, 1); + while(entity.accumulator > itemUseTime && entity.items.has(item, 1)){ + entity.items.remove(item, 1); entity.accumulator -= itemUseTime; } - if(entity.input >= Math.min(inputLiquidUse * Timers.delta(), inputCapacity) && entity.accumulator < itemUseTime){ + if(entity.cons.valid() && entity.accumulator < itemUseTime){ super.update(tile); - entity.input -= inputLiquidUse * Timers.delta(); entity.accumulator += Timers.delta(); }else{ - tryDumpLiquid(tile); - } - } - - @Override - public boolean acceptItem(Item item, Tile tile, Tile source) { - return item == inputItem && tile.entity.items.total() < itemCapacity; - } - - @Override - public float handleAuxLiquid(Tile tile, Tile source, Liquid liquid, float amount) { - if(liquid != inputLiquid){ - return 0f; - }else{ - FrackerEntity entity = tile.entity(); - float accepted = Math.min(inputCapacity - entity.input, amount); - entity.input += accepted; - return accepted; + tryDumpLiquid(tile, result); } } @@ -107,7 +82,6 @@ public class Fracker extends SolidPump { } public static class FrackerEntity extends SolidPumpEntity{ - public float input; public float accumulator; } } diff --git a/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java b/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java index 7abfd25839..8f7f773915 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java @@ -5,11 +5,10 @@ import io.anuke.mindustry.content.fx.BlockFx; import io.anuke.mindustry.content.fx.Fx; import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.type.Item; -import io.anuke.mindustry.type.ItemStack; -import io.anuke.mindustry.type.Liquid; import io.anuke.mindustry.world.BarType; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.consumers.ConsumeItem; import io.anuke.mindustry.world.meta.BlockBar; import io.anuke.mindustry.world.meta.BlockStat; import io.anuke.mindustry.world.meta.StatUnit; @@ -27,14 +26,14 @@ public class GenericCrafter extends Block{ protected final int timerDump = timers++; /**Can be null. If you use this, make sure to set hasItems to true!*/ - protected ItemStack inputItem; + //protected ItemStack inputItem; /**Can be null. If you use this, make sure to set hasLiquids to true!*/ - protected Liquid inputLiquid; + //protected Liquid inputLiquid; /**Required.*/ protected Item output; protected float craftTime = 80; - protected float powerUse; - protected float liquidUse; + //protected float powerUse; + //protected float liquidUse; protected Effect craftEffect = BlockFx.purify; protected Effect updateEffect = Fx.none; protected float updateEffectChance = 0.04f; @@ -50,8 +49,8 @@ public class GenericCrafter extends Block{ public void setBars(){ super.setBars(); - if(inputItem != null) bars.replace(new BlockBar(BarType.inventory, true, - tile -> (float)tile.entity.items.get(inputItem.item) / itemCapacity)); + if(consumes.has(ConsumeItem.class)) bars.replace(new BlockBar(BarType.inventory, true, + tile -> (float)tile.entity.items.get(consumes.item()) / itemCapacity)); } @Override @@ -59,11 +58,6 @@ public class GenericCrafter extends Block{ super.setStats(); stats.add(BlockStat.craftSpeed, 60f/craftTime, StatUnit.itemsSecond); stats.add(BlockStat.outputItem, output); - - if(inputLiquid != null) stats.add(BlockStat.inputLiquid, inputLiquid); - if(inputLiquid != null) stats.add(BlockStat.liquidUse, (liquidUse * craftTime), StatUnit.liquidSecond); - if(inputItem != null) stats.add(BlockStat.inputItem, inputItem); - if(hasPower) stats.add(BlockStat.powerUse, powerUse * 60f, StatUnit.powerSecond); } @Override @@ -72,8 +66,8 @@ public class GenericCrafter extends Block{ if(!hasLiquids) return; - Draw.color(tile.entity.liquids.liquid.color); - Draw.alpha(tile.entity.liquids.amount / liquidCapacity); + Draw.color(tile.entity.liquids.current().color); + Draw.alpha(tile.entity.liquids.total() / liquidCapacity); Draw.rect("blank", tile.drawx(), tile.drawy(), 2, 2); Draw.color(); } @@ -87,19 +81,11 @@ public class GenericCrafter extends Block{ public void update(Tile tile){ GenericCrafterEntity entity = tile.entity(); - float powerUsed = Math.min(powerCapacity, powerUse * Timers.delta()); - float liquidUsed = Math.min(liquidCapacity, liquidUse * Timers.delta()); - int itemsUsed = (inputItem == null ? 0 : (int)(1 + inputItem.amount * entity.progress)); - - if((!hasLiquids || entity.liquids.amount >= liquidUsed) && - (!hasPower || entity.power.amount >= powerUsed) && - (inputItem == null || entity.items.has(inputItem.item, itemsUsed))){ + if(entity.cons.valid()){ entity.progress += 1f / craftTime * Timers.delta(); entity.totalProgress += Timers.delta(); entity.warmup = Mathf.lerp(entity.warmup, 1f, 0.02f); - if(hasPower) entity.power.amount -= powerUsed; - if(hasLiquids) entity.liquids.amount -= liquidUsed; if(Mathf.chance(Timers.delta() * updateEffectChance)) Effects.effect(updateEffect, entity.x + Mathf.range(size*4f), entity.y + Mathf.range(size*4)); @@ -109,7 +95,7 @@ public class GenericCrafter extends Block{ if(entity.progress >= 1f){ - if(inputItem != null) tile.entity.items.remove(inputItem); + if(consumes.has(ConsumeItem.class)) tile.entity.items.remove(consumes.item(), consumes.itemAmount()); offloadNear(tile, output); Effects.effect(craftEffect, tile.drawx(), tile.drawy()); entity.progress = 0f; @@ -119,17 +105,6 @@ public class GenericCrafter extends Block{ tryDump(tile, output); } } - - @Override - public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){ - return super.acceptLiquid(tile, source, liquid, amount) && liquid == inputLiquid; - } - - @Override - public boolean acceptItem(Item item, Tile tile, Tile source){ - TileEntity entity = tile.entity(); - return inputItem != null && item == inputItem.item && entity.items.get(inputItem.item) < itemCapacity; - } @Override public TileEntity getEntity() { diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Incinerator.java b/core/src/io/anuke/mindustry/world/blocks/production/Incinerator.java index 285288b9c5..00b8997d07 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Incinerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Incinerator.java @@ -8,8 +8,6 @@ import io.anuke.mindustry.type.Liquid; import io.anuke.mindustry.world.BarType; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.meta.BlockStat; -import io.anuke.mindustry.world.meta.StatUnit; import io.anuke.ucore.core.Effects; import io.anuke.ucore.core.Effects.Effect; import io.anuke.ucore.core.Timers; @@ -18,7 +16,6 @@ import io.anuke.ucore.graphics.Fill; import io.anuke.ucore.util.Mathf; public class Incinerator extends Block { - protected float powerUse = 0.07f; protected Effect effect = BlockFx.fuelburn; protected Color flameColor = Color.valueOf("ffad9d"); @@ -28,6 +25,8 @@ public class Incinerator extends Block { hasLiquids = true; update = true; solid = true; + + consumes.power(0.05f); } @Override @@ -36,21 +35,11 @@ public class Incinerator extends Block { bars.remove(BarType.liquid); } - @Override - public void setStats() { - super.setStats(); - - stats.add(BlockStat.powerUse, powerUse * 60f, StatUnit.powerSecond); - } - @Override public void update(Tile tile) { IncineratorEntity entity = tile.entity(); - float used = Math.min(powerCapacity, powerUse * Timers.delta()); - - if(entity.power.amount >= used){ - entity.power.amount -= used; + if(entity.cons.valid()){ entity.heat = Mathf.lerpDelta(entity.heat, 1f, 0.04f); }else{ entity.heat = Mathf.lerpDelta(entity.heat, 0f, 0.02f); diff --git a/core/src/io/anuke/mindustry/world/blocks/production/LiquidMixer.java b/core/src/io/anuke/mindustry/world/blocks/production/LiquidMixer.java index 0611b7c727..befc9cc980 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/LiquidMixer.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/LiquidMixer.java @@ -5,7 +5,6 @@ import io.anuke.mindustry.type.Liquid; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.LiquidBlock; import io.anuke.mindustry.world.consumers.ConsumeLiquid; -import io.anuke.mindustry.world.consumers.Uses; import io.anuke.mindustry.world.meta.BlockStat; import io.anuke.ucore.core.Timers; @@ -37,7 +36,7 @@ public class LiquidMixer extends LiquidBlock{ LiquidMixerEntity entity = tile.entity(); if(tile.entity.cons.valid()){ - float use = Math.min(consumes.get(Uses.liquid).used() * Timers.delta(), liquidCapacity - entity.liquids.get(outputLiquid)); + float use = Math.min(consumes.get(ConsumeLiquid.class).used() * Timers.delta(), liquidCapacity - entity.liquids.get(outputLiquid)); entity.accumulator += use; entity.liquids.add(outputLiquid, use); for (int i = 0; i < (int)(entity.accumulator / liquidPerItem); i++) { diff --git a/core/src/io/anuke/mindustry/world/blocks/production/PowerCrafter.java b/core/src/io/anuke/mindustry/world/blocks/production/PowerCrafter.java index f2cbe29263..bda67dc758 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/PowerCrafter.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/PowerCrafter.java @@ -2,27 +2,21 @@ package io.anuke.mindustry.world.blocks.production; import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.type.Item; -import io.anuke.mindustry.type.ItemStack; import io.anuke.mindustry.type.Liquid; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.production.GenericCrafter.GenericCrafterEntity; import io.anuke.mindustry.world.meta.BlockStat; -import io.anuke.mindustry.world.meta.StatUnit; import io.anuke.ucore.core.Timers; -import io.anuke.ucore.util.Mathf; public class PowerCrafter extends Block{ protected final int timerDump = timers++; - /**Required.*/ - protected ItemStack input; /**Optional.*/ protected Item outputItem; /**Optional. Set hasLiquids to true when using.*/ protected Liquid outputLiquid; protected float outputLiquidAmount; - protected float powerUse; protected float craftTime; public PowerCrafter(String name) { @@ -37,8 +31,6 @@ public class PowerCrafter extends Block{ public void setStats() { super.setStats(); - stats.add(BlockStat.inputItem, input); - if(outputItem != null){ stats.add(BlockStat.outputItem, outputItem); } @@ -46,26 +38,19 @@ public class PowerCrafter extends Block{ if(outputLiquid != null){ stats.add(BlockStat.liquidOutput, outputLiquid); } - - if(hasPower){ - stats.add(BlockStat.powerUse, 60f * powerUse, StatUnit.powerSecond); - } } @Override public void update(Tile tile) { GenericCrafterEntity entity = tile.entity(); - float powerUsed = Math.min(Timers.delta() * powerUse, tile.entity.power.amount); - int itemsUsed = Mathf.ceil(1 + input.amount * entity.progress); - - if(entity.power.amount > powerUsed && entity.items.has(input.item, itemsUsed)){ + if(entity.cons.valid()){ entity.progress += 1f/craftTime; entity.totalProgress += Timers.delta(); } if(entity.progress >= 1f){ - entity.items.remove(input); + entity.items.remove(consumes.item(), consumes.itemAmount()); if(outputItem != null) offloadNear(tile, outputItem); if(outputLiquid != null) handleLiquid(tile, tile, outputLiquid, outputLiquidAmount); entity.progress = 0f; @@ -80,11 +65,6 @@ public class PowerCrafter extends Block{ } } - @Override - public boolean acceptItem(Item item, Tile tile, Tile source) { - return item == input.item && tile.entity.items.get(input.item) < itemCapacity; - } - @Override public TileEntity getEntity() { return new GenericCrafterEntity(); diff --git a/core/src/io/anuke/mindustry/world/blocks/production/PowerSmelter.java b/core/src/io/anuke/mindustry/world/blocks/production/PowerSmelter.java index dcc147c953..a2949393c2 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/PowerSmelter.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/PowerSmelter.java @@ -12,7 +12,6 @@ import io.anuke.mindustry.world.blocks.PowerBlock; import io.anuke.mindustry.world.meta.BlockBar; import io.anuke.mindustry.world.meta.BlockStat; import io.anuke.mindustry.world.meta.StatUnit; -import io.anuke.mindustry.world.meta.values.ItemListValue; import io.anuke.ucore.core.Effects; import io.anuke.ucore.core.Effects.Effect; import io.anuke.ucore.core.Timers; @@ -28,12 +27,7 @@ public class PowerSmelter extends PowerBlock { protected final int timerDump = timers++; protected final int timerCraft = timers++; - /**Recipe format: - * First item in each array: result - * Everything else in each array: requirements. Can have duplicates.*/ - protected ItemStack[] inputs; protected Item result; - protected float powerUse; protected float minFlux = 0.2f; protected int fluxNeeded = 1; @@ -70,7 +64,7 @@ public class PowerSmelter extends PowerBlock { super.setBars(); bars.remove(BarType.inventory); - for(ItemStack item : inputs){ + for(ItemStack item : consumes.items()){ bars.add(new BlockBar(BarType.inventory, true, tile -> (float) tile.entity.items.get(item.item) / itemCapacity)); } } @@ -78,10 +72,7 @@ public class PowerSmelter extends PowerBlock { @Override public void setStats(){ super.setStats(); - //TODO input/outputs - stats.add(BlockStat.inputItems, new ItemListValue(inputs)); - stats.add(BlockStat.powerUse, powerUse * 60f, StatUnit.powerSecond); stats.add(BlockStat.outputItem, result); stats.add(BlockStat.craftSpeed, 60f/craftTime, StatUnit.itemsSecond); stats.add(BlockStat.inputItemCapacity, itemCapacity, StatUnit.items); @@ -97,11 +88,8 @@ public class PowerSmelter extends PowerBlock { tryDump(tile, result); } - float used = powerUse * Timers.delta(); - //heat it up if there's enough power - if(entity.power.amount > used){ - entity.power.amount -= used; + if(entity.cons.valid()){ entity.heat += 1f / heatUpTime; if(Mathf.chance(Timers.delta() * burnEffectChance)) Effects.effect(burnEffect, entity.x + Mathf.range(size*4f), entity.y + Mathf.range(size*4)); @@ -112,11 +100,8 @@ public class PowerSmelter extends PowerBlock { entity.heat = Mathf.clamp(entity.heat); entity.time += entity.heat * Timers.delta(); - //make sure it has all the items - for(ItemStack item : inputs){ - if(!entity.items.has(item.item, item.amount)){ - return; - } + if(!entity.cons.valid()){ + return; } if(entity.items.get(result) >= itemCapacity //output full @@ -141,7 +126,7 @@ public class PowerSmelter extends PowerBlock { } if(consumeInputs) { - for (ItemStack item : inputs) { + for (ItemStack item : consumes.items()) { entity.items.remove(item.item, item.amount); } } @@ -153,7 +138,7 @@ public class PowerSmelter extends PowerBlock { @Override public boolean acceptItem(Item item, Tile tile, Tile source){ - for(ItemStack stack : inputs){ + for(ItemStack stack : consumes.items()){ if(stack.item == item){ return tile.entity.items.get(item) < itemCapacity; } diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Smelter.java b/core/src/io/anuke/mindustry/world/blocks/production/Smelter.java index 5deb96b27b..b28847fb7e 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Smelter.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Smelter.java @@ -8,10 +8,11 @@ import io.anuke.mindustry.type.ItemStack; import io.anuke.mindustry.world.BarType; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.consumers.ConsumeItem; +import io.anuke.mindustry.world.consumers.ConsumeItems; import io.anuke.mindustry.world.meta.BlockBar; import io.anuke.mindustry.world.meta.BlockStat; import io.anuke.mindustry.world.meta.StatUnit; -import io.anuke.mindustry.world.meta.values.ItemListValue; import io.anuke.ucore.core.Effects; import io.anuke.ucore.core.Effects.Effect; import io.anuke.ucore.core.Timers; @@ -22,9 +23,7 @@ import io.anuke.ucore.util.Mathf; public class Smelter extends Block{ protected final int timerDump = timers++; protected final int timerCraft = timers++; - - protected ItemStack[] inputs; - protected Item fuel; + protected Item result; protected float minFlux = 0.2f; @@ -42,11 +41,14 @@ public class Smelter extends Block{ hasItems = true; solid = true; itemCapacity = 20; + + consumes.require(ConsumeItems.class); + consumes.require(ConsumeItem.class); } @Override public void setBars(){ - for(ItemStack item : inputs){ + for(ItemStack item : consumes.items()){ bars.add(new BlockBar(BarType.inventory, true, tile -> (float)tile.entity.items.get(item.item)/itemCapacity)); } } @@ -55,9 +57,9 @@ public class Smelter extends Block{ public void setStats(){ super.setStats(); - stats.add(BlockStat.inputFuel, fuel); + //TODO + //stats.add(BlockStat.inputFuel, fuel); stats.add(BlockStat.fuelBurnTime, burnDuration/60f, StatUnit.seconds); - stats.add(BlockStat.inputItems, new ItemListValue(inputs)); stats.add(BlockStat.outputItem, result); stats.add(BlockStat.craftSpeed, 60f/craftTime, StatUnit.itemsSecond); stats.add(BlockStat.inputItemCapacity, itemCapacity, StatUnit.items); @@ -68,7 +70,7 @@ public class Smelter extends Block{ public void init() { super.init(); - for(ItemStack item : inputs){ + for(ItemStack item : consumes.items()){ if(item.item.fluxiness >= minFlux && useFlux){ throw new IllegalArgumentException("'" + name + "' has input item '" + item.item.name + "', which is a flux, when useFlux is enabled. To prevent ambiguous item use, either remove this flux item from the inputs, or set useFlux to false."); } @@ -84,8 +86,8 @@ public class Smelter extends Block{ } //add fuel - if(entity.items.get(fuel) > 0 && entity.burnTime <= 0f){ - entity.items.remove(fuel, 1); + if(entity.consumed(ConsumeItem.class) && entity.burnTime <= 0f){ + entity.items.remove(consumes.item(), 1); entity.burnTime += burnDuration; Effects.effect(burnEffect, entity.x + Mathf.range(2f), entity.y + Mathf.range(2f)); } @@ -99,10 +101,8 @@ public class Smelter extends Block{ } //make sure it has all the items - for(ItemStack item : inputs){ - if(!entity.items.has(item.item, item.amount)){ - return; - } + if(!entity.cons.valid()){ + return; } if(entity.items.get(result) >= itemCapacity //output full @@ -127,7 +127,7 @@ public class Smelter extends Block{ } if(consumeInputs) { - for (ItemStack item : inputs) { + for (ItemStack item : consumes.items()) { entity.items.remove(item.item, item.amount); } } @@ -145,14 +145,14 @@ public class Smelter extends Block{ public boolean acceptItem(Item item, Tile tile, Tile source){ boolean isInput = false; - for(ItemStack req : inputs){ + for(ItemStack req : consumes.items()){ if(req.item == item){ isInput = true; break; } } - return (isInput && tile.entity.items.get(item) < itemCapacity) || (item == fuel && tile.entity.items.get(fuel) < itemCapacity) || + return (isInput && tile.entity.items.get(item) < itemCapacity) || (item == consumes.item() && tile.entity.items.get(consumes.item()) < itemCapacity) || (useFlux && item.fluxiness >= minFlux && tile.entity.items.get(item) < itemCapacity); } diff --git a/core/src/io/anuke/mindustry/world/blocks/units/Projector.java b/core/src/io/anuke/mindustry/world/blocks/units/Projector.java index 5dd6c7c3e5..55ddcd1e78 100644 --- a/core/src/io/anuke/mindustry/world/blocks/units/Projector.java +++ b/core/src/io/anuke/mindustry/world/blocks/units/Projector.java @@ -15,7 +15,6 @@ public abstract class Projector extends Block { protected final int timerApply = timers++; protected final float applyTime = 4f; - protected float powerUse = 0.01f; protected float range = 80f; protected StatusEffect status; @@ -39,11 +38,8 @@ public abstract class Projector extends Block { public void update(Tile tile) { ProjectorEntity entity = tile.entity(); - float used = Math.min(powerCapacity, powerUse * Timers.delta()); - - if(entity.power.amount >= used){ + if(entity.cons.valid()){ entity.heat = Mathf.lerpDelta(entity.heat, 1f, 0.01f); - entity.power.amount -= used; }else{ entity.heat = Mathf.lerpDelta(entity.heat, 0f, 0.01f); } diff --git a/core/src/io/anuke/mindustry/world/blocks/units/UnitFactory.java b/core/src/io/anuke/mindustry/world/blocks/units/UnitFactory.java index 3015c88970..a68d7562b5 100644 --- a/core/src/io/anuke/mindustry/world/blocks/units/UnitFactory.java +++ b/core/src/io/anuke/mindustry/world/blocks/units/UnitFactory.java @@ -19,10 +19,10 @@ import io.anuke.mindustry.type.ItemStack; import io.anuke.mindustry.world.BarType; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.consumers.ConsumeItems; import io.anuke.mindustry.world.meta.BlockBar; import io.anuke.mindustry.world.meta.BlockStat; import io.anuke.mindustry.world.meta.StatUnit; -import io.anuke.mindustry.world.meta.values.ItemListValue; import io.anuke.mindustry.world.modules.InventoryModule; import io.anuke.ucore.core.Effects; import io.anuke.ucore.core.Graphics; @@ -37,9 +37,7 @@ import java.io.IOException; public class UnitFactory extends Block { protected UnitType type; - protected ItemStack[] requirements; protected float produceTime = 1000f; - protected float powerUse = 0.1f; protected float openDuration = 50f; protected float launchVelocity = 0f; protected String unitRegion; @@ -50,14 +48,14 @@ public class UnitFactory extends Block { hasPower = true; hasItems = true; solidifes = true; + + consumes.require(ConsumeItems.class); } @Override public void setStats() { super.setStats(); - stats.add(BlockStat.inputItems, new ItemListValue(requirements)); - stats.add(BlockStat.powerUse, powerUse * 60f, StatUnit.powerSecond); stats.add(BlockStat.craftSpeed, produceTime/60f, StatUnit.seconds); } @@ -119,8 +117,6 @@ public class UnitFactory extends Block { public void update(Tile tile) { UnitFactoryEntity entity = tile.entity(); - float used = Math.min(powerUse * Timers.delta(), powerCapacity); - entity.time += Timers.delta() * entity.speedScl; if(entity.openCountdown > 0){ @@ -148,10 +144,9 @@ public class UnitFactory extends Block { }*/ if(!entity.hasSpawned && hasRequirements(entity.items, entity.buildTime/produceTime) && - entity.power.amount >= used && !entity.open){ + entity.cons.valid() && !entity.open){ entity.buildTime += Timers.delta(); - entity.power.amount -= used; entity.speedScl = Mathf.lerpDelta(entity.speedScl, 1f, 0.05f); }else{ if(!entity.open) entity.speedScl = Mathf.lerpDelta(entity.speedScl, 0f, 0.05f); @@ -164,7 +159,7 @@ public class UnitFactory extends Block { entity.openCountdown = openDuration; - for(ItemStack stack : requirements){ + for(ItemStack stack : consumes.items()){ entity.items.remove(stack.item, stack.amount); } } @@ -172,7 +167,7 @@ public class UnitFactory extends Block { @Override public boolean acceptItem(Item item, Tile tile, Tile source) { - for(ItemStack stack : requirements){ + for(ItemStack stack : consumes.items()){ if(item == stack.item && tile.entity.items.get(item) <= stack.amount*2){ return true; } @@ -186,7 +181,7 @@ public class UnitFactory extends Block { } protected boolean hasRequirements(InventoryModule inv, float fraction){ - for(ItemStack stack : requirements){ + for(ItemStack stack : consumes.items()){ if(!inv.has(stack.item, (int)(fraction * stack.amount))){ return false; } diff --git a/core/src/io/anuke/mindustry/world/consumers/Consume.java b/core/src/io/anuke/mindustry/world/consumers/Consume.java index f920ebf888..8a43c31e55 100644 --- a/core/src/io/anuke/mindustry/world/consumers/Consume.java +++ b/core/src/io/anuke/mindustry/world/consumers/Consume.java @@ -4,26 +4,29 @@ import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.meta.BlockStats; -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; - public abstract class Consume { private boolean optional; + private boolean update; - public void optional(boolean optional) { + public Consume optional(boolean optional) { this.optional = optional; + return this; + } + + public Consume update(boolean update){ + this.update = update; + return this; } public boolean isOptional() { return optional; } + public boolean isUpdate() { + return update; + } + public abstract void update(Block block, TileEntity entity); public abstract boolean valid(Block block, TileEntity entity); public abstract void display(BlockStats stats); - - public Consume copy(){ return this; } - public void write(DataOutput stream) throws IOException{} - public void read(DataInput stream) throws IOException{} } diff --git a/core/src/io/anuke/mindustry/world/consumers/ConsumeItem.java b/core/src/io/anuke/mindustry/world/consumers/ConsumeItem.java index e1d3db4e64..b618c7b7ab 100644 --- a/core/src/io/anuke/mindustry/world/consumers/ConsumeItem.java +++ b/core/src/io/anuke/mindustry/world/consumers/ConsumeItem.java @@ -8,9 +8,20 @@ import io.anuke.mindustry.world.meta.BlockStats; public class ConsumeItem extends Consume { private final Item item; + private final int amount; public ConsumeItem(Item item) { this.item = item; + this.amount = 1; + } + + public ConsumeItem(Item item, int amount) { + this.item = item; + this.amount = amount; + } + + public int getAmount() { + return amount; } public Item get() { @@ -24,7 +35,7 @@ public class ConsumeItem extends Consume { @Override public boolean valid(Block block, TileEntity entity) { - return entity.items.has(item); + return entity.items.has(item, amount); } @Override diff --git a/core/src/io/anuke/mindustry/world/consumers/ConsumeItemFilter.java b/core/src/io/anuke/mindustry/world/consumers/ConsumeItemFilter.java new file mode 100644 index 0000000000..44e9cc2b08 --- /dev/null +++ b/core/src/io/anuke/mindustry/world/consumers/ConsumeItemFilter.java @@ -0,0 +1,38 @@ +package io.anuke.mindustry.world.consumers; + +import io.anuke.mindustry.entities.TileEntity; +import io.anuke.mindustry.type.Item; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.meta.BlockStat; +import io.anuke.mindustry.world.meta.BlockStats; +import io.anuke.mindustry.world.meta.values.ItemFilterValue; +import io.anuke.ucore.function.Predicate; + +public class ConsumeItemFilter extends Consume{ + private final Predicate item; + + public ConsumeItemFilter(Predicate item) { + this.item = item; + } + + @Override + public void update(Block block, TileEntity entity) { + + } + + @Override + public boolean valid(Block block, TileEntity entity) { + for(int i = 0; i < Item.all().size; i ++){ + Item item = Item.getByID(i); + if(entity.items.has(item) && this.item.test(item)){ + return true; + } + } + return false; + } + + @Override + public void display(BlockStats stats) { + stats.add(BlockStat.inputItems, new ItemFilterValue(item)); + } +} diff --git a/core/src/io/anuke/mindustry/world/consumers/ConsumeItems.java b/core/src/io/anuke/mindustry/world/consumers/ConsumeItems.java new file mode 100644 index 0000000000..f662fcfebb --- /dev/null +++ b/core/src/io/anuke/mindustry/world/consumers/ConsumeItems.java @@ -0,0 +1,37 @@ +package io.anuke.mindustry.world.consumers; + +import io.anuke.mindustry.entities.TileEntity; +import io.anuke.mindustry.type.ItemStack; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.meta.BlockStat; +import io.anuke.mindustry.world.meta.BlockStats; +import io.anuke.mindustry.world.meta.values.ItemListValue; + +public class ConsumeItems extends Consume { + private ItemStack[] items; + + public ConsumeItems(ItemStack[] items) { + this.items = items; + } + + public ItemStack[] getItems() { + return items; + } + + @Override + public void update(Block block, TileEntity entity) { + for(ItemStack stack : items){ + entity.items.remove(stack); + } + } + + @Override + public boolean valid(Block block, TileEntity entity) { + return entity.items.has(items); + } + + @Override + public void display(BlockStats stats) { + stats.add(BlockStat.inputItems, new ItemListValue(items)); + } +} diff --git a/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquid.java b/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquid.java index 2fa7e36404..1f82439c5d 100644 --- a/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquid.java +++ b/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquid.java @@ -6,6 +6,7 @@ import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.meta.BlockStat; import io.anuke.mindustry.world.meta.BlockStats; import io.anuke.mindustry.world.meta.StatUnit; +import io.anuke.ucore.core.Timers; public class ConsumeLiquid extends Consume { private final float use; @@ -41,6 +42,6 @@ public class ConsumeLiquid extends Consume { } float use(Block block) { - return Math.min(use, block.liquidCapacity); + return Math.min(use * Timers.delta(), block.liquidCapacity); } } diff --git a/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquidFilter.java b/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquidFilter.java new file mode 100644 index 0000000000..5f3fd5dfc4 --- /dev/null +++ b/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquidFilter.java @@ -0,0 +1,41 @@ +package io.anuke.mindustry.world.consumers; + +import io.anuke.mindustry.entities.TileEntity; +import io.anuke.mindustry.type.Liquid; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.meta.BlockStat; +import io.anuke.mindustry.world.meta.BlockStats; +import io.anuke.mindustry.world.meta.StatUnit; +import io.anuke.mindustry.world.meta.values.LiquidFilterValue; +import io.anuke.ucore.core.Timers; +import io.anuke.ucore.function.Predicate; + +public class ConsumeLiquidFilter extends Consume{ + private final Predicate liquid; + private final float use; + + public ConsumeLiquidFilter(Predicate liquid, float amount) { + this.liquid = liquid; + this.use = amount; + } + + @Override + public void update(Block block, TileEntity entity) { + entity.liquids.remove(entity.liquids.current(), use(block)); + } + + @Override + public boolean valid(Block block, TileEntity entity) { + return liquid.test(entity.liquids.current()) && entity.liquids.currentAmount() >= use(block); + } + + @Override + public void display(BlockStats stats) { + stats.add(BlockStat.inputLiquid, new LiquidFilterValue(liquid)); + stats.add(BlockStat.liquidUse, 60f * use, StatUnit.liquidSecond); + } + + float use(Block block) { + return Math.min(use * Timers.delta(), block.liquidCapacity); + } +} diff --git a/core/src/io/anuke/mindustry/world/consumers/Consumers.java b/core/src/io/anuke/mindustry/world/consumers/Consumers.java index 02590d85c4..552b41bce3 100644 --- a/core/src/io/anuke/mindustry/world/consumers/Consumers.java +++ b/core/src/io/anuke/mindustry/world/consumers/Consumers.java @@ -1,71 +1,101 @@ package io.anuke.mindustry.world.consumers; -import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.ObjectMap; +import com.badlogic.gdx.utils.ObjectSet; +import com.badlogic.gdx.utils.reflect.ClassReflection; import io.anuke.mindustry.type.Item; +import io.anuke.mindustry.type.ItemStack; import io.anuke.mindustry.type.Liquid; +import io.anuke.mindustry.world.Block; import io.anuke.ucore.function.Consumer; public class Consumers { - private Consume[] consumeMap = new Consume[Uses.values().length]; + private ObjectMap, Consume> map = new ObjectMap<>(); + private ObjectSet> required = new ObjectSet<>(); + + public void require(Class type){ + required.add(type); + } + + public void checkRequired(Block block){ + for (Class c : required){ + if(!map.containsKey(c)){ + throw new RuntimeException("Missing required consumer of type \"" + ClassReflection.getSimpleName(c) + "\" in block \"" + block.name + "\"!"); + } + } + } public ConsumePower power(float amount){ ConsumePower p = new ConsumePower(amount); - add(Uses.power, p); + add(p); return p; } public ConsumeLiquid liquid(Liquid liquid, float amount){ ConsumeLiquid c = new ConsumeLiquid(liquid, amount); - add(Uses.liquid, c); + add(c); return c; } public ConsumeItem item(Item item){ - ConsumeItem i = new ConsumeItem(item); - add(Uses.items, i); + return item(item, 1); + } + + public ConsumeItem item(Item item, int amount){ + ConsumeItem i = new ConsumeItem(item, amount); + add(i); + return i; + } + + public ConsumeItems items(ItemStack[] items){ + ConsumeItems i = new ConsumeItems(items); + add(i); return i; } public Item item(){ - return this.get(Uses.items).get(); + return get(ConsumeItem.class).get(); + } + + public ItemStack[] items(){ + return get(ConsumeItems.class).getItems(); + } + + public int itemAmount(){ + return get(ConsumeItem.class).getAmount(); } public Liquid liquid(){ - return this.get(Uses.liquid).get(); + return get(ConsumeLiquid.class).get(); } - public void add(Uses type, Consume consume){ - consumeMap[type.ordinal()] = consume; + public Consume add(Consume consume){ + map.put(consume.getClass(), consume); + return consume; } - public void remove(Uses type){ - consumeMap[type.ordinal()] = null; + public void remove(Class type){ + map.remove(type); } - public boolean has(Uses type){ - return consumeMap[type.ordinal()] != null; + public boolean has(Class type){ + return map.containsKey(type); } - public T get(Uses type){ - if(consumeMap[type.ordinal()] == null){ + public T get(Class type){ + if(!map.containsKey(type)){ throw new IllegalArgumentException("Block does not contain consumer of type '" + type + "'!"); } - return (T)consumeMap[type.ordinal()]; + return (T)map.get(type); + } + + public Iterable all() { + return map.values(); } public void forEach(Consumer cons){ - for (Consume c : consumeMap) { - if (c != null) { - cons.accept(c); - } - } - } - - public void addAll(Array result){ - for (Consume c : consumeMap) { - if (c != null) { - result.add(c.copy()); - } + for(Consume c : all()){ + cons.accept(c); } } } diff --git a/core/src/io/anuke/mindustry/world/consumers/Uses.java b/core/src/io/anuke/mindustry/world/consumers/Uses.java deleted file mode 100644 index c1bb49632a..0000000000 --- a/core/src/io/anuke/mindustry/world/consumers/Uses.java +++ /dev/null @@ -1,7 +0,0 @@ -package io.anuke.mindustry.world.consumers; - -public enum Uses { - power, - liquid, - items, -} diff --git a/core/src/io/anuke/mindustry/world/meta/BlockStat.java b/core/src/io/anuke/mindustry/world/meta/BlockStat.java index 4279f272c9..1e18967fbf 100644 --- a/core/src/io/anuke/mindustry/world/meta/BlockStat.java +++ b/core/src/io/anuke/mindustry/world/meta/BlockStat.java @@ -22,7 +22,6 @@ public enum BlockStat { maxPowerGeneration(StatCategory.power), inputLiquid(StatCategory.crafting), - inputLiquidAux(StatCategory.crafting), liquidUse(StatCategory.crafting), inputItem(StatCategory.crafting), inputItems(StatCategory.crafting), diff --git a/core/src/io/anuke/mindustry/world/meta/BlockStats.java b/core/src/io/anuke/mindustry/world/meta/BlockStats.java index 112f431211..e697c0255f 100644 --- a/core/src/io/anuke/mindustry/world/meta/BlockStats.java +++ b/core/src/io/anuke/mindustry/world/meta/BlockStats.java @@ -5,6 +5,7 @@ import com.badlogic.gdx.utils.OrderedMap; import io.anuke.mindustry.type.Item; import io.anuke.mindustry.type.ItemStack; import io.anuke.mindustry.type.Liquid; +import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.meta.values.*; import io.anuke.ucore.util.Bundles; import io.anuke.ucore.util.Log; @@ -13,9 +14,14 @@ import io.anuke.ucore.util.Log; public class BlockStats { private static final boolean errorWhenMissing = true; - private OrderedMap> map = new OrderedMap<>(); + private final OrderedMap> map = new OrderedMap<>(); + private final Block block; private boolean dirty; + public BlockStats(Block block) { + this.block = block; + } + /**Adds a single float value with this stat, formatted to 2 decimal places.*/ public void add(BlockStat stat, float value, StatUnit unit){ add(stat, new NumberValue(value, unit)); @@ -66,7 +72,7 @@ public class BlockStats { } if(map.containsKey(stat.category) && map.get(stat.category).containsKey(stat)){ - throw new RuntimeException("Duplicate stat entry: \"" +stat + "\""); + throw new RuntimeException("Duplicate stat entry: \"" +stat + "\" in block '" + block.name + "'"); } if(!map.containsKey(stat.category)){ @@ -80,7 +86,7 @@ public class BlockStats { public void remove(BlockStat stat){ if(!map.containsKey(stat.category) || !map.get(stat.category).containsKey(stat)){ - throw new RuntimeException("No stat entry found: \"" + stat + "\"!"); + throw new RuntimeException("No stat entry found: \"" + stat + "\" in block '" + block.name + "'!"); } map.get(stat.category).remove(stat); diff --git a/core/src/io/anuke/mindustry/world/modules/ConsumeModule.java b/core/src/io/anuke/mindustry/world/modules/ConsumeModule.java index 39bf00dc5c..c33e556ee8 100644 --- a/core/src/io/anuke/mindustry/world/modules/ConsumeModule.java +++ b/core/src/io/anuke/mindustry/world/modules/ConsumeModule.java @@ -1,6 +1,5 @@ package io.anuke.mindustry.world.modules; -import com.badlogic.gdx.utils.Array; import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.world.consumers.Consume; @@ -9,15 +8,14 @@ import java.io.DataOutput; import java.io.IOException; public class ConsumeModule extends BlockModule{ - private Array consumers = new Array<>(); private boolean valid; public void update(TileEntity entity){ boolean prevValid = valid; valid = true; - for(Consume cons : consumers){ - if(prevValid && entity.tile.block().shouldConsume(entity.tile)){ + for(Consume cons : entity.tile.block().consumes.all()){ + if(cons.isUpdate() && prevValid && entity.tile.block().shouldConsume(entity.tile) && cons.valid(entity.getTile().block(), entity)){ cons.update(entity.getTile().block(), entity); } @@ -31,21 +29,13 @@ public class ConsumeModule extends BlockModule{ return valid; } - public Array all() { - return consumers; - } - @Override public void write(DataOutput stream) throws IOException { - for(Consume cons : consumers){ - cons.write(stream); - } + stream.writeBoolean(valid); } @Override public void read(DataInput stream) throws IOException { - for(Consume cons : consumers){ - cons.read(stream); - } + valid = stream.readBoolean(); } } diff --git a/core/src/io/anuke/mindustry/world/modules/LiquidModule.java b/core/src/io/anuke/mindustry/world/modules/LiquidModule.java index 564bed0512..42358a1ca5 100644 --- a/core/src/io/anuke/mindustry/world/modules/LiquidModule.java +++ b/core/src/io/anuke/mindustry/world/modules/LiquidModule.java @@ -21,6 +21,10 @@ public class LiquidModule extends BlockModule { return current; } + public float currentAmount(){ + return liquids[current.id]; + } + public float get(Liquid liquid){ return liquids[liquid.id]; } From a2a0c2f791cd86abb7aa7499327ff76a3704f58f Mon Sep 17 00:00:00 2001 From: Anuken Date: Sun, 8 Jul 2018 16:50:52 -0400 Subject: [PATCH 10/47] Fixed startup error --- core/assets/bundles/bundle.properties | 4 ++++ .../blocks/power/ItemLiquidGenerator.java | 2 +- .../world/blocks/production/Drill.java | 9 ++++++++- .../world/consumers/ConsumeLiquid.java | 4 ++-- .../world/consumers/ConsumeLiquidFilter.java | 19 +++++++++++++++---- .../anuke/mindustry/world/meta/BlockStat.java | 4 ++++ 6 files changed, 34 insertions(+), 8 deletions(-) diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index b8a2fb2501..de455f1004 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -297,6 +297,10 @@ text.blocks.drilltier=Drillables text.blocks.drillspeed=Base Drill Speed text.blocks.liquidoutput=Liquid Output text.blocks.liquiduse=Liquid Use +text.blocks.coolant=Coolant +text.blocks.coolantuse=Coolant Use +text.blocks.inputliquidfuel=Fuel Liquid +text.blocks.liquidfueluse=Liquid Fuel Use text.blocks.explosive=Highly explosive! text.blocks.health=Health text.blocks.inaccuracy=Inaccuracy diff --git a/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java b/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java index 5b9d3f32d1..cb290d8f25 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java @@ -23,7 +23,7 @@ public abstract class ItemLiquidGenerator extends ItemGenerator { hasLiquids = true; liquidCapacity = 10f; - consumes.add(new ConsumeLiquidFilter(liquid -> getLiquidEfficiency(liquid) >= minLiquidEfficiency, 0.001f)).update(false); + consumes.add(new ConsumeLiquidFilter(liquid -> getLiquidEfficiency(liquid) >= minLiquidEfficiency, 0.001f, true)).update(false); } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Drill.java b/core/src/io/anuke/mindustry/world/blocks/production/Drill.java index 28aaffbefb..87445d4762 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Drill.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Drill.java @@ -13,6 +13,7 @@ import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.consumers.ConsumeLiquid; import io.anuke.mindustry.world.meta.BlockGroup; import io.anuke.mindustry.world.meta.BlockStat; +import io.anuke.mindustry.world.meta.BlockStats; import io.anuke.mindustry.world.meta.StatUnit; import io.anuke.ucore.core.Effects; import io.anuke.ucore.core.Effects.Effect; @@ -69,7 +70,13 @@ public class Drill extends Block{ liquidCapacity = 5f; hasItems = true; - consumes.liquid(Liquids.water, 0.01f).optional(true); + consumes.add(new ConsumeLiquid(Liquids.water, 0.05f){ + @Override + public void display(BlockStats stats) { + stats.add(BlockStat.coolantUse, use * 60f, StatUnit.liquidSecond); + stats.add(BlockStat.coolant, liquid); + } + }).optional(true); } @Override diff --git a/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquid.java b/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquid.java index 1f82439c5d..eb8ac197e9 100644 --- a/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquid.java +++ b/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquid.java @@ -9,8 +9,8 @@ import io.anuke.mindustry.world.meta.StatUnit; import io.anuke.ucore.core.Timers; public class ConsumeLiquid extends Consume { - private final float use; - private final Liquid liquid; + protected final float use; + protected final Liquid liquid; public ConsumeLiquid(Liquid liquid, float use) { this.liquid = liquid; diff --git a/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquidFilter.java b/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquidFilter.java index 5f3fd5dfc4..65c1ccb557 100644 --- a/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquidFilter.java +++ b/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquidFilter.java @@ -13,13 +13,19 @@ import io.anuke.ucore.function.Predicate; public class ConsumeLiquidFilter extends Consume{ private final Predicate liquid; private final float use; + private final boolean isFuel; - public ConsumeLiquidFilter(Predicate liquid, float amount) { + public ConsumeLiquidFilter(Predicate liquid, float amount, boolean isFuel) { this.liquid = liquid; this.use = amount; + this.isFuel = isFuel; } - @Override + public ConsumeLiquidFilter(Predicate liquid, float amount){ + this(liquid, amount, false); + } + + @Override public void update(Block block, TileEntity entity) { entity.liquids.remove(entity.liquids.current(), use(block)); } @@ -31,8 +37,13 @@ public class ConsumeLiquidFilter extends Consume{ @Override public void display(BlockStats stats) { - stats.add(BlockStat.inputLiquid, new LiquidFilterValue(liquid)); - stats.add(BlockStat.liquidUse, 60f * use, StatUnit.liquidSecond); + if(isFuel){ + stats.add(BlockStat.inputLiquidFuel, new LiquidFilterValue(liquid)); + stats.add(BlockStat.liquidFuelUse, 60f * use, StatUnit.liquidSecond); + }else { + stats.add(BlockStat.inputLiquid, new LiquidFilterValue(liquid)); + stats.add(BlockStat.liquidUse, 60f * use, StatUnit.liquidSecond); + } } float use(Block block) { diff --git a/core/src/io/anuke/mindustry/world/meta/BlockStat.java b/core/src/io/anuke/mindustry/world/meta/BlockStat.java index 1e18967fbf..03e715abdb 100644 --- a/core/src/io/anuke/mindustry/world/meta/BlockStat.java +++ b/core/src/io/anuke/mindustry/world/meta/BlockStat.java @@ -14,12 +14,16 @@ public enum BlockStat { liquidCapacity(StatCategory.liquids), liquidOutput(StatCategory.liquids), + coolant(StatCategory.liquids), + coolantUse(StatCategory.liquids), powerCapacity(StatCategory.power), powerUse(StatCategory.power), powerRange(StatCategory.power), powerTransferSpeed(StatCategory.power), maxPowerGeneration(StatCategory.power), + inputLiquidFuel(StatCategory.power), + liquidFuelUse(StatCategory.power), inputLiquid(StatCategory.crafting), liquidUse(StatCategory.crafting), From a0e94577fcf5edf8b3f178c6e9854e1c64878050 Mon Sep 17 00:00:00 2001 From: Anuken Date: Sun, 8 Jul 2018 17:04:32 -0400 Subject: [PATCH 11/47] Fixed infinite red square, glitchy shadows --- .../{multiplexer.png => distributor.png} | Bin core/assets/sprites/sprites.atlas | 535 +++++++++--------- core/assets/sprites/sprites.png | Bin 134410 -> 134646 bytes .../anuke/mindustry/entities/TileEntity.java | 4 +- .../entities/traits/BuilderTrait.java | 11 +- .../mindustry/entities/units/types/Drone.java | 5 + core/src/io/anuke/mindustry/world/Tile.java | 4 +- .../mindustry/world/blocks/BuildBlock.java | 4 +- .../io/anuke/mindustry/world/blocks/Rock.java | 2 +- .../mindustry/world/consumers/Consumers.java | 4 + 10 files changed, 283 insertions(+), 286 deletions(-) rename core/assets-raw/sprites/blocks/distribution/{multiplexer.png => distributor.png} (100%) diff --git a/core/assets-raw/sprites/blocks/distribution/multiplexer.png b/core/assets-raw/sprites/blocks/distribution/distributor.png similarity index 100% rename from core/assets-raw/sprites/blocks/distribution/multiplexer.png rename to core/assets-raw/sprites/blocks/distribution/distributor.png diff --git a/core/assets/sprites/sprites.atlas b/core/assets/sprites/sprites.atlas index 84ce8e37db..6ca44197c3 100644 --- a/core/assets/sprites/sprites.atlas +++ b/core/assets/sprites/sprites.atlas @@ -13,42 +13,49 @@ background index: -1 bridge-conveyor-arrow rotate: false - xy: 333, 15 + xy: 333, 25 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 bridge-conveyor-bridge rotate: false - xy: 343, 25 + xy: 323, 5 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 bridge-conveyor-end rotate: false - xy: 333, 5 + xy: 333, 15 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 phase-conveyor-arrow rotate: false - xy: 597, 128 + xy: 587, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 phase-conveyor-bridge rotate: false - xy: 587, 108 + xy: 597, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 phase-conveyor-end rotate: false - xy: 597, 118 + xy: 587, 108 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +router + rotate: false + xy: 617, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -90,21 +97,21 @@ blast-drill-top index: -1 carbide-drill rotate: false - xy: 343, 15 + xy: 343, 25 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 carbide-drill-rotator rotate: false - xy: 353, 25 + xy: 333, 5 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 carbide-drill-top rotate: false - xy: 343, 5 + xy: 343, 15 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -293,35 +300,35 @@ blackstoneedge index: -1 coal1 rotate: false - xy: 353, 15 + xy: 353, 25 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 coal2 rotate: false - xy: 363, 25 + xy: 343, 5 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 coal3 rotate: false - xy: 353, 5 + xy: 353, 15 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 dirt2 rotate: false - xy: 383, 25 + xy: 363, 5 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 dirt3 rotate: false - xy: 373, 5 + xy: 373, 15 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -335,42 +342,42 @@ dirtedge index: -1 grass-cliff-edge rotate: false - xy: 403, 25 + xy: 393, 15 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 grass-cliff-edge-1 rotate: false - xy: 403, 15 + xy: 393, 5 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 grass-cliff-edge-2 rotate: false - xy: 403, 5 + xy: 403, 25 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 grass-cliff-side rotate: false - xy: 517, 131 + xy: 403, 15 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 grass2 rotate: false - xy: 393, 15 + xy: 393, 25 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 grass3 rotate: false - xy: 393, 5 + xy: 383, 5 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -384,42 +391,42 @@ grassedge index: -1 ice-cliff-edge rotate: false - xy: 517, 101 + xy: 517, 121 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ice-cliff-edge-1 rotate: false - xy: 967, 357 + xy: 517, 111 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ice-cliff-edge-2 rotate: false - xy: 967, 347 + xy: 517, 101 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ice-cliff-side rotate: false - xy: 977, 357 + xy: 967, 357 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ice2 rotate: false - xy: 517, 121 + xy: 403, 5 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ice3 rotate: false - xy: 517, 111 + xy: 517, 131 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -433,35 +440,35 @@ iceedge index: -1 icerock2 rotate: false - xy: 977, 347 + xy: 967, 347 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 icerockshadow1 rotate: false - xy: 987, 355 + xy: 977, 357 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 rockshadow1 rotate: false - xy: 987, 355 + xy: 977, 357 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 icerockshadow2 rotate: false - xy: 997, 355 + xy: 977, 347 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 rockshadow2 rotate: false - xy: 997, 355 + xy: 977, 347 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -475,56 +482,56 @@ lavaedge index: -1 lead1 rotate: false - xy: 505, 171 + xy: 795, 302 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 lead2 rotate: false - xy: 517, 181 + xy: 507, 181 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 lead3 rotate: false - xy: 527, 181 + xy: 505, 171 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 metalfloor2 rotate: false - xy: 565, 171 + xy: 567, 181 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 metalfloor3 rotate: false - xy: 522, 161 + xy: 555, 171 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 metalfloor4 rotate: false - xy: 532, 161 + xy: 565, 171 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 metalfloor5 rotate: false - xy: 542, 161 + xy: 522, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 metalfloor6 rotate: false - xy: 552, 161 + xy: 532, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -545,7 +552,7 @@ oiledge index: -1 rock2 rotate: false - xy: 617, 108 + xy: 627, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -832,42 +839,42 @@ combustion-generator-top index: -1 block-middle rotate: false - xy: 313, 15 + xy: 313, 25 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 pump-liquid rotate: false - xy: 313, 15 + xy: 313, 25 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-slope rotate: false - xy: 323, 25 + xy: 303, 5 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 border rotate: false - xy: 313, 5 + xy: 313, 15 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 conduit-liquid rotate: false - xy: 373, 25 + xy: 353, 5 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 cross-1 rotate: false - xy: 373, 15 + xy: 373, 25 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -895,7 +902,7 @@ cross-4 index: -1 enemyspawn rotate: false - xy: 383, 5 + xy: 383, 15 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -909,14 +916,14 @@ nuclearreactor-shadow index: -1 place-arrow rotate: false - xy: 607, 128 + xy: 597, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 playerspawn rotate: false - xy: 597, 108 + xy: 607, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -1035,63 +1042,63 @@ shadow-rounded-2 index: -1 bridge-conduit-arrow rotate: false - xy: 323, 15 + xy: 323, 25 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 bridge-conduit-bridge rotate: false - xy: 333, 25 + xy: 313, 5 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 bridge-conduit-end rotate: false - xy: 323, 5 + xy: 323, 15 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 conduit-bottom rotate: false - xy: 363, 15 + xy: 363, 25 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 conduit-top rotate: false - xy: 363, 5 + xy: 363, 15 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 liquid-router rotate: false - xy: 557, 181 + xy: 547, 181 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 liquid-router-bottom rotate: false - xy: 545, 171 + xy: 535, 171 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 liquid-router-liquid rotate: false - xy: 567, 181 + xy: 557, 181 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 liquid-router-top rotate: false - xy: 555, 171 + xy: 545, 171 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -1119,35 +1126,35 @@ liquid-tank-top index: -1 phase-conduit-arrow rotate: false - xy: 587, 128 + xy: 577, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 phase-conduit-bridge rotate: false - xy: 577, 108 + xy: 587, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 phase-conduit-end rotate: false - xy: 587, 118 + xy: 577, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 pulse-conduit-bottom rotate: false - xy: 607, 118 + xy: 597, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 pulse-conduit-top rotate: false - xy: 617, 128 + xy: 607, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -1427,7 +1434,14 @@ cultivator-top index: -1 lavasmelter rotate: false - xy: 507, 181 + xy: 785, 302 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +oilrefinery + rotate: false + xy: 542, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -1469,14 +1483,14 @@ poweralloysmelter-top index: -1 pulverizer rotate: false - xy: 607, 108 + xy: 617, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 pulverizer-rotator rotate: false - xy: 617, 118 + xy: 607, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -1581,7 +1595,7 @@ mass-driver-turret index: -1 duo rotate: false - xy: 393, 25 + xy: 373, 5 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -1784,7 +1798,7 @@ reconstructor-open index: -1 repair-point-turret rotate: false - xy: 627, 128 + xy: 617, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -1805,7 +1819,7 @@ door-large-open index: -1 door-open rotate: false - xy: 383, 15 + xy: 383, 25 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -2279,6 +2293,20 @@ dirt1 orig: 8, 8 offset: 0, 0 index: -1 +block-icon-distributor + rotate: false + xy: 597, 188 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +distributor + rotate: false + xy: 597, 188 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 block-icon-door rotate: false xy: 726, 241 @@ -2295,21 +2323,21 @@ door index: -1 block-icon-door-large rotate: false - xy: 597, 188 + xy: 615, 188 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 door-large rotate: false - xy: 597, 188 + xy: 615, 188 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 block-icon-drone-factory rotate: false - xy: 615, 188 + xy: 633, 188 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -2337,7 +2365,7 @@ block-icon-duo index: -1 block-icon-fabricator-factory rotate: false - xy: 633, 188 + xy: 665, 187 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -2449,14 +2477,14 @@ itemvoid index: -1 block-icon-javelin-ship-factory rotate: false - xy: 665, 187 + xy: 683, 187 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 javelin-ship-factory rotate: false - xy: 665, 187 + xy: 683, 187 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -2484,7 +2512,7 @@ block-icon-lancer index: -1 block-icon-laser-drill rotate: false - xy: 683, 187 + xy: 456, 140 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -2615,20 +2643,6 @@ metalfloor1 orig: 8, 8 offset: 0, 0 index: -1 -block-icon-multiplexer - rotate: false - xy: 456, 140 - size: 16, 16 - orig: 16, 16 - offset: 0, 0 - index: -1 -multiplexer - rotate: false - xy: 456, 140 - size: 16, 16 - orig: 16, 16 - offset: 0, 0 - index: -1 block-icon-nuclear-reactor rotate: false xy: 203, 135 @@ -2664,58 +2678,44 @@ block-icon-oil-extractor orig: 24, 24 offset: 0, 0 index: -1 -block-icon-oilrefinery - rotate: false - xy: 319, 45 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -oilrefinery - rotate: false - xy: 319, 45 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 block-icon-overflow-gate rotate: false - xy: 339, 55 + xy: 319, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 overflow-gate rotate: false - xy: 339, 55 + xy: 319, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-phase-conduit rotate: false - xy: 319, 35 + xy: 339, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 phase-conduit rotate: false - xy: 319, 35 + xy: 339, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-phase-conveyor rotate: false - xy: 329, 45 + xy: 319, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 phase-conveyor rotate: false - xy: 329, 45 + xy: 319, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -2750,14 +2750,14 @@ plastanium-compressor index: -1 block-icon-power-node rotate: false - xy: 349, 55 + xy: 329, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 power-node rotate: false - xy: 349, 55 + xy: 329, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -2778,42 +2778,42 @@ power-node-large index: -1 block-icon-powerinfinite rotate: false - xy: 329, 35 + xy: 349, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 powerinfinite rotate: false - xy: 329, 35 + xy: 349, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-powervoid rotate: false - xy: 339, 45 + xy: 329, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 powervoid rotate: false - xy: 339, 45 + xy: 329, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-pulse-conduit rotate: false - xy: 359, 55 + xy: 339, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-pulverizer rotate: false - xy: 339, 35 + xy: 359, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -2862,28 +2862,28 @@ reconstructor index: -1 block-icon-repair-point rotate: false - xy: 349, 45 + xy: 339, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 repair-point rotate: false - xy: 349, 45 + xy: 339, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-resupply-point rotate: false - xy: 369, 55 + xy: 349, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 resupply-point rotate: false - xy: 369, 55 + xy: 349, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -2897,14 +2897,14 @@ block-icon-ripple index: -1 block-icon-rock rotate: false - xy: 349, 35 + xy: 369, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 rock1 rotate: false - xy: 349, 35 + xy: 369, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -2923,30 +2923,16 @@ rotary-pump orig: 16, 16 offset: 0, 0 index: -1 -block-icon-router - rotate: false - xy: 359, 45 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -router - rotate: false - xy: 359, 45 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 block-icon-rtg-generator rotate: false - xy: 379, 55 + xy: 349, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 rtg-generator rotate: false - xy: 379, 55 + xy: 349, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -2960,14 +2946,14 @@ block-icon-salvo index: -1 block-icon-sand rotate: false - xy: 359, 35 + xy: 359, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 sand1 rotate: false - xy: 359, 35 + xy: 359, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -2981,28 +2967,28 @@ block-icon-scorch index: -1 block-icon-separator rotate: false - xy: 369, 45 + xy: 379, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 separator rotate: false - xy: 369, 45 + xy: 379, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-shrub rotate: false - xy: 389, 55 + xy: 359, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 shrub rotate: false - xy: 389, 55 + xy: 359, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -3023,42 +3009,42 @@ silicon-smelter index: -1 block-icon-smelter rotate: false - xy: 369, 35 + xy: 369, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 smelter rotate: false - xy: 369, 35 + xy: 369, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-snow rotate: false - xy: 379, 45 + xy: 389, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 snow1 rotate: false - xy: 379, 45 + xy: 389, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-solar-panel rotate: false - xy: 379, 35 + xy: 369, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 solar-panel rotate: false - xy: 379, 35 + xy: 369, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -3079,56 +3065,56 @@ solar-panel-large index: -1 block-icon-solidifer rotate: false - xy: 389, 45 + xy: 379, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 solidifer rotate: false - xy: 389, 45 + xy: 379, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-sortedunloader rotate: false - xy: 389, 35 + xy: 379, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 sortedunloader rotate: false - xy: 389, 35 + xy: 379, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-sorter rotate: false - xy: 399, 55 + xy: 389, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 sorter rotate: false - xy: 399, 55 + xy: 389, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-space rotate: false - xy: 399, 45 + xy: 389, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 space rotate: false - xy: 399, 45 + xy: 389, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -3142,28 +3128,28 @@ block-icon-spectre index: -1 block-icon-splitter rotate: false - xy: 399, 35 + xy: 399, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 splitter rotate: false - xy: 399, 35 + xy: 399, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-stone rotate: false - xy: 293, 23 + xy: 399, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 stone1 rotate: false - xy: 293, 23 + xy: 399, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -3205,14 +3191,14 @@ thermal-pump index: -1 block-icon-thorium-wall rotate: false - xy: 293, 13 + xy: 399, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 thorium-wall rotate: false - xy: 293, 13 + xy: 399, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -3233,35 +3219,35 @@ thorium-wall-large index: -1 block-icon-titanium-conveyor rotate: false - xy: 293, 3 + xy: 293, 23 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 titanium-conveyor rotate: false - xy: 293, 3 + xy: 293, 23 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-tungsten-drill rotate: false - xy: 303, 25 + xy: 293, 13 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-tungsten-wall rotate: false - xy: 303, 15 + xy: 293, 3 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 tungsten-wall rotate: false - xy: 303, 15 + xy: 293, 3 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -3296,14 +3282,14 @@ turbine-generator index: -1 block-icon-unloader rotate: false - xy: 313, 25 + xy: 303, 25 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 unloader rotate: false - xy: 313, 25 + xy: 303, 25 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -3338,14 +3324,14 @@ warp-gate index: -1 block-icon-water rotate: false - xy: 303, 5 + xy: 303, 15 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 water rotate: false - xy: 303, 5 + xy: 303, 15 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -3366,28 +3352,28 @@ block-icon-wave index: -1 liquid-icon-cryofluid rotate: false - xy: 537, 181 + xy: 527, 181 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 liquid-icon-lava rotate: false - xy: 525, 171 + xy: 515, 171 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 liquid-icon-oil rotate: false - xy: 547, 181 + xy: 537, 181 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 liquid-icon-water rotate: false - xy: 535, 171 + xy: 525, 171 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -3443,525 +3429,525 @@ mech-icon-tau-mech index: -1 ore-coal-grass1 rotate: false - xy: 562, 161 + xy: 552, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-grass2 rotate: false - xy: 522, 151 + xy: 562, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-grass3 rotate: false - xy: 520, 141 + xy: 522, 151 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-ice1 rotate: false - xy: 532, 151 + xy: 520, 141 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-ice2 rotate: false - xy: 530, 141 + xy: 532, 151 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-ice3 rotate: false - xy: 527, 131 + xy: 530, 141 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-sand1 rotate: false - xy: 542, 151 + xy: 527, 131 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-sand2 rotate: false - xy: 540, 141 + xy: 542, 151 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-sand3 rotate: false - xy: 527, 121 + xy: 540, 141 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-snow1 rotate: false - xy: 537, 131 + xy: 527, 121 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-snow2 rotate: false - xy: 552, 151 + xy: 537, 131 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-snow3 rotate: false - xy: 550, 141 + xy: 552, 151 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-stone1 rotate: false - xy: 527, 111 + xy: 550, 141 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-stone2 rotate: false - xy: 537, 121 + xy: 527, 111 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-stone3 rotate: false - xy: 547, 131 + xy: 537, 121 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-grass1 rotate: false - xy: 562, 151 + xy: 547, 131 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-grass2 rotate: false - xy: 560, 141 + xy: 562, 151 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-grass3 rotate: false - xy: 527, 101 + xy: 560, 141 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-ice1 rotate: false - xy: 537, 111 + xy: 527, 101 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-ice2 rotate: false - xy: 547, 121 + xy: 537, 111 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-ice3 rotate: false - xy: 557, 131 + xy: 547, 121 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-sand1 rotate: false - xy: 537, 101 + xy: 557, 131 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-sand2 rotate: false - xy: 547, 111 + xy: 537, 101 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-sand3 rotate: false - xy: 557, 121 + xy: 547, 111 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-snow1 rotate: false - xy: 547, 101 + xy: 557, 121 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-snow2 rotate: false - xy: 557, 111 + xy: 547, 101 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-snow3 rotate: false - xy: 557, 101 + xy: 557, 111 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-stone1 rotate: false - xy: 577, 178 + xy: 557, 101 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-stone2 rotate: false - xy: 587, 178 + xy: 577, 178 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-stone3 rotate: false - xy: 597, 178 + xy: 587, 178 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-grass1 rotate: false - xy: 607, 178 + xy: 597, 178 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-grass2 rotate: false - xy: 617, 178 + xy: 607, 178 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-grass3 rotate: false - xy: 627, 178 + xy: 617, 178 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-ice1 rotate: false - xy: 637, 178 + xy: 627, 178 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-ice2 rotate: false - xy: 575, 168 + xy: 637, 178 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-ice3 rotate: false - xy: 585, 168 + xy: 575, 168 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-sand1 rotate: false - xy: 595, 168 + xy: 585, 168 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-sand2 rotate: false - xy: 605, 168 + xy: 595, 168 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-sand3 rotate: false - xy: 615, 168 + xy: 605, 168 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-snow1 rotate: false - xy: 625, 168 + xy: 615, 168 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-snow2 rotate: false - xy: 635, 168 + xy: 625, 168 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-snow3 rotate: false - xy: 572, 158 + xy: 635, 168 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-stone1 rotate: false - xy: 582, 158 + xy: 572, 158 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-stone2 rotate: false - xy: 592, 158 + xy: 582, 158 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-stone3 rotate: false - xy: 602, 158 + xy: 592, 158 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-grass1 rotate: false - xy: 612, 158 + xy: 602, 158 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-grass2 rotate: false - xy: 622, 158 + xy: 612, 158 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-grass3 rotate: false - xy: 632, 158 + xy: 622, 158 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-ice1 rotate: false - xy: 572, 148 + xy: 632, 158 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-ice2 rotate: false - xy: 582, 148 + xy: 572, 148 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-ice3 rotate: false - xy: 592, 148 + xy: 582, 148 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-sand1 rotate: false - xy: 602, 148 + xy: 592, 148 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-sand2 rotate: false - xy: 612, 148 + xy: 602, 148 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-sand3 rotate: false - xy: 622, 148 + xy: 612, 148 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-snow1 rotate: false - xy: 632, 148 + xy: 622, 148 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-snow2 rotate: false - xy: 645, 168 + xy: 632, 148 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-snow3 rotate: false - xy: 655, 168 + xy: 645, 168 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-stone1 rotate: false - xy: 642, 158 + xy: 655, 168 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-stone2 rotate: false - xy: 642, 148 + xy: 642, 158 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-stone3 rotate: false - xy: 652, 158 + xy: 642, 148 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-grass1 rotate: false - xy: 652, 148 + xy: 652, 158 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-grass2 rotate: false - xy: 570, 138 + xy: 652, 148 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-grass3 rotate: false - xy: 580, 138 + xy: 570, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-ice1 rotate: false - xy: 590, 138 + xy: 580, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-ice2 rotate: false - xy: 600, 138 + xy: 590, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-ice3 rotate: false - xy: 610, 138 + xy: 600, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-sand1 rotate: false - xy: 620, 138 + xy: 610, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-sand2 rotate: false - xy: 630, 138 + xy: 620, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-sand3 rotate: false - xy: 640, 138 + xy: 630, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-snow1 rotate: false - xy: 650, 138 + xy: 640, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-snow2 rotate: false - xy: 567, 128 + xy: 650, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-snow3 rotate: false - xy: 567, 118 + xy: 567, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-stone1 rotate: false - xy: 577, 128 + xy: 567, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-stone2 rotate: false - xy: 567, 108 + xy: 577, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-stone3 rotate: false - xy: 577, 118 + xy: 567, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -3996,119 +3982,112 @@ vtol index: -1 item-biomatter rotate: false - xy: 997, 345 + xy: 997, 355 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-blast-compound rotate: false - xy: 1007, 349 + xy: 987, 345 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-carbide rotate: false - xy: 1007, 339 + xy: 997, 345 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-coal rotate: false - xy: 409, 48 + xy: 1007, 349 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-lead rotate: false - xy: 409, 38 + xy: 1007, 339 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-phase-matter rotate: false - xy: 413, 28 + xy: 409, 48 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-plastanium rotate: false - xy: 413, 18 + xy: 409, 38 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-pyratite rotate: false - xy: 413, 8 + xy: 413, 28 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-sand rotate: false - xy: 701, 161 + xy: 413, 18 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-silicon rotate: false - xy: 711, 161 + xy: 413, 8 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-stone rotate: false - xy: 721, 161 + xy: 701, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-surge-alloy rotate: false - xy: 731, 161 + xy: 711, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-thorium rotate: false - xy: 775, 302 + xy: 721, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-titanium rotate: false - xy: 785, 302 + xy: 731, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-tungsten rotate: false - xy: 795, 302 + xy: 775, 302 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 liquid-icon rotate: false - xy: 515, 171 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -liquid-icon-none - rotate: false - xy: 515, 171 + xy: 517, 181 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -4761,7 +4740,7 @@ icon-itch.io index: -1 icon-items-none rotate: false - xy: 987, 345 + xy: 987, 355 size: 8, 8 orig: 8, 8 offset: 0, 0 diff --git a/core/assets/sprites/sprites.png b/core/assets/sprites/sprites.png index 3900661c1daf4242fa16fea8b0d01aa08195c79d..da1679d01eb1453f5328318dddca9d6e0a7ceeab 100644 GIT binary patch delta 129917 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%0AS6ILQ96JN{2nAY((v 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%0DXqz2jzPDD?W4 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 a{_yjBG79o-ILE63%Eq3qelF{r5}E+{(T2kS delta 129772 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 ^N0{ScP?Ez0-Wa#CmJb zt>RgySp}X%T>qK%I3_t|Kb!QYXABAp^;oUSFJEWb_196TzPM)YXXhuE3-A1$|D0Ka ziGhRZfC+dTEJKa z6<}tl`P}|1xz6Et-4ZhU}MIZeg6vP-rD*tmb z{9trgJc(hMK*MT9hF+0X#mD+KxFyZ&u0G4`(6?}o)}LKo=C$!}?0T;=I83a(wl+Gw z-d>rHk%Q&JD*K+Or?bLhuk+8#IA5fCz+=kfk9Iw!p#88Z^EhvvUvF70yE*xR&fM~8 zFZ$E}FVB5-{6@F9{xU6a64`on$B$`xYqt1*oBU(C0>kZD>@7^2_-twQ(pUiMc`8>0L7n8@M{rmUd2Q@O9 zuhv_(uVH%NwQ2ftu7*ATny0c{D6bcL%U`=+-gK=@&BMiw3y}0%(arK4AOI14E#kK($pDM zI1cc3GNuY0xY=!;-J<3HN?d4_NgNY{#M)=hcf3FPA3L5i&tCJl-ZdfZqKD^xXYSu~ z?M~M@@GyW7!-NIu&No=je4W$K$ig78_-}0H!g|p!bJ{a<>^{u9x##syo|upY$3hS8 zuUOW2vG~iy9p(2bms>DsOr7)qv+ICElWDr$uI;pd`hXi^x?4TGM)uxHs5a7 zKX(3Wd72??Dszh78lz(SzYNFvm+|hMTf=%p>RV!+RoFSlbJ|kR60KPJ{4t3*jK6W&phWwfH}hix28C| z`L@+%Q!lG^>)9_>-o0m>{iz`*?&e;!FL+#lw+ScGsIr;@7=$sbi+sGs>?0E zy?%@JZNB}Z@a*?Yk=g%^pFNtt``6@#qx?0~6&Yu&w?F3ee*KHDnpJfIopy&!FKG!r zIm@^}GVH)!rn$XW@BCZ*i>D1x<;ty$zsKg^;AL^( z>i?NfbKhid3wWx1k%cj4_5WKI@sbB5Chr$_`aIzW|5;|mGv0r_;kE{b7#j~mM+S#5#s{8nVzg`4S>0DOJTO1` z-G$%URkfe4X}m%kD3|IXbWUuPf4*Kpxy>$~>>_Lau^DZ;nKo>97`O zEtI|#aBaew?fMB~OjF`dXM$Jo=w5ua_VSz6^~|!T4$04oeDh($X8$PPk zPpV{Xn4!|Ju_?Zd>4*yB6NZL+%AHj?pP!fH@BCF+%Iv_v#9(u#X?o0&eE~uzF8mez zRk!B*zjN6#L7$8o7JlZwdw=1-Fct>Ui-(TZ2rzsoG-PmaVmLeN>Mgcq(T_px`1r5O z>-3a7|AaF(m{;+xakF^$ev9^Z6aH-*_4A$m>KRnnmRvt>_RTbS#UWq0c?ulSGwfI2 zJu}zln$)(~oxDGGTs%9}E~~Rz#s7TD$+GqF)+hD1{wy?VSa-F)o{1rZb-_HNh7hI?N4(wN z7=E3`c;(1`ewGY|1E%(S-Gx4@y!css%l-ZNtN!nK`%C^-M6kB&%53>@%Hdk@vHT5; z4FBttEKV{uy#M3$y?n+0C?*DhKgMA(+qNzFpPYRoT%Vz!u_4V=Xw_Wp=QI1h78^PG zGR-}*@BEJlt9{w^{q>Qi8`OlSG2b?uSzMca=AbS2$;%3xQ+J2Y*qVKP)~CP8ay1_u zga64Uo|@l&{ax*geNr2HYl~uj)|n`~^%y+e|4~cY@)Kh*XzQr0^6_YC(_Lq6Z0gb? zx9FKqPFu5qVL|e>7~}GHF+vN>N*EVa23~JkeW>o1OZJU-v+TD&tJzTBe@yq)j3mYb z(|n~tI~sHuH17NVs7+(Flsn!p|6b((i^CQw-Z7g}I&J6An~~qx+zcrA&WL|Jnk>ShSC)|wn1#{zH{Nww& z-HPD>+s67YFD{;8X1LA5a7Cb@xu4gYfuVUpjtE1<<}_d5Np=hk@AKYoY|Fjr&!C|4 zZ+7P4g%6|F#O%COCnxaAASXF~vrE z5-?|2;31oJ{gwT9h7V`g*E2G_WDMX3g+k5WUIvDJRyTeK>L&lMv~cj@VA#2)Sf62j zLv9Oqtp&O0Cda|x6zvOIv#?$%Sb?*v=GAy zcTR9~dcoKBd+`^oZg_lA7tSf{_GIGNcuM$2Vl>wc8|{A^lXtQ=y#6b?*Ur^hBFTxt zNseKiO2Zx=h4QP*BWkDrIB&xcq0#WV;?EBaqy0S04?e!Mspn}}KjpDtQ(gT1Q(7PI z-@l*V>&&3Sxa4YmolWYk^OIRFY`qk3!m04RV8L_4rg$~RCvhxa{-wOxe}+-uSB=wm z_ZPnnzx~s@b$%~n#%yMW>)@)eUYn_5sr0!SS9kA=Rekl-%l_;5@*zy3;UF9Uz2w` zn>tm4#o=_4{A}g};Z|$@zxkOkb1%5fP_usR-1#>3^DkVqVW_B=Yq(+5pm)9IPXfo( z#?P|n!~bhaCivDT)OiIRzk4O|OLN;Qh6xj2+kJhUpW~drJT~)rTkVW^?cR3F7ZpN} zKK*cGX1M<6i}QqLhN}9CB}?jOowkp)k93+ao#Dzleg2IcwRR=`7oL0GE~#(XC;$20 z{ZIcirJRJV_Fwob`zqg+p*}$Dza)dg_4-};OW7IP8K!7lU~1UB-{iM{y}_C~`3*-3 zLskC@F|3IHBi_gm^T+xW%XTe>A33R4=QB9ur^Y)oHn1_qJmz9(xbglnWXaRQ>?qmi z0drpk?AJN1zBWVlP5s{o%R?<0mwc?KJL~jZ{j`{ucGW-E*D0rE7%ErQGb~vD;Jm}x#0n4qX?6R9m4_FT_SHcGTk+8H=BP;e#ZP)SG*6uGCdRed>)Iw zTqUPN9G8H-S*B{t?B;5Q6B2)K7&UBUIAFT}_1}xXkN+-aWY}XbwVy9hIKY6PaL&FL6kLMW~dYqdc@-Rd^+Bu1h z(Jtan-r4YHeaD_iTHj%8P)H2!MeK%_#sbAaBTl*sW z$ZHM;1zUzDhJ=G_UIsEKOp$kZUCszP4B8VsmAbo(p`qvF_p2-Uz806)%n-TbTK_s> z|BtizR~;Ds%{l&k@{8x^ou9iu``RWX3q?rUBv(_uDulM0-5aF}ocep3OFz9-ceDC66&2Zyr z2n*BApZ(2qS>;vZv#PJ2cI`9KOxXG}ii07du>aWhr^UQXA`A>}JMDNm86D2CPcc1W z(=S)QBLlP~i*4SY6H?nYt`A(V%5WfElHo@V4+H3gI#YRu4o|)ny_XpdbT8i~6#>qS zQK!wrXWGYul9;xqJm=d*r5hL-IG83BN(h|#ByY7pK(u+$^Y|@(X5SeUKAvJ-^PYX5 zerNm}SSpi$A1JIY!w|saI*(;SJ;SMwm(%AkO=*$#|My&*IYOBET;KDJe}x!+{BrhW zFbzEQ_}WI_jlT^#ggB}sLO}~nUHBM!mP=H|F-=&-uw&AfpX=6#@dYq7+)!x{+3g?7 z@XC|P<9+WsaYlxHzs?skoG@T``+52V_up$w86Id~Jy{&n@@~Fc$juLX>n{G@eYJiv zgAnLQ(VA^f7#sHM%5hC#Y+zGkEV&x8o}EPpv|F5wVM}Q3{OJAX_r`w~yYTGv&z)CN zEg2oAKHxXyWUTSk{@$KaFKwQ;<>}8STMycOIr)mSvc)Pc`PP<9F{TUIQ(Eh%dfo_N zVd~iL&B@SIzh1pcJ?invKPC(hgn!O@@x1=_jMXJ8FW>qtUd6j6jzM9cIiw-jCeNTT ziS@qvI!*?WmCb8A>-W7q{o%YVL(KE%ObjfHUGwTMTHU<=N?P4oo&`M25488&Y@46n`}}zOujEqG578I65i9op__2P|>FRyj6EpSx z&e~vK^`fEj`^w{I&&OM2)>YT7-#Xu}wEo6B=lN`0TP85_@wX{6oVcCD(4h7&@oDu5 zrv}jV1utzIYc*ISSeSa|UdW4QuaxIFa3;D?ubMZ2Ac-s}LmR`D$DsS8?l3Pn@l$;5WL`zZyVp-jE10n?H~~w!_94gmpEEqTZkW%FrlG+Mtj9FqXs>OhJD8FY&r}JycnKb>v4}`D_FtE z(8&3TVTCwDz*c4(A(jbGMJwxXy1rvvQ2X!B>E^?7|85@cQhE9Lu6-{Tt}WbtMeT5(r4skY_nzn4cq>-dw6L6KL0fa`T6FGhBG~{Nw*WdtUJ}F0g!2HSxAR|HcRR zr1k|h$S@qJnCbp=XS)+a(xxxF-^tH!4_JJvMQrE(Xjuk_GtM_ApHyks!=G$f54!dz zUyxxzXN~4R?{ynDDl*hujcjHLFI!{m^kw&%zN;9Acax%+Sf$Z6?mZv2|{@+Dc0XDOLsz zmWJmH6`$X*ef{UH$N=hrdz=1D?m1o`vHMj>`>&k`ruEyfJM1mpu=B_C+d>Q<+|M#Q zSlhp3Jm4+yNrvGoIJHlM#sjUE{r8`( z{=ClW!=DO1!A+YIr>YdL`*-0_(3Dd@O0au0(nG`q`j$Qm{ zW4CXfO2gMM#)7l{@B72vdhWSCnI+(y<5m6>%ni(9^M5>_&tJ*#;d}=}|5`hRb^QN- zpN(GTzq>xh)9AJ6Up@YYs`u~1*ct94U6|Ix4O9rbm4e-u9beafj-fnmlr&PVr|7&QN>vaEQ*vf(PD5QD~2 z0r18JQ$~jWj$*4G%e_)>Tr25P_Topt+{|~33nUpC&RQ@O%$={#H>H~4N;RkvRR39} zOys}5EyE5)wR!gXzkZ#4#~-`@!dHIIicbs+vX7r;JjTEvclv$t`R`L1zsSkI{P%v= z{PjWErLQU_8gv*thwYdoiumemI14Ll7Be;l)YX5t!ClB8SkfG__@`H;R4T9 z#vPf-AIvY@zn@?B?#|5eulBdj+Nag@DtuvJ*yqnxdE=p{a1VFeLVs&FZ=M+p2TEBP zj)mxOG4OyjqU%&b9{&#)9f6@K?g`fA= zJgvSKCBSe+Vij|U7faOPvZYB5X?O45UABGu^<$auZ){ww!LY*I-r+7o!sA1g&p!QH z8rn3^i8%(ekKVzOF^ZF6i9kbJ8^aSO2Ir|P0lSya`|-?s+54%%7a16m6G1sI%bAPe z)0Ig6tA)=#)_+&|_49MU+>D}^<(>Zm?03H3%~E^u--WN7d-n&_YqZVp=GHX(aDLal zpQ%EI@pIoP{ayGv{LB0E;py|j87HJNIEOM+NIK}<`oXU&^x~Y=heiLa>-Ah0UX!0Q zpOGP)(?MvPh`j&j!)xo~@1N3PX>exP*YCveW8)qshZC;#AOG~#ugc!FJU`dqo4r2= zXp8ju)$;@x=6N?LFnnhzm>Tn?)=uQnLHp0E1SWa$Fefv>5y}h=c0Dt#%h!qKd0cQid7Ep;f9tzHlZ)Ao zFfsg-$@|ywTzz`oj*rD03ia<@=LJkqVCXw4d69GhQX|KgYE z-`}6F&QxbuaP)q38L`Z=Mlz`T57v<qzTC$^e!gYWbB9ufIHeC&yo?{F zGO{qNaA;Wd|No8lssG%jFg}^TQ-|53o|#3eYYHRJ-`F;(NB_6VFbFYZtp20-o`IJk zWKzkrxO$77M__vsm;n=<1**-#wE0<2Y^?`}O_0^r)99ZV?GjN=hk9OaaQE$xPa1V5W ziM#^C>i>q83>9m?&2XuWVfe&Sf9vC>Eeh|a>pfCrP{_V=*>2w7`9^n~? z=MQM{V1mwq#x*JzG#Czq?~P4ma0X2vUtwl=vh|Uw*i+MI2?xIZ43PBrH?ux#{?7OB z&A)(B<7`%jBv4{J&&ZI|5q7P(dt#WiJFn6a28MlA^*Mhcn3J70FAM7QQ3(H98~IVG zL6G6c%i#98aV&=gK*s}dvTU)d%ekj^bmxXO>pfOi@wPC>oaId5y**Wc(Kz>(2{&Wg zrqCnx0t_d9)aBV}Okig?z`#(i@W=BHyMhC_V|VMr2hea~js2q-28Yw&jsH)w6&cpm zU4La8|4{#u{AcEy-~T!NT`2mi%9$aF(K%=CyQ<&Pj0`oOf4@5OS%yL7-^rvRh7&3c zdt?;ea_?+t@9Te6en#JxAw|<+=8ygsqnKZbEDSM<3@i)^{|x6jv;3-`80XK?Fpa&T zzU~g|J#%~he1(U6tIb&&s?9Fch$I~h|CcJ)5WvB3^zY;s(+f^~ho-q+1_sxC_nn^G zi!dmt+0|J%G}wIn-OR{vDoxL?fSDm&tddnkfWctD?f&go85$BF*`+LVWH=ByaiIaj zi8%I$3@11njx=00i#4s!Nsdb)U>+(vo9;d+DkDE4GIk=)6VWcwPmA8KMTYCf8Vyx{hn}726}9`Gat0ZRAX2$ zlZ7E_ZpOYt@%@(n8q<~bWz@g;)p9R>g?-4f^Wl>^+Zm3W>%Y`g`Kkj{@b6z#UoYTk zw^)GT6te?ELp8$$#f1tz?;r1EsCoB)pHV~oz7LE{3=2OnUH@ZoZ|cH%$E_!2Jbxtn z_lNYY3%4`FZ|zK!f41D`$DQtm^yBJ^di~4)t`lh3!*IZKe_7tAzdK|8z5H7^`F+27 z*b}j6wh7J*k%3H~pEEMtx?lg|pMKSU(Z6-vbL|-$p5NP*GOzBlw6pHNc2Sm$rr-67 z3n ze)Kjtj$uIyGvr7=-q_X4`>I!*Gw%O>eNBe!&kL_XO)96q3qR{evG30Kzjph*s9Pov z_g&i8`uNSa6@iOaeG^^#IQGkr3(v#1@Ks*?St-gekGsCXh9Rk^+v3)`uJ7MD4qW(n zMgKGNk#p;>E_?p_!{RpXn7Bh1);Uby4jpZKP-t_^b0N>+I-y5KZu>8S!a?-n)Gn4U z|9<>@xRaaV!tck&KO`r9+U3PvYgh93$Mf@t8EP09*gT%^{@-0!FShr;^WW*~Yu10c z9AEdu`FH(Y;zY_9+R=8su+{@Xd(^DordA;MSUy#^-K#Y86F54C_EEv@_qOG_K z!E&IycJF-t&mSHX)Vr%yK6x*{(6VOsnbQB_fB)%J{cr!Zao2gp1{S8~hl~O3Kjg&( z4v1FAEMQ4kDaW7?dyZdkM(Vuiy-SwyEYObWtQM4+XL2ycuI@nBfBCxCLEcjsWwJN| z_Vu5cV_EFu@855llYIZH%D>M{4X34#r#-u6Ui~fS>WlaJ&(F<$cGkT9e#rZ|mg>8L zI2^K8F1=>8cAnL{g;wVmYB9LJKG7bxKV#be8CqTTh0czZVBIT=>dc zYj^2ud{~VF$AeRs4>C5C!NbS%!DX-gER1K*F$OsN;kRV@FlYPc8kEE1 z=B;sIXz;nmE+fFe@ZjTg_wq=F55HQ|dG^~2F?9UXS^Q6J;#T=#xgO{a;kNq5Iq9+xn96tSSrvOZv($K6aiTZ+wd3+06Mac0Dcin;9Cy*w)Sbd|Hb^ zWWHbLs|{zv|DT+2RsNA>2%Es)1{MPbW~+S{QyFR%88)Ue6#B(nxE{ ztNzBt#qM*<&f9*UVf}v309{sIp^JUo2! zzS*{)#aZ?8`%3p^-Ct__`waiQo55S(-`JSUcI^9H>vFwn{q_6*?W#KaKBg?6jq%nt zU(7#SQHdZx;O z_QditX0S0#kYo7D!0_^+Kyhs{14H@m=~GoUteyJu3IkFaTFQPPfw6(D>;FS8MyEpt z->W+q8J=9y=Y7baC)i^5R$}Kb`#TH{JvW{zNl$avc>d$f)925>K3NdSIg+g*_Xt6Ddik9KgWIj)cx~m zN4rE#%~m~bT=j=%VK&q2Ti52gcx0_yYW78rVPokX=hAtxOtYRdXau?2efhQSYVAA5 z2e07;m2&*Ce?{dFVC9n(BSY62f2I2Pb^oqUW=`1A%5Xw7D~@eKi`MxbehP=Nl z3{Hv+A0!$cRxokegxQ&~9AIZi(E!bA_WxHu!3b*O><0~k?cTS~2sDbX{qM;?|A2;V zseAUH=XSXF-*vxz_r3oM>!$y>?gSb^nx*isU6|#=yx^Qt=C!_`85o)vp41=HKl6;G zfb(v7|6AkzH+C*#W14WiVRhDluY3%3pJ(4^VPrVA-)Pl&9VdszhCF~8_AE#<_CMXSWjr2h)YRReTDJ4I=*yuK$hyQvZANi@)-((wX*ai7@mF zG052d|570Ke=Qf|qVS%HPvxI6F-$qp@7&;J(csH-Vij}Bw=<93H~0LXeqBG*nf((( zgQF2QLlR4^G2?;t^?$QJgHAxb`TYAucX>$(TX*?mJEDI6+j`9Yv;@P0syKdz9TguJ zndUe@u>bL(S(2f_Fuz{aIP;Rq?die{GuWcf&M{{@a?8h%hT@?LjQ}d&M#v8 zpwjRa+Cq?F_>pt^K*P;F>E{c?|2wlWzAF59UfLLT0<6hG{>cIjMNAVU>YX|rWae6*KSMz%P&yBG-KS7-FY_FxM27`1KL&E*Z0zV9s?7z(jXuk2k|Njqu<^8)) zTxd63XIA&8;_71Os@MX92T^~^d&?OcKK;CUqfgdaheg7+YK!7W|Nh`*J{up^|NQjy z)y8H9hB?JYnHcI~IhY`AuzKF^F#owP0_MK_KYwp|{j}A)&bdD4UwNv2!XIXHzqxlN zuL@RQ8U4IHxbwX2K6Oj;gN!*Hl@ha^gc5WT<)*Pt`|6}H;l`v3%#1=+8!ldB=Je!f zaR_oTcvi^6)^fnXfJNlMiH{q57s}Mget(;PX|Zk9mpR2}c0P-`yZ8IN%~>Jq>$g5z ze0|pH>eBlAGRZa1=a$E1AM25fJAJkC`kS--e%tH5-kx@S$(#8ma_`?8t=(VtcjxEX z*7ZBB?%(>F{ndB={680`*8Au(3gTe&kmGIFzTW4PeK;K%Ue*zuny zUoZN+}uua%@Jz&AE?5(sKG~%@qi~o`}OXxaxcDYZ+qM4-Z=5CVH?Z;#V!ng zzQ+GAoz76t(OA7@=li$PYj$!l-!yM!`0?|6{W-@CHElN^XFS$kH;3=i>gMO=Fa8~? z{HE-|P#vqsz;9jgLE-4H?Mv7l((kMN`uw$DfnnG00PBv85+AD#`S1Qb zeN~fvef?GQ+B%sUPlIEe3`Kv-D;X~AGvT{)qI9+}Bg+NT^^EcVSA3kg+~NL@9)=Gu zuOze1-gGONkKxVXt@GO%3WOTu7Yi_4@8WkT^5I}8|9xGE>C@jE0t{C2yWE)mYJ&C= zKW1sjuiN^&|7ZE#@>_k1^$aSU4lE3Nnu_+@?>ry>_UB4l`IEP~JP+#adot-H(?9o4 z77w;x`yO#IDm+m4WN5cZ=wAP>Sf5dzVQTWJ_YXF3G@P&dwwcW=hv@)&%hII+40;dd zXE6#L$ho=csMmAL4({FOIT?O(F&((h|Lt}Bf2&`c=Q}cNStZ1v%CcZeke6ir%^+9x zsLL~t$=m#ztllfk5L$X~Yn1?l3zNW)=eKuiF+|nB{Z;(jy!>BL>AvDnu~~NZOn-_> zn}ivbFgdU==p18isN17iDZTew;|~X(9cTXNeZ61%H6?12qwn&0cm7vyvN`hq@Pr5a z%TyUz7#{>NGC1(5zMSmIpep#lcuS`zQ@w{4LrH8&Z|G!(T_Jp1KObTr*98(1t9adk_d#^hG=dp(h;(7msg@x8n4ljolx1|I5#d^Ck9N z;xe#57XD+WkV6l9!}_ zkhGWP(kVf0UJOfKxMW*h-X}UwhN+={{Z(&%hH#Dp^#(Q!CQJ%tb;Zy1!=LZw<8M$q zuiwV-fQ#YFs!E0lKGN*}Ssroh$z+IK_v6z)6T9yQ6TWU_aqw6^ufZzyl)9utL&hhD z4}uIXOfomOAASkC6Y|KKKc_b^^fDaK{+@F}mEp(N#4|H~_9`+Y{E@r-yr~0pjOF@Y zp930Xm=wPKtY3UJ{42u=RR%YXKmL+J4$Q`i+y7~~ZTz=RrSz~GLxLg`16zRld+WZB zW_nByW~R@3d291|yX>!T7n;|EcK)lM`_`Xl;A~i-(lFaB*NETt%Y?1FudV%7mma|8{`T9uyU~*aKF9NZ zzgr&fe`dcnOTy`Cx|8>O_O1`V|LK>sRR`+plRcd)!z zrOd*?AaUnjeEr|A8Q1INf2cDDygR`@t8?z}-|y06SsHk5-mv=Eu`~TqgDS(dwb9}# zj33@CsO4h+u(kHz&*!y@3{yE8I7}H#Sswhe{Vup6|KOYVZt1s+&&XKq*xxWAsNPF6 z)x!1P%x-X5`cvNK=c!_bkgJu~Uvd8NR${oV$Iqb0sKLqb@6~6934gb~K0o*Vou8+# z{=Bv>es%t@KS`4ql3eE>|9`xKA;4C(u=pwChsOVU3~x@H`52lIF2}Id&S2lNV1J|X zExUZ{ucZh%F#P}cu`qiMi-UDTy_}x|L%pRL=YsQR3IX%@TJL#t$d< z&3*QextfauobJ7$>HZ7*`P3hir?39J?yB&Ia>arzlUmM5#F8q4Hf>%P|wTY9@;KkM(C=j*PiGNt%2DDfGjH85mcbzs=y z#KJI%?RJ?v`0>jTs%1s^&5*!Q>px`_e(B;h@b-DA}tM;|dy2<~RW?FqUw7RM| z_qA2YhT>!L-+pF(x8H8}{qy1QA3J3d*03{_U!V41P6|UZ!-6UG&hb%<)80=s0;M|b zo0Aw8d_QhBo#9DP1H*ZSm6I$Koa;diq5XVJ47FQ7{``5ezIbu*H&7#}rtXJnk)*@@ zKUv?8PhwG$RM`Lg*7Nw^HP7w#Z~a(2U;oyRCzd7OZl=%Pe9kIctKol&&;kL5-Wfk1 zO&6bGe9q#qLz2+?`j6uEft?HwKK@yFKCzwSg)&3MIR;0D2{6{JC?(2G!`+r!8-v4{|zT^!N#{Zrw4vGx36dA;H z>JLo5_v3;FSL6Q-kgaD07#RL$Utf1r`$Wj*kKaOV=WEALWO2}7X{cATe#mfn^0lAx zjJGC#`#H&2gyDan%7GL`h9ocH+SNh~41a(0y}Pw_b?JwBhMV8t<6XA*uK9lju8M^z zwNXM2(*u(ZyqvuImkOiq|F)=C{IGr!Ln}i@ z8^et~mBtM9ufHs;T$}!F?Y0N7p$)vDEcr*u_VJs1LXUhNC6_hULyRdw}P|HYNlZ@&GwoJ;SoffBZQc#;LW1>(%P3wu}kRX_KV805}hPR&>CRlylXm!z( zzxjOmU435dyXWmi3 z>Wohw)YsJ9ky2;8!1o}&Pcf8XOT9|N%6I=mCoxQ#&I~SFmt;RKcF|(k_w#FGeO-M_ zrNWb1{@Q}9E8VTiok8tXnkBrjCxfH|KagMlxrL)xF`mJC9n3{m@b)c^Hi zI^eEX(*O6t&UD6qi#Z!~%h$ec`VVQwJF7B0k$0N!wdP;pB}T|f|pm+UxZkf zuFJl1aGlA&4d?xL|KIs_=X`l3h6*Kz2zY7vf1kZq)PJjE{4cL8mOEz4cp{+RUQ7Cz z*sZB$c@}PuKik#Fd{I}>tN(p{Ez<+(`N9mJWM2NN(feOgYr?kQ%xc-${53o(=if6N z-tl5Rs{j4ZKYG3YvuyG6Wxm0(f2ikss8Dm`+JZ zx7F$~o>*GX@R6PW??u0+oR^FW$zOtP8B9R6My&i?>F<6E9@{ZBtn(LAuIU3e%vCv$ z*iKvh-|~L{viYVPO0xzE`3_nOEasF&y5Q1>WAkwHpsXg$ zUt6*~8BCZO)<4_5hbg6&k8wd`eJ=}#ARhzY2f5rv!#Ab2h4<-;UEJ=e9gHS|CpMpt{Qa&oll5OU!?AaV?d7JPWz$UC-!uca<@476o200@T4(B(1eSKY8g0W1A zF+p#8T?I?R(fgu$4u5|{$SEE8zPF3@hvtF(qTUQGP7K%9MT8vi&|EsjXch}Y`DH0K z#sl}^sr$_;IR+`l`UAEMv-a5E`V$Tcn^Vag3~Qbhf8g)_$S^_sKg**XF7X@-K7Wh( zKR3PUxg6W3IAw{Xl2?__>Y^U9&9L8;-|odAz}c|v67vF1CIuDd7t-@N_|AmtF&7d$sVKlpsjTe~l9H!FXrryW<`Z*uy&+zJfu>DYv zE`5HnQIKKBveeVlc%SUgaW4G7@U7iXzbO*@48MAt8yOm=g8a?X%g}LkW?t0u*y{f= zvTtJMzFJngVe7v?{5xt&D;eqwI-Tb+FIf3|@!9=mb?f{pMfn>VT%oXl`n_3Cq` zhCTbW|IMEL#pFZp+0tjGbF1dvo%i}qGs}&wm8JYYbEbK&o?{XFK^y&_iKe^#fJr^Igce?In|zw(Y|tN!=~DU ztl~4il^?5l=^fuBv-#((>-*Af-Dlh__3pJh3q!;Ix7+XEYueiXJwp0P^yJ@{YTho@ zjox{8i(~!ubvx3|#1uZQ(Vuy-?ilC_RPOH+6ehPa{5YFmCp_JSjWLn&@bqPDjBfSG z`g>mdF1AlSXn*SHb-8V2_pX<1*&)ibz>qd@>u_a^u3q85y$wFPl+( z&hmIn$wk++XJ=-{mj4i7+)(;DY;(=ePmO{M75AgBzxg5Z=ltH!90AppH8Zu%guVOwYvWLoeA@p6Yl!5_JD@lnsxPL(|w=s z%$%FGcHZ&ByM0#YM;(uh z%YvR_k9u77 zec?WCalI{NZ?|6GmN1FsLBl=vf3dkvEDSkTQ!X#}kN*98vSvp5`ParnLa{?_og%B9@@ zV%a+PipNzreq}3YEX(rue6P3n%OyQVhUELK#b2+6e^=RGKe;WXGWWrKRR#-Ahd9Pd zV(YE0CGRV{JeR?u^p%K1o%oCW-|tnQ%iZD_9Ul4bgxls@7FEUvtFJ$M{$R0t z2vbAY|KMT;$Dg;V)CRdtIR5{1VeHpiKV~TVE}wU7q0i4HObpF?4IsRPMZm>P@|I}3NxwVGROs}7Nt<2ED@`15x{_lDf&V+BW6%|o`TdnG^)vJF^J~m!f|n(n+H|fz^XsiYN1lNO&$h@eW_oZb_xW$dnRfHv zS?_FYf0dlVmR$16(m~DHt-c}MmrdZS0%w8(&kB1%uEiS69u51tcoyv0H{<8k6b8$a zhu?4KVdrH0a^bu7_n&iq|2bV-SIEThOl$HCmVkrxiuDX^?hFSQo><-gEXUN)TpwBg z>B&hwjqidCdJO!|NeBB@*Lznnlzcn;?d|Qof7kzdCX{XW;eFK6?}7|>YLX|+I-nsE z|7qW@V{S43+vV#r9=!X1dH%mG{2v}myt=S#vi5@fIoa}2*>5jZ=Vcr;oBPi_n|t1k zq~34U91W24Q{ThFC@`t=)YZcE%?ufbm>1~DzP7SqC@R_W`U3+;@BCIAQV+>SNgNHHUQ8~{)0`Mgycy=$ znXFg8ci)wP_01ge(klk+5_Ay z5B3SapPy2%z^Ea^V9B^)|Nqv<4F79c8n!^%kinA}PF!5M&WquL48y`-%SzX5{r-LJ zx#blME$93p#qgb9R!$5_91KOjuiG$uIFLB00zlSUPhn6I~f%<{8c z+c$ZQY$a0zLqoK^AZTYR=zfrGAEXqT*dBkZkN9~*fI;`={59U3j0%&O!%SHk;^&`c zb~u;s!8M`1;Y78bsyr;Ppkc+?;OWMs(kSZ2;2|v`X)u+OVdjT^2eV0ke%dfFe2QUd z_`;;X#8CF;hM{t&>z4Z)5}Abzqy_)?zi*7(k}>gRg~XSy&AGS#IZR+s5UOvu5_iJl z$Mb2<3`>}vNj27g`rakXV4%d1Q7T`2dh)-@dFr-bKd>zL=fd#d$B$tCGERmyzaD>Y z6lN+}Bfzk9O3cZy?)AsMOVTk$a`#K`~y?*$Moi6g{83RIL zb=RF=yCyOGdbYfao#Ff^`Nxd)4hnq@Hs#l^b1>w|d^zdTuqTfBe@uGZ<(1DP-)6Q- zKM+$nuY>I46O{McdY2ihTVV82Ye4MWl-hPH3#PwnOx&#&KkUf+*FrsmIX%LfTTppG$5FGEI*?bk<} z=zjoW@7?lQnmW1=o=bzTR-{i@_zi@(y9rJ%(Bf0zk-|c=cHQkb*v4*i{`Zj(> z=lbgJZ_RIiJED4ADp6>`G|>xA3?^<2Ul`u}?7w$&`!E0X+vnYQ&GKX;3xk8*d+*i% zr!iC{g_YZ1Iqu3Zft^8}g=5d}1N&7Nmxwc*7Z&hjaH;1vI#|D2i*tb>!}~p-`&d6u zc`LvAH{+v&d7Et*is8wd9B7!^LY2V{+@N`-5E~2F8+G`nRzS2gYWa-eVpdp zzoM-E-OlG_Zzp-{y}T*^?eqF|zoe(HH}IaWzy7dqMNsqq2KNIV6B#Ez*gJ=Xf&J$k zh6&mqTo^hoG$=4Sto~`h^sA=Eli^Fl|8Is@0&hQ`U;i&ywDB#!r87g&!TtR#1+5GR znC@?1^4{6^-T#v+4Lpn$uU0P4Il=x!wl?yA{lCxi*=_6jSMYl_&OJ zWEIAMNet71yfl?VQ<^Mhfa*|q2+ePo-odaxY<_j^tsf$G4)fcuoOeIbI{$!K`Tpgi z6}Q}M-<}SC<^0>I+rDr2ug|~c$S~+>nqTMIyNp>smAQ7k-rkDb8sFvf!gf6U37yZZ zZ;*Gkp7F+C!BUOIq4@XrtHn&9D`=ljmtuOb%g&GC$FuO=_J8C~|Mp=yAiVru{@%~4 z{oiQMyko!X$*OCex!pR942R7)8WQdQzgp3Lc8=xcGxLg5WfKyEn*Td7@XTW+52lO$+_b^yj+(L&w|Yxlw!l z6&WtfXRyEjZU(6Dwb+4SOOQu{1V6(|)&m?3dOP?l8GnTR-^t!^-h#2<^5)-M5}={u zHfM%{3CmyFJb&f!{8a>h^`AShzpE;iJWVLL`aEvF*}i2;3u5={cgnNBc~a-E{o&6H zmaj&^_4fDGC)Yly{`kwPh~Y%e-`WGQo=nFd|5`eGzrXxFQ8tC+$M1|CGVO z?f(sxhK%NIdZG*rDY~BW-x)Ym{}o?duEID)iy?FpWPcT5|L59OM|E>n!H(cErce(4~64w8dRb>)32{F{$eR$t>@VSaZ2g8Bq(<1w`?PrQH zDcmld!hGiH^|fJ~3=ukv4ksC%Sr~F$Y#0`KHk@=~ICFRVlb|*?27wcoOXd}q zE-5Zu({5Y$?rZx2^Y08E6E%Hy+U3^BGYb6v`QhY`!iMt)_p^N3)W=*|Ip=%n)~RnR z&e-n_4;5vopZBBx{qa)_pZ*o8MqK-@${CWv;B57kWrC+xe+WZT3WFz;K<>r%U&(7i zBoC}oWVrQX-Rsq$iAnvmI%lH>yZ00ABu{ShJaE1@?C(hxMuRiY<72-nFeHSY67Xzr zJNQ53E1QJC!30hQO-2spAN*_o6*DoYaWd$!YH%>{J-GXze!nxr4iAQ#@+=2TulM?< zgXVEp=(03ObF&9HGsxBaVSiRXXZt$QWO3cQISHWAQ%&WEi~^S`&t08(?&?OlZ$EZk zUw=~I*a?wmG4svpde|La{HkSP=qpr^i#n?=)DRczFmKz=)B9K7a~1kvz`|wi@sXk6 zkk^!yn(|J*DSVtnAups|;sVs{-=!p|*Sj4K?N>RqZCejM2Me8x)^ zwjM7g32%liXKVgl;)V3P7NdJLo}F<*ajhXsL-tquZ=WA>Gb%{hi{E&@@wc5n1K*FA zJFfL#zpTCXLvcUDJb8s{%m-L6B|9;!&<*D=5aimE`{swC)z>XfewS(uPW(FB#*lM! zQ~l9xuJW(^lLa{iq?rVYzL#HhW-~BjX^4-%D!@=#Yxw!~WTq)B4Ew*wdC!Ql! z3<4d><+=N+>CdHVj3u%1s*F5m z*O%3+a6SmC?NDUcVa||S%#;vue!tY~v##Q?Q~pYuGYBPUJg67h!>BfMX6x$tn^*s5 zc(dGZuF<}T`gLpv4(|77n=lzPy|M4#U6lqtQHGm+lNi=TR36_liLvL0-v78WKa5xy z<~N7ORUWOV5BndjXVjp=^m5h%L5IJ7^X+o&e!Wnx=38z4uUV7DL38O8qf8D4rpj68 zir4j(tn2%_;rRKzbxaTRlnvxoo!@JhTk~_K$KRTQ3(l+m*VKokFzjS!sN^}ais8f5 z+xz}H2X!dYd zDnsq#<1zEkKb^l$|4KY)je&>UyMy+^ia+;%Tzq`Gpai2VOSHL7+H@lcMm3hdzvW*} zkC9-MuHSp(qvenKPbbxPvmB7l-}6w3aZg)ly~I842KnO-YJ!X#G9OhhV}8ZW7yugb z$}4=SpKWz9YVWeoHm|I%-umG?yR6RlzP#pdb=~j(p5D7J;IOOux!%=`*7!EXBTpC{ z=CW4yO|6m$>o8@|_+6~T_&4^e3&W9~^)IhvTYaz4*Q;kdv6O*v-b(xF{~jAvvK&e6 zsk_V4kpE`}Xera5Up0TX{=E8*-=FQo`&yk!SEEL~>KC2roA>`eT(@ts`JtoY@i_^l zQ`tNk7B>B7kPP5p;AcE=V5jhv-y974wAN2#n8Wab@vF|wczMPP>xu;#EZ$YghjTQX z=WghU`SrAr??-06j3iUReP{LL{lSjeA9l~Nt>gJo|9o!wnceUAWxr>f$goLZfj%TI zH8~jM+E>1G$^JU)+~*aqL$V*6$F83nfBAd+o&TI4yqy$40T4gm@7H7fc?Gwk<>zc` z%T;1n!sU>|*ASpqzRyYiQseYp?`xsM`rtbIbiDwBAJd2A^7XM|>#L&UZxWbDe zLy_UrpCD(3n(NC!DLTc2>s_YTitc`f1K$}-ci#V>Xn+5tHsk95r7a95E=)ZlKlJZ0 zc(OEby-4M7-~x4nnJyfDJO5S1xBmh2BbXY58I@Mfw^g&{SS7%qsp4?>{^O`)Um5D- z7dbqymw>E~QFAR+O}bRFXls@4^U5owtG0fez3%$C^)G)fUdiy}-u^qkxhFin}<_Fqe3@sU){uUoU?zMSYu)p5hAXWu4PK8?w)`l=nFlx~BWJvzZ zP$0^n%ef$gam6H-3#GNU{(Rf<+xnfqmtVICQ-f1ILlHPKH(y{We%LC0?ceNhW=?~m z2m1vWezGvgephAu^0ixy!OCIY9{vxpFN7F!7uUwy|FhdCz#z|g;3U(G)x7e~Z43vN zIVV135c^WD!t{IFMYdc>a`mxfQkbF|wfcI=Jonh;QPTTgR=xc*YoEOxQ-epto!`@| z>mSxXd&6XJ$Ip=G!cd^;6!q6Xl<`65{~vW8Yyq$5Ti5mY*)pa~I@SF4URCt#6Z>Ri znH(lFxsdnA7~f`ulq`xq|z<>i@rRzE?MQ4%0yahI{k&B*w5X^vwHxrT^Q)=>G2+ z%w@t%A13j95M)?qXXL`*$@?8L7~$sE;FsjU{l)P95{U-TU{=i2{QY~>W->U;2aV89 zSt!Wx15b=97q1Ck+k3xcFDRSNm*8)h1M+q)gGM{U1H*rYa`g;)H}}n(TGPt# z;p=(X_{%Gwulsn3IpO>~+iM*x1%eDK7MEROZrFY5xDZ3A6N3{=!~0*Wgcv^k+VWFY zjq!jp)SvuK3@bdCe7HWyKjtksc_;buIlbBcmqeD`UT9Zv?u^Z!4~G>Pda_IZ&rF~9 z@mur6T@4Bhzi#E<*>UkseZ}M6w#f{4o}HPQeC*h<9v;Um>Asm~XPaAdGRQAz*NrP( zbG+tlg$08;!?$;Lt#$wLKbAYh@x}h@m0<9==JWGWnYz;$7JLIYB!Zh5I9}TH@ABJf zR(JX9t)Hf~^);*i-(gmG4l0`>m>k-eJeV4G{}2Dj@WASwVNk7Ny==uRNIb8dFnOEp zn+F?9PbxCJaQ>UQW(H)=IrHj$cTk4>^J`Tb!-?13GvnurBur*uXgzX7h{5vw^kwtk zH!Cp+c{X@5|KnzSVEXy2wA{zWVr347)glbx`B691Ssdc+jV3YjOkgP6|JPPwvI;{A zGjC%p=f6a5v3i{kLJT!q?{EF|{u4_GNENYp;6easP&)M@psNUSY zH_q(*mL@#=ifHb|<x{}q*nPk(|YGyGcTKRf>V z-iP0p>oGYzJ2=6TVO8D#_P;gHSsbeG9IIzm_Gs{A{l}bgV2e6K0$)L%3S-07SS5zK zKab^ib2O~mI^Xu$+wJ$?{gzpHzf)NK*DYgyhV1az(y2S&Y&y-;_`j7~eAd?N>qSBb zZq@eZn()urQGa^P{jEB)7&s;}?E7|UHp7D7N8Q6`Ui0QYd)$7A8?=gYl3aBOEaP|HF4fEfZyqV;!XBfaF;leqAVY1Yp-Fq1K6+9>^GydD% zmt(@ew{CjnpUKv5wR0XfiZUxR#MOOC-+yZ#piSHT0VbtZJqS~yzRFgcE68UVfX!~$nQT{_Srl+0jU#=(he&z z=>0mzyo|YJxh~^{2kCCS@`Y2smCxd6*mvJ*li82=v+k737WX=~J?CG;dv5PFwqK6i zUsJ=l88_7YEXr@Z-b7Q!EE0)8{z01d1>=YO_S)Z?NbQ39 z{4;;Q-;KV-SNqR_L1E5O)mF*RHH-`FcRkU{R^O((>GvOru>Un*6&Q==`x~Cuzj0=! zvG4D_Y`>~4t{&L0!_vgf7~uc&$z=aUoD5q^r~dr>JX)KjVYh+`)3a0l3XFUC|Lnfi z!od)6)#;v+;syP0xxc|ROXD;xt2e%{9iD%yKYw-SwR`q%%k8!}UH|guX&*mRg9k(A z90rb;pjcJINdYd-sV)lgbE)=QDWZ7Rc$ACLRb>ev4~E%)L7>-hh-4*W3O z;uw8ismHX+FGI!Q&A#&v6An44H1NORZ)R|q(Q>BJLB(O+D@VvO1}6rU7aPpiGS9tj zAO1Y5x_+%)8$(9XzoO&(jrB?lucrO1fBe4R-r%xo^KG$^B)-^J$sJG^P z5E6Cvyb8mzwO1qSeqEkFYyRIi>CgUtzn?#m@y%hdZ^LKWR+}Bz|1d=n)E3?kTC{&N zy+uL1u;*3fe9#hwhSPrXv(k0H?Kr-Aff&<+S=rAmC(E@l>^=Y2eiF-uR}7)voQ$CL z)5}B{CoBq{zZw=E|5w#peZAwl{p8QnviF}eUQl6FC}!Bf!N7UL^2PGpeXq{n-ktPs zCl`Zk_g4mmNISiY*EdgE&)^XM{|+d9zmw9pWdL2!+Y9QJH&|t<+cNA}UBkmTVbdph zZ-yW1)@v~;fLBU0HE_t*)d$;v#`bO48RD-_U-z%*>ip}!IqDmxJP%f6;9@ig{8}`y)0iZG5cNHxh?ju36sORJH-pmAKYj2?A3J^#)7Nn(<44N z)?e4!*U#|anRN3Nriz~r`Rg+#+wS{*uX@^t#zg7k0mr?eE8DV|6x@+BcxcJ1U52T_ z?#ldQUc8JKCjC3PYl;9vU2Ui{!;`N^&#d?73J`L*RAJXJLxy3cqT3vXf}^0zAmeRy zzW+4wVOlUH=#>u3myNsLe`a`bC9-&`<@K0{UlX`k`xmM-{9tx=m_O;e=iHL( zg?=m-xa`##f{GagJeF7Fy*nrC%<$t^djEY-CW%Wcrw17@iq)$y@-Y6NkbdB-_deOV zECKW4vxFS(f6e^-dY8E#(}~jlliVt%><*_svv&TekZ-8EwM?`=-d3GqmEC-ydGXH{ zb?a%_Us|dn>hV1H?yjq#1@2z&-rm`H`OUq(x8L0R+;G$8z>6=yXv>K<;pnE z&3$ap%kbmFVg9r0>%Ojz%{ybi?Wx_Z9~=!kz4zyxWxc1#)9|0o>%)1@qM7w7tqcp2 zS)M&zW&fRR!rNLFhG+KQ?e1);aR$v?G{lRmGd}sK#ljHw+qz-@?CXrTE!wQ&6khMh zxVR{Ti6Qsm^63kd7)9#OUtBr8{=)1T`|o`0WGImIXo!$~zv}brU(AQUzn;MO#F9bd zpS>$ff;*cyAcD^?ZB|N5lKy@}~k9N&b3fH$Q29d;r6bSK;;hU;VlJ>&|n#`-?QQ4$HL9EzrV#f?EGKIZI-T6dtPc=;TiUSiTeBhY^r>__4*m<{5^^HDxb@K zez*Jmw>2;RKQrGyQH!DWcgA5`HUU0{^9(9%HXnYJZLav7V_a6@$Nh}y-5<+G-tV4Q zUHed|;hDh0_<)Oj0zfQ zyy9`{%S%hcwk8~GV%5zv*=N(Y?FFPlz3}K#=e3e0#jj^y+n9f=XuZ7)pUsyelLM{p z-O(_czUJl6v$Fe7{$#ee)RUgF`~pKxdFm|@hT}^acGnx;%dy{H{|?gL>bLKXVVdC0 z_~`tSYx5g+^j!2;Wr$H^oWt;;SH8H`j!EI!FX#$6nMwT>M}H{ti}W%mOpvSE*Ya!E zo@q~R_}bf_lz#B2Ti)SP1&rVEKp2pCy_vf?O*Y3`CunGGr`6HY+_oOfb+k+c{ z0t`Ri3o#gYeG}*Y=d8kT?2%C8I+X^&cYh9AUAOR;y%%!3_5lk+B}0Kz!vRzFKZ-&Q zRg52O7t9~E%(eZ`XANft z3(f=mfgBBQ;!Rl`nE%zx7iNg!Xt3FTMSua6V^`mm=liv@aMJQDz6O~B{nxR-pDDkg?$B#l^+Do-uz~XI7z_pnkqn{(Elwt)k+s-7mw#=f3{y z^!d3G!xpo7zhCcNe@nvrUd7_bZ8h?~@`3u} zQ8w%LKYlhp<;CI8tP|P~9DM&afRkaqRYS?g>l_V}%VRFqo624HXPDPqIqC1?PxX7| z#bwIe`*!8~v8>(v>jW6Ye@t(%Zv)TZRCsbS-q?~kS(U*?sJS+bqanykGgag0R1L4o z6VI*9k2=2Z<*z%h`zLbpd|e(|@5gkZUs0W5)~x$?KF*$H&-cTbh2ft};eqppWC~E_T=4qWmY=+L<@en> zf0*IN##NRKCJpnQ#XhJk^R>5+%xKv8;ShJ|hx=^PV!IU?Vjf&|XZUf;gF&~ksDt4I zh^-o+%}o2GnxGv^u1phJ~(-svz}>z>#?POmz>_e z{=@`^^AdiU_VbtzO#eDxn0cL@P{?i$hL?}{8_qb|vVT`R%HSxm;`>2Yh8^#mrN6*u zX`U~zcVP;sV_^DIvpYHM`NR2N?i`+?(s085;|b;d5?h9Vb2gvPth;E(&$#33wP3b#9=6xexc*W(Be|aNqDQkiV=po#DgQwO$N6 zj-63uh)MQhI>7$Hl2IV*u`)w}y{XWFP%*u@Ys@y)hbnHYGLEkeb7Ii>QUCR7c&;2n zL-~<+rv(}~m=1h<>A`aAdi?)i*WH8|40xaHPGN9<(x44GC-8xT-0_{|4DA2EXr(gQ zCe|~b``z$!*>kzO>{ZWBRZnzc@ObcEnGrOC$C@CwAdab4T(Y5Qn*uMW7ZT*w#K3_Z zF1s0Y$~X32{5b97|1I*~3{TGFzx&~PcE0wvcx48QfZ1G%43K$)Ti-S`AHSyB&ai<0 z>++3BP7Df9r*C?7-j+3}-ihJI*HB)@3D?Dq_uFYYykvB!wd53d%{W1dDS#ccJ^fWx z-7x_M#t-qG3=>vgGt9MUD9ddRd$oGk&*ef4`(&6bA2ME&c2;KCVrIwWpvbuAd&SF7 zzSkIR9)|h3aXydHKL4c2FRk(UzvJAD0v{MU6d9h>Dtvgqo8N`$#P0ypdX@vOOd*_% z9-0NupKCGLeenOvA?|R)K{I&!mi^7KyWea&eWpj!IHykfSMOo7zJ-0;b(cz>l_ zNPB~w?8`g9mX*Gly$&?qzG0pHT|=vTHtY=NSs5N%LUZz*{Sy2P`yTOgI;;SlT@=S4 zK9j{@Ik@()RXsV`s{XqXr$PgCF+hNj!%cfr&IRZH+Pm;I*mU?ZX`I(L?%`0T1$^7TPIz6r zs=Ho~QJ|{6a@{O;XP*}hDhx%3HJw--rW*8%O7}%X7r%dPTYunxulc=<*zK>D)T%z{ zeE;v$$NpUhCLdUS)~o)-*Gy@q2d4xNoNTxuz)&Y|!RT<$Zw|wQIEGlJ1;z47lNpXp z0L@-56A5DU@MP*Z(zw)zpNYYZ<-qiPUsSVQ7*3QeonmCb?*y`@#*G19a8$U#dHiT^deMkT5uM39al7ik0R|Ne8l^9mFRIa)8$;M$h z>pX_Z41ZbvWX^6-2hGNAxjS{K^{vQU{9!cHIAIfDn zQ>Rx>4>DrZtG~ScdDH{{2k}*mEnx|s$LD%)FO>ZrAzK0MmiRuGdw*1wVW)_~yQzpq zgB%lTqoG%^VaJpCcluQ)tctpP{`{RPC6n9dotal(wzU!2Ehl_GZaGf^595LGC$*)e z0t}yGoE9?MF*Pt(%QMzL0F@T2+A4Q&Fw{KHd^+EQf$7hdUp_4j%?uKfk`8`MKb#m| zO=5ha!m#yeBD0}U!ymf~U(d|9-=FPr7Nxmg`^;95(EK2;%q zXDr;6w)1@dteo|91rnVVNg$SV(DG>c;0GeaDwpHzEe~q$#lSzp{~b?;g?X3 zY8%55ruu#7IT>_W>K*3AXK^%ua?RCu{bJ81-j1)%{q=KZ^m&^(H7pI&Sv)|A>s8dm zYqw5>*55lnwKYDkSjgd8`=+1Eul+n{`sLm&)2z?R&z-Y{of&ox`{wd>KNkK3t;6O0cTuc%g}qVzNo9V9SGN|#t1{H= zjIUX6$erPW)()1>MXB>`7(qx-zf zf8SnnwLP|f^Ob#5g4moG>I;+@80It=a30$V-mh(9Shw!nmazG&i+$~nOj`P!g@Jtm zi(TgYe``)(&Aex4vQOJlzW?I&bMdd|TkqfZb76Mr&gZ8eM;t6)`{;A-mD9;L)R^49 zb$(>%xLUY=74v*+yJw-7SE^4hTgfzG7E8b?h9hc>C4vkai$D`GOSv4T)~htEdwm(S zh;k+4mplIqSsW&V(=WpW<^#e^7u=a992H=&V|cB^P?KB4$-vvveu617oSjkNvRc}| zn72s>_iLYVzH@!rAD$gt{!AyF8Im{}*0DRN|CCW>szL8blsVdPDKl0%G5xT+wIEi$qXvdI2im`I+oOZoAE_KLWHs5YHdSZ%J25X*_E6` zjPc8R%P;vXzoKPdPhyzEaC_JPmkYN3Z}yeC)ACwzf*fCR{LfS28qe<6+55-+ES-MK zjIpTx-P-M2``+BXsLD`d)ZhYYQwP+8)&WXL9*B4#ug!dd6Vz2tg>Abqn98u?lKnO2 z1uLJY=hir3!KFRM#>7NN{g^F36${WL=n z(*Zkc>-*YWw$ED}E`M3~>>)cXDF?#C|@TZAGMJ z20pKk&+lx_(p}5R)G)t36toxI`SW9@cavBi6hBj9OpzD&R5;1PCED?o;X=us)vpeK zdl$vmD_$~gNIx%^?ZUK!gW*b`^-IPBvGq@rIUBS*8Jv-(mgcTuahMBAne*8lL?^_a zdZJZ&=QiJqH?Pi5W^xc`*zTw<$iQNz$ZT2M0sA zyn9;R=Jy8^CLdoXZ!8K zu;tyo@7~=1uIK$euy5Ubi&Kqt+`s20>ss~w_4H!cBC`1{7h}MwCDZdP8Dm)3``3C; zKWE8kBxuUXu=WQBgVFpybGjLB>?}^7E@;JQSC2BwC9TFVZ{JlN+g*or{=V2Y%`|6w zzppCOgN>r?RU8fe$L7CMWVB*&;C6d3e}CSMkHOsk60?u-&lWA0S2%Eg*6u&+#BvV@ zw3|(yz>wh@6%Sf+?#1Dt>8#37@yL$vz|Ym$t@lDfCrm^g|FP5Ra#ZcQbEWk@LJm7G zou7Q>=!*l#RTxVcEBzRLK#HAPKf@W#WIkknE&8X*DEydl!OI=tR~ygE?VrqIuq4>u z^V5=ZdwrNV9@yL2<@HWxn8Fh9&EJ>lMe%?BiOdT8Ns}2)efIWcda>Isx2~3RSI{Je zXJ7hN8D>@0J-b%S^q`4bPs9GwQfU=YPX+TSi_b`{l@0Hp7?qhN3vaP>j z=4M~#x45s~a58-UPOgTuTR&Lt>=ayZd^M!%>z!FY-|+;)r=R^_859KMFBP7@+6bO3 zyFv-*tLUJM0~J(-ffJ$=8Xo8g0`=l`Q}<&`1{ z?`pmuEY*+61t)4np##_Bs)Tdy?YVg%Kx60qM^C-a{MpKV_UiX@#>MYuXYc!Y>QNoj z0%xX(_Y5r@fAsthEcwl6Tl`>|-N?9RTeW=W*Y?+EeV1R40QDRAa$~l=_~kl{VMo$6QC)VLmWEDQ|) zUgfs$vSi@+Q-0>m?$XJ*#$D%irt>y|wn3e8;Ajw;TkU&%=gWUPt-kHF`hM&8WB)IY z_2+#6t^8OQ)4$O@_PBNVKZY;!E$YA4&2N3CllpH7PqHXK!@E`YAJ4iUxsbmqKjW`4 zXyhQXHM&n9HtgasKRP{$!$FU6K`XwWKu^)@XE0Sg(s8_$G6Sd_8 z6X?3QXaQCQ5iOoSt63OwtOOZ0>;0bf^Y;1qvE6fje}6uEeOyiF&s*2mDHt7)y27X+ z$Z^>0?RLhx{*zU=mKTcY#biv=kI!THm#9!Tp}L{5!n!>7!Tli4hW@Xzq*tCUjP4r z?1uP%o%~lX%YO;H_VL`@{UJ;WsdY=)8O|S`Fp0S#{q^&kKa-0Ye*Ajg$FCL%ZVuI> zwuffPBurIl2x48}CFF1&Gy>rDNW8lKgH5Nx%PG{Z-7A@b1pe zXP4Ul?@toddDQ<#!TUpu+GiVXp#$0b{(bV_Z{oqCchBbbmd3rcs)zTzmWZ?7)D(NT zJoDe5pJ^W-9i1kS$y~ZVw|>XNsLvN}OkKwNWp3N9*U!!{8RYXG)Uj}lU?^gq5ac}n zZT%gc-LIF--1ud$-pyvv;Pv+T8%{bgY?=Ss_W8@8))#6zZ)U9KHQroyHmdk@@BB#& zA6O#(tX1V;-%wU^`|h)!e{Y}3?q6%z|MqT@+5LYF-8=@Xui0h2yHT>Acj?W2_1W`p zZmFIBaDn`{3$fj2g1>Lc{hs^m;N9;MPx=ZZ_c_mdetn4o1L$VE6IbjC zdTrTzUHA->S!$71|gw zKK)$%b-DdU1|0?!)-%`Hez;F#m~p1XVKNJY3R5S;j>1R5i{D0ntIhf&k-M1xB=@;_ zwzq{CuE*E!joeV5(5T9=;VsjD^E3a$rZZHe-M#+%fPLTHil^b3sdXAG4(d7$B20hE zz8v0X!)&qaTe8rE(iR4re?J~Cd>^Ch!Z2^@hFu&DytxHk1{@6KOb`5J@1^+F=AV(; z_H-Z1n-|@NPb|vTnBB17T0JlG?Y}P5oX0cx{~WISvRMA-vyFZQ@@b}dS5+E9`fPsB zseJmj{7kg{KZXVSUBA8a|6k588Q`VKtjG|AmeAL?GAv>EH=+C8idPlS&&B6)G?>)~ zwHd0iGsN$F8dSSQ)*GC}*T1|1Z&pq3lwnjbJ0`?%>+e}r1{3~Vs=0t_3b->W%2r{=RR_rI50 zlUo@yQa8vkY_$45tFQR--{R6Y#ij4IeqZmz&~m4$vi5{)c}U5g;^*gG8TNc$?Z1hG z;ib~qyZ?8JBzLhhG@A20N~(8esDVtDE*CAjw5?s%W}BWR3q$P+`(9CDCI-#@Q$p+I zLB-;S*Q=$N6xQpf)ztf|3wDiv-yLcT z=#ofeTEN+_9B===qKCm?3voL-5`I!6=QW&OJ%*>NVIMdc+;B$}ni2 zpMK~0`fKyko!o>DF#TF?@?ie9oy=T}0sM>y!ryV;@n&K;Us3fmx8lAC6T>`)xdjZf z#Tjl}hFfSkEm!>Y zyaGd%C`ZE!D@O*G=ne)AIR?|oPub0f3{cis1R{Ie?`uk zPKpd7Yz)f%4H2RYOm!>|s=k@d6kiKl3)4Tf0oz)b`eXca@7M2nzr9SaM(@Aj_2p;t z-~BzFex>}c?^6bk%6*_S>;9SehsW1$Z4|v=|JI>ik(rBef)4}31>pmr&h!6G^CmNB zvVd=6X^45gu{XK2n&F5V&!1bSc}xvq-!}TT3Ll!o!Xe0@*vc`TsYQiBWv0V%jz7C^ zh43(XsMa$k-MZ?;^r8AVCxa5>3sV<{C#%-Q?D~u;bbDXaalyX(ug)>S_d(q|L^P53>#SD#xq>kGpI z!y=vUKfUies%SNfUa(*5Sg**eCdv50z5a{)&1qf?0t?Co8D##PuDa!~$}mB=A^q-h z8%DtfT}^feoejHdUgl)$oj3mXhkqi&9VhMYijQlTus3x3aL5X)G=wl-;F#*h*$|ST z$F0n;t6qgc@)MH?(*n>y`>ej=yuHhW=dV^?+k5{1oYVHND}H4D-74xMx&HL`@KA7<5l51-`6$S49ZuPolZ}maOwVXCV|<^4K@r;R_}INeE_fR*>Xtt_;kpD z4;m~B^xqj7u{bhYq%J4vuQImxsoc)0q=Z!t{Z#&#qh%C7IOnYsh2a`XO2IGDeMhii2 zE=E131L9%_`mC|o)aWF7_Rpe)=yW`F9rcz-x14qNpY0s{`uhg^n z9kc7b^pu_pLJTLq_~-p)`BkgJ(8{oYUy$L+z5X9{ehfPt?AIUG{uNN<;Hgr;ebwHtNAB(WCc&Iss><};J};qkT7-G7|G$QPZ$+3CT$nOg7#jARZ;h{F`CzwU z@$~=U*&Uys8~&a0#eWskhr)|(CmMryKbMSf6`gV`ltCov`0syvw*0Tx{xUO6{O9b8 z?hF+N>b->+mx?eYWUw&o?0)vjBl~fT_4II57Te<0lV@!k5lO$;l(@N|B8^FfG#;fr?Iniq^eJ}6}V-@mObH_kox|KE+%)4Ul2 zAwvPXy&3iDSGqw$_oWNNpM&y$9JjS{G?;zpS7V5YV+dq9aFPG)J9mHgv+M_MHL-H% z9J-Xfe(yD@ZG}1_VgXYP3IrK8dNRbSG)!YD5E5co#`L4VYvuZGhKRjYTUoz=4wK5x zetK%E*pL3+-bheCXWivv-&T4txJ+PZWzeu>s7ki^QEPQ>=hs<&HS7AenAQ0R9SEq` z^)Am!D~`V(p`_3n^T3i})}^0$L2MiSL>pxI8GMiLUBy>}SOF?kziNK!mtN$#kkY%e zZ~R_g$#CS1e;2#M-FX&wm>0bIT)s+xf%S*D(gFd7vw{p$K-aE$e9*UL0Btd3PPqHO zlOZEohmm2ndA{7Ftmwnrrim}m;(VZ`D0x7D(}7DTh^;_Ck)fzZ%9$Z(FN;>{_4OWB zH}<{0wzl5*?SCP$2hKA&{;&QRx%2h9-D^tsFy_eKTA=$YgqKl+#Xfotxcv#R)O^zNjho+|b-Fx6$qQoVEtOTIrX&I=E~1$%nkiNnQp}I zt69A8f4$L-V}@52^4?nH@9W9n&EcRqUu^oh-OC=Yd3oUZt257EUC91=Oa95)=9#>2yn`OM2CQOuKuwS-zK^CH%o^{UlA437BgC1*#y;&6aXtj9I z!tYoM#s|{&C3PJ?+kbL2K)rdq{#^g+x*L`ZPrk425@d8(oyq!RdQeA$6KJ(G!#^9& z1*e4_nCfq>lVWPPk>kg}vHAGLvw%s(EytA3@zaE9Sv{r$~px3}do{EuZ~SkLgUcwNOkscn_AApaUQv@+CdES=8q z;pm0kC38$=f5goF86#V@Htu(GPxu7JFIBJCZZC^Z+*B8_z9ep4Ro%RWWee*~`hQU6enJSQ;iX{E=v7IPp(Yks)eDx6LubXt^4aGBpvlGdp@@+e&)lh{b2tT zCngal1vO3ub9qLE+o8TpH|o-6n`U2|aXWAKS=N7R7xLv^D7(PI!2YF#XXd{|7lwen zIq#Jil6;gIR6g*_K0dxyC;4`evvSa9tt&1JD^`EL!_4qsmgB?y9ZzbrT{r?d9O@4+ zE1c@$Nzgp^X!1qwmloMpA9h-O-f8tEYVSApnGHJMs}w%?-`*1^=U!hIv+d!5m_GeZ zHt`A5AD{g9=WnX5eE7Wn*Df}=7wGGMPJA)@N)O(47Ms8z44YmH=^^g9_w{SUJRq5HN5MOtGOUqn4rUkjr zKqGty_D^wQ5n)o$V1Cj4^?4S9%%A6{7?^VOV|QI)`pqkCCK0Y=8?pS{u~KtSCJ&|q zsY?VLo@F_^G~_?Mr}p~wj_;Fq9@wqIP@}@I^Z)My_Pxv>_7^|*6FpL&F2LYv>0HFg zFps(6fPGvi!-L~fWI*NH(mE!EDJOS!`NVgft69glH%$ID;{&ex2?{q|g?7A3I(t35 zsfhDKtV`4Sx$FDO_}2Zp@Y_3yxNd~eUtp#s$b`>RsLO8!pSh7 zM?sJwqucI^d~&@fQ-Xw1!yJYO*Pj>vT>kp=)zh!LXWo~Pba?yEY$kKV`FgJzj*~ad zv#DtiX4t^=&s!680$>M&L!5vBgMs*u@`(%#|AmEx50*)EGoL&AU9RldALZTu@9ZqL zIfm+QZ#KHAG;}jK z$Znl1$8aZfA0Jcw0ahPVrUT`*p_~krpe1UcuBYr4c84BQ#vkh1i~$YxicDOL1~N%+gLBZ;=3TjhJa`##%XJtb0}~IH zxNSeZF0=i+w&CtX-ZxU8)}EPfe_u$)Zl}E$pT_sj35t^!vefG-w=x*TsULUzW9`#$ ze_A&S$3#Z1j(*E9Z$1WwDgmbtJTDpka>JI3trOroQ9 z{gwCOQ)6T&E3zzT_zx;15-L~@Scc1)v-~r4(067?@@$agu1i?A=VNC4Cj0PX{}mVy zJbJS``M~4^0nliN_?noMy#fggm+B*Ym+z>`v@UxiVadesyw{?F-y#2K8;gkxgUV`# z1qBKG(L0Nlsxl|sSoYXO#3jc4!(T7WrRzO5oV8`Bu{t-4uVx)62=<1}f5bT9*DI!P zLJzJ_Wic?B$G>1VL-m=jOXi3_Wtd>e@!{2JMgyMJix$hRejFFdZdd)sPw2n|KihiM zlasA5mPx2`CY)Ne-=|1?Hu5UZ;)6Xq|IcD?aAV1sAODUqz%?~NfJ@ef^FiwLShH6T z_D{)lV-aCW*uu$R_0F5=#cOdT28I(UoA!6HG?-Qh#MJ-&%Dg+h{9dKHAjAI2);u%T zXZ$@;Zu!olNE{-1xfkzZrj1lFxstil{|pk9&DZ7R!*bBYWyXD-`mGJ6#GGuU4_FUqu` z>}}NL&ZpAU9i;1S@q$l@I3}ZKkuAr2hW?^)W-)Wk%eLH zmf`^2wKX18T@_#~~8KaH(iN%i@9@)v}GrjV?c3o8I!WHfQkJH`ipX{8Y z7|!Srd)eyA#*-(DaCcuFzV-7muIOgC;8c&&DCp0g^1w!HMRC*R@BbD)U;JL8zK-va zCG%{<8-s|NmXr+UGf$h2dF$V@<`&o0csflQ)EaJ3D_1i^J5b|5rU^u3!0zb+I4A zoBR9sAOEoP=BCtc)jJ6{)QmD;Ut9ZaGqY0m!$Ym#elrX4w<+Yq8t-UC6t<%=g*7 z>i1jSJXI#%PkMiEZ|!^Q+-~i_dHS65doF&z`g;;b5ySnI{{jabWWx?yJb2#1@UP*L zmrdWr!@f*E)Ltk5Q(zAH{f)Dv{o7CN`u^+vlJk@P#JaIGoMAlhIymLTsuvTEUu&PV zBGZz=ME3hCQ1}1w=CvprJZAYYop|q`gS;;CZQ*zSJ^zi^9Zr33e%8Kc=D#2P|92ek zxZ$Ok@Z>lDB!-OHmY>fUcb~4;R8?6Zz!1mOaKNUq!l<6<_oXd$6>RXJWJHPJa z@;TqPU#s~V^?z~w|NCn{{<`s`M&`!uM{k{f|Cy@(dvb2f0*R6i{ZK zF{{IGGU_H0H&CbEl40&VeLW_I(w%Y=%_^^$w}x>s?ZH>5C3JSx9>7Sj{&f_0M^q!<@8*2-%N z)GIMC+_>RYpM6_adv{U!jkKTDXFkfB3o>q)5ILQpBW9<+DudHb=BS0=y?wN9m@pb_ z@?_Zhx$fW3=bSCucj!iJI55lR-Io<56*l+ke!so8V*Z`isumKpS_GHm%B{q%RBIpc$>|1TXFL_WlyN>OH9#?83l)h>PZ$46gpk$?B! z@BY@yr~cmAS*&Z}uCw3&YikdKjnJ)$>W=j}ikkb+N$4Kc5@c8>S{u*DF#UK#KLf*t zbnU<2wtxJxu=3iDXSK_Hg;^YWX4l8EG`ya9p<3}yG~nZNv% z3>o+PC72F~GYJT1HB{Z%y;6oL;6T0RW}Zb$FfhLS%9+Z(KHPGvh@l|TE}V}ce(x0CC%0t|m|wMDV(e#ivp!15 z;l6wY=Y!cRzVG~VTJ%?<5W^j71<(M4&<+j;`5zfk?RQM;*047$17{>yyG0dSv&ED% zK~(nF6y)8k_q_tn{4Ez`(7eyTj!EJEY;{JT$gk5qL)d4s2!tJ|*Q{@4(O@~CA;`dT zVPUwxC8JH+?FG466G5k^f4ZsX!Jy8_;JG(~>42!zgW|9KiVQp08?tvNGC909?qYE` zvtzQ;J^g3<8W3 zu!`@&QpO|ZcjHg}^L3LvV4YV#E3-G|)zg0)QW>_&Fx+8oc+R*$fMNdG`PSAE{IVY% zPcUR@F(}G@kCFWW-gV{sT&`ZjtVef>{e^GPA&hU&SsbqZKF`T;|A!%{6Vt!bF1N1u zef_&XM@}$2>#^66bWr(nkSmJu|56dqQRR!>7%YmIzcl{85mDb-&+vlzmtH@!C({R~ zlixW0?EmxWv~A&E#RKc#UbeX7{NSYe{5OgWN3!bg+bJ{5`JNyoz!0eSQ}877;#V;d zx0AO{*N-a<*g8dk;T@v`3xlW!bN-S4 zAFnU?BER%Z&Hp2-pIuoQ{E?-8qITMxcIF*D$8YcUc|G5HUz$|Iv#UWz^8SsrKEp zsneUYC_rNK`zXccGla; zGfpU~-S@MX@j{9H%Rf)+&V9|Ef8SA{VR9#f#)JJ+PWmy4FeR99FvK7GZ^g;rX>vq> zf#3em22+N5BfYokv42cg+H0*d47gYMe6Ac5L*z})FXh6|xAj|cFf8Xu;64A$QJ7(+ zCj&R*0nnN4T#Of@k8OLmb=%tmg53Y+*ZixWs>k@@koNlM5C89+yq*&)#CYKGo0Gx^ z*soeYE2*8;SLLyMt__EiqMDvk*5hNnvBjxk z%Th`=)W=5WZ*JRGG-t-o&wK5AyT7yFc&@_m_VMSF=eM5vZT?rvc>g|u2CH@cziUit z|K56TUS8wNR$|l&E@wj&7{Oh`hRF;kKJ~Yh8O-~*KWpV=h9CdF@8<`NhF##Q<6~4f zzTiE>bcQ1}B@OnTjJJ|wxIV1B>;3z{|BJfUZ`NC~R9-xFX3GB~!u}wSCA4STeVeYo zXVunU$MRRM-_mRM!uzq$otQ@+5|WzsCzz8C8TFgqVUG7%~o8{80CA z2#@3AHTzG5*WY^o{U4rROZxr! zYMrL>iPs*E59I&-`@ikkWXUV9w$J}=zxHEQFT(@912&q9I~lyUGR(J8WUPwctHS!D z>@tf(sN`0OnV#YfPqnYI1avYi2ydvDQ2Ft0=X1I28*JTuxo1w#GR?jce#=e%%hHc! zZ*Qe;KW@G*>+OwA+kW5s^6!iK;nMnji~Sf*d{$lGr3OmlJd+qE7`2oNFnp2yezL)j zP4WQKKku1dpfMk<(|;?E?|#~Key07O!}c;AOH~-BB-p5NJ}}^DD8FBOo#`2`v{}Rk ze#s*u3;LQ24Ccive*0sx(&gRKkHg(lZtdFa(~Tw7>q( z>HR*e|7u+r7;HVXDiRx3FnwU($pjtkUlX_2O6<>fK}N$PT-1k#G`6nR>@Fc z%D}}rL6Kp@NiT*296ySU7#=dL{}-&p=+Lm5%i;OEAHD(%H8pGFsu*mx6)!T~Sd+Ph z-9eF|gh_#op@3mMUs}Qg`&Nd6R(7tW13g|G2ae0v=iHxtu6^x?)e3WOIoTEd`}4C@ zh@rMps_*T-i467LGuBT2DZ6Ak>w&!rj8*%7hsY~1Jo&7o_GN()W5IFR@`#51|J4V) z>v!0FI`X1|`G(aN$J>?N=F1uW%P}S7T;BZbDw}x)mE=lWk{H;$wN3}mA@wV?a*d-IKeujnxnI^@cvy5699KO|`-+swJ)QQ1KTeGx{VTYZ3up&!> zfF;8$E=Ex4pE!}Bl_6sqLx-*u;{~>-%|`l+0!oZAY#ZJ%{Xgu*#BsRt^Jk_Tml`UU z_nMv%-)7i2@kzUx_@=%63%9} z@~<2V-ygmj9-q5z|G!^y&bCkVCfe-a{jyduC+X*>r`I<4{$}`}!aU98%f0AZ`_hhw z&)JsoM@^sefGy)9TZSb^s==4B8(Fz)n^}K|CboQ|8$}r6O*uI+O%%s{iQTmLwZ%_Y9 zJjrnE3B!bqe@ag^w0y-~XB?F7y{NdGI(sV zvSr}-a5!$qPws0O^Iy$mcMxL~n5^8`?%DCnSJr!CV$VMDb(=CSD%CIl`)d1IyWdCm z*Cy3{dZ@pbO~~ozeL*FLsFdP&`wley_rCsTZ|-vi#$9DUKBhkkVTjpy&1T*{&SKC1 zg_mD#)%ic`D8mjl#%*jLJ~n(5WHEZleO~i{7gzAodB+V-UoGNHu;4V%Ibfc_Aj-VK ziNW*tpQBQHL=&3XW&|hRS7(m6UjJO(g~6ANNkRNUdl3syGq}yQ(g)P$dL_iL(ZylK zg;N)c!VPVVHZ}eBVdj$ES8H|M%bd>7#-SgZ7S6K?a5`OKv?lHT%KFb=yJf z@TDGIkDhs{<*))n1s{VFcA?y5BJ&r-o38<&gTA^sK2{wgcXGpUR7}(_-MnhWzspv-8~1-uUtO0 zPX5OASH+wEFsmpU$~Q*st9_II?+jCte5FH!<#xL#kL=$FurzqBOkB6q;B>^lzyj8M zFZbBh*JG9xr}D%~eQqu>oX@r1j%h*P$NH@T6B6s|W|w_juk6my%8S@0!L&bgu#tWYs>%)zl#W@e`l`H0CVEA=AKlawyH~ij#YT&!j{v5p6 z`)segt$6{{0zOHHlyB|X`%8XLzdbjgF|nHAPyD)E)#**&ch2@=U}>mdo&4;{?EIPa z^}nWo?dhD&x=dNXU^{{-1l>P zeq2V>_1G`g-8UTGOw%pck1@eSG8|z-@lhSzg29#YT)9lF)uBh=X>)qm`*%WFd_2U z@>A0rvKbXQF1m`(oN1gsZTiY=M)mq;>3l=>yq$;E?DwhgbTbU&NpuCvzz=^N(zge?1Ms9d>ZH&H zTlec{*YEwbC+@@jji*=|xc3w{%nzQwS}0&TdqY3N1Eb2Aw)I_S?tMS^ca7<)=|=UZ z&)J`z<;}FAt0zI$sMXTynbBr@d%xdvR3?1Yi*%^G^ZnJWZFa)!};~EKX+RFjIE#8;;`;a{2HGXWwpi6&KORQ zEsKP8=~@{wCNMluY2bfz`tiO!jCxE8EdQKU9C#QN#Iu}O4xF@Jw?R!<<5w3C^8=wq z(fZ&?j}8hqtl|2%lHrE3qDt8J#1p9vZ~hzPTrS)ax#7>hs1OD=2Zk9lEYB-2MA+$>(k<3`vhJ7v_TR=cIT-%kJSltqsip9*KfmneZ)B*sVaago_OnUedYY^M zFBQ>maAFW)T9C$cAbkRd3b*X!W1bCfZ*S-CVesH<^T9U_Mz}|5mR4W6RFm%Xia0 zc13Tzt2F!b+RD4NplYz_K==Kt5}ySa6qOb%=Vko8jDw+-WyKr`g9*$B+!-W#87%7l z?5NT^@uA=6Kf|9bkNAI|um4&<>;Dh&`G)nM;{W&O{jnEhkP}kqW0)WWnhV`jy-wo) zs;@zO7p5~G2)AD$!=T4dufbSz^;e)j(+lQVD`vks5O6t=-}6YIki&g0t@DflSO3~y zNw>N)KTw_Fl@HSk?o`jef0n5+%7pMSRMr}CK6pAkKJGxhCV04IiYmi7i^n{hb8nj& z8yY5-y}OgSq3T|8wVc6hbvfhH@2oP@bSf|Zd3*Iebb0@;Uxv~3TeCt<7!?@nd45cf zW8E>&v6W$o3S*7|OTq#c28O*G#Eg}XjBNIca0E@#SsfOZx#UTuFzn*W` zWpr4`kfOi$XP7ys!YRw=a~|82KPg#I%<$vC|Np=CE3Uu##PA@+K^1iV5#NKQf(y>C zR8KhH%V5v`Z(r3}W(7C4dIJ$AgB(eRyiN13Xyz}fyJ7V#*pV$@-L+FP40f~a%ge%Q zCo=ZjpU&bicmDhsNr!pAl|s`t^GQ0qD-3Ww=090*$?K1?@qIiAg@5%^ceR3QxKBb1 z3^N`-e$2M|S)9QB{~!H18Pv?Q0u)lUHyz)^BnYV%V|FQH`-jwVolylc83{ zAw}pwpaMfw*Vira#n)qHo@}2XxiPnwIi-d5%+7at^W7V^&b)r_cCP<{d3KxaPOCBq zc{GIaa5@}M@@NRQ|9k)04gPu0=P|@q|5pE>RrfJ}{td(W|KIEHPhEfT&%O75!RM*Px?36s6mmxVR!jEu2)NbhyCwlkYHg@Jn;TD z|JUi0N-h=fhVp)1ytas6`oP87FL&?9TxOW^?Vk(736@;e27i901%0*QL8m2{R<1k0 zo_o&q+FGOKjOr3uDhytlsi&$LMVJyk2{BxlR(|Bd_uCiwqjNW0Nw!$_?e&o-n++H~ zo7dNWHn)uW$;WT_8C7`XJu1!je;@Qd?fbjE_1WQbt(Nbu-yeT9s=D<1%HUGJ z`F8vNef=K%+whwDKh?vp7=k1Zn8#IBWH3BnxBr>h_2YdrgLl|kFD8Z;oDG$XD`G8c zXPfWJ{dVW--9L=YEDRGJ6to-@xEKx?H0=L*TYkmg8~@(!|GV9`zTD&w`w?5Nntk#S z>9g-YztsQdyb?>jhaZ#2QH%9V4zBjKz4Mqt4qU#z^Y4yJ{7LamjMLZ|7#McEbY6Ew zkwIW~kM{DK@K=9IY)XyYZttlqzW%^Aw5k72tVVAS{L)mJ?TlOJT9qBD808oWRCY!)75ImPwG#yS9UPAF*JyN zjE`z$U}f+LVc3vyQAs`Z>GUK2kBZ0Nv3k8`^PYqGA6Yma+}gkNu4MY0z@=?9tA4gQ z?fSd>UZz09ER}!eIX51}F46SxXk;kixiQN$`_7FUGj_k*^;(9XdBU4-xAWic`oBJY z|GmSrzSZ1oV`UHu|Nq;eLFu3B>-u-Muhr*0Jk%QUVmph%g}ZE=tP8ssTIQXJ+i*A5 z=5eOTZ<(Z>`seDd-Q86)zxn*E>I4T3&Whs>Pn-V!{=U1Joj)x0EB{}$7t9Qd7w;$S zWLRUv_-^L&_=5f4<^MT;L-!VU@c+Y9uSjO(-B{)kdwU}U^IwNC4cYVt$I+IcJsRhpvW z7&WgrFfi2KW@M=Mc$`?>a>M-pIb#Ng)r*dH{kP*_Xp-|{Eo<4w#t^lE{|)!Hl#@dI zjO%2okHi}Nu8?QgQS{EU{Oq0NxIe)RAC5`q?^r&s>Xb9XiC#vwNB@^jk1Kk6Kl?Ek z!+VF|t$evPZ2r9S!p3dWUo2v1sA39XWk?WV zcu=Dur*d%pL9RGHek~Fnrj^#E`*zW6zGf|9S6)KhFAa{O_~#^Y4p$1zZ(*c>h%eQ-WW^ zyuS(3Yrg15Z#z@?Vng2DT_4#P?%006_dG9l%MNx1Q2)06UXp`@`<3kn9A2!CFUt=9 z^0@eoJA*;_zby9ok5`4R{Vi_3vM9Ec$xamVx0-t9aZ8If)JLPwVf0 zb96rwgH~hMSqX*=&O`A`47z7Oyl;FR$IIaHYX&pJtr-XMC%V7fY0huE#i${QbH4z? zfsZ@?8ZsRC#IS;yp{4%S@%AItJS-c&O>JTO|IvnrLBQ&}$b!IBo2?=I47_|iQVazf zM0TCuBG7c>`n1+}`E~Ng-sp4gx$t}9Gk)3gRcD`XUbkF?DPeB@3#*9h_Fq3w+IQcA zf#dFeUj~NPe}dnozx^A^$l&^Cy4g%c2Sx^AZiWY3np}PreTU{(F&rq~r!k+OTSAe6 z;pX{D3x^X{zbkGR9*;<7V2E?JIPms!`mZAm5sD1F+t$WU|5eT?Qy(8+v&~%XpDn|T zeIFFdKlZxcopST`-7_&Wuw+|4m?b2xeFi%F4iTXy5MnEZnbH814x>Ff%A}2t@7W-}&SCx9blX4>&U< zeP-~mWY{AA+|d5V*1~Jw0&cFaPi$j2(pB}gHcO6aqmy6p!R7Pwr_N!p;Cr;#nc;v{ zT~C8nzbd1eLS~m zpZp&F1bG8St_uC;{r8Nou`}Gd_Ly-2L&Ke`&m$ZHyg3}2UhkJ+I>6|VeLeM_3PS_u zzkfyFgk!JQ3w)X(AR(2v|J$|nUBCU~f3*DfnP>lR#^T?HYyaNzu77&;|6=|-)*cK3 zQBSusH1IQe2!5M;q@nh=7lT8=`~Hho@0sfv;+Q5dF+^SS=VClySW}Z{ug@Nl&d2cU z@Xjou1Bv#1PC^G}OpQA`SxMD$9*ctm!_hUbjyEyPV>!EOv7D9N7tzL828VSHeJ2?e z?%od*?GM~hzxMm|Su7hm{$EnFW|2^3xUj^xgu%hpM)b1?!<$=MSD$BgkT%a@U|4rQ zq|(9p@p;K0zhgWbCF(-L#7XBYp< zZwuyVc${FLc_)QYn<3#r?g8FMUk_R^H1zzNef|D3x!v`?@(c~R)#kc~*Zi+#NcjBh z?7O4l@oT0RRx?V}7pF#-yZ(P9x!$UU;SMvy(fV}x{O&N@SA6gGoPJoR@Jo=b!S>sO zy6;z-tEJf)oH!4hVqoxQRAQOn#hAqDQ1j$>*(&Ce-;xX~MA+U|blPqJogjPs0o#K0 zOE1-2SyQ*xCA7I(x{Rq`>%|*m84hUN-~agG z--g^fJ1*+R@7vSy{}LCA21CLjriP#1+gC6&bXAu8K3?}LO8PP@1N;1WTNoHzY`7|a z-4;Lft8oVV#`FEx8=2N^_K#;eVEt}~^0s$18_w0N+WVRR|BHP;=WY*HY}mZ%|8fR~ zlK;n_Yy2_fVPHI+jwSmM=H?CvVG;A=l8IHm79%-iNCE3}Nf76RH?& z{-2qwskC2@VZx-p`}rAsc7BU`n}6Z&A^ks1?{|DS#4THU_WR;T`vVyY-duaVXYZce zyzgoa&o8uVf1Gt=`>*1=9?8WF9;Nq!Co^O{U9;uawUwotmKsIvRAdO+De~*qEB;o7 z`jC~(dQ+!MKXqT-x9jTp``gQGUs>Frv8iF<-x`RMLzt9p_d4sbT@cmI=rbLZv2@_$;23@har5~j!RzbWm@ z)bZ}03Fm_z*6)@E8NavRf4bhbg0bS`{^xS+Z0F^7mA9W~{BUghzKa?R9Vb{H@YOxt z5WTx@@9mxG`62Zy4&36EZocnn_?_#%zBP-3*OvCx|3sJ+6c;f#7;!Od`Dex806K+V zrXoj_VdazQA@97j?;k(?p!F0_)YI)rlNc5q_|6$>rTyoM@qt&@CoyC_{rjPPZ^%7g zt^dbcXN1)GX-_`(dd=m>7hWG+3V;<9FplqkcyjuUZ{UM_*!-E^Uo!J>e4ipK%*{<#=2SbRQXeD~?}_;=2V2bDi{ zs?W>dV|c(-d%~54(Luv||5bYq{sya43{g!hvVQKquq|M3=|AOYZRQTWzaM^FSejnF z`1Xt9n)@ZuUXl*F-Ss~8FaCyVu0PRQ;C0f?&b@v`j}#NrYZ+4kq2PA`yYDA4Okra) zd64o}|KMDKiVf$~OWp>1xwpMre_@Uk!-aLFZ|<)td+S_(`r2OM&z2eA^UwS}$Ga-F z<=?Uog5NJcTlClPyY2qCgdfSP85%BeF-XWVe7M;EZ{F`0FZVyMd%eAGwQ0ue6ATQ8 z84Rw;AGML%^`}0VpFxVr;b^@$L&NX?D(no#X=gr^eO@D(_fL28pZ_8eN7*mCEK7+t zUtjq8`rT*qYCfHuAyk(Y@9<^zBGW^{4kkHlECrK(9KZU%`rS@;ZN`$^-{0Qe`%!kI z{QtSHd1t$#1sF>9GOlp!bnv*uF<-bw?#7z)i_V4qi+JJGQm1{4uin0D$@y#r(SH$T z@7L}AREnCrq4;_Dt}uMAr)*beowx7?hCsQ&b&e?kPhG4iHr>U|K8ru z{OEq|3upN(-djuz3&Nzu7#zei`50H+SU&TgCCh?G`=i+kS{N9(z6Dq`qzSQfd}5gK zuP(G<>gIdqcebURoV4IZO96w4nnN&ygSHR@3u~?(LxZKmh57Zx=Pciy|M%v66~lq~ ze@@QtU|{G!?Yb|O{avI$>gxRHclOLb)+F6FU|@*ZEW*I>``yj@{8$EuKl?x2Q05O| zW!S~ez{*hJ{NVM+N1&ZuPacBWRD1YpSMba$IX9`|`l)A;PRTPA8Ls`l6f=A21xDVi zC0%8&EB@H5Jwh{=N1(8&zV>xebnVe7I0MQ+b+mJ-g=o#ij8P z@8dr)oT!!8tUqp8Z?1V=(tiDn-@>d`4h+96vk%377Ta*I{hQ^HnB^;jm+$(P%*e1* zl)=D>f#FE~akrDq33r$oHh}tF%nU0684m1a*l|$C;QESIh6M@NS!#RgUtDK65dQAI zDpP}La`^7G@!yi}|Bq)m&-7{cIcJ7@6&ws<>%irB!`zHAv|NjsC4=cNG%m2M7U-^)~e?xwjuuEO{%;fXC)8_70T_9ior?C8b zZ28W)*5ze~1%B*X*fy18hhuhj{Pp^OxmQ^k0!00*{1_P`luk@|r~P$a#nYwFXP;$s zC}wQ9D8n%6!g5Q7E&m*!F?5_{c9>tx5O6)&qrt1GBxEu})_d(|e?1Rv>U%x4>hGG* zixcgenKlg=x+yxd>^(f%z=j656x3I*2zvKPhziCr#81B}}ioX1@Vk*NOi?7_aatv2qRIJ|LXVkFM zlEFlpzx2*pqlPOlxa?zp_P?EfCY7PERbLY zFum^O^!h3W2TsNn%Xt+1nNAr0Ui@IWCqrhW!>)XO28kp|2d!PE3<;cddcR&4i#)ww zoz}`MEyD2Q+GFqJu#Pq@9e1@8*LQBlx}Q(&v@CG=`+8B&>`hn(9Dr=>y)w7I*zUSJ6=e6*)q(#vsrrv zv%(a12WEy4iN!k}JMBDKS7n=V#%^!g!hd%jA2wx(u6do4|3fPG2Gc>S{Wt2)e)#89 zo*@!cQNQ$@xsLSJ^Y!_Qm>J~soV49}oEp5p>;J8j@n>L|WX{Uqkh#=Xi^G76f#H_^ zzeoCF3<1In3@11l8q^qh7#TD<7jQ8+{F%RLAv1%<>vkopEtm2u*&f7{?Ax~Ud)yrJ z4FV01`S0a%ZcWIklvQNlV0a+N^6|>*_)l&}3tzub4Q<)~ij$#ueRTcqt^PfaxA8Ds zU}h}4vvrmA_h)Bk|L$|&4r+&TGdi45X1G!OK$AVK(k&3#6z{F5*zi`F=kH@5UGs)lMs6G3;P*wiOJ{D$! zc82rH4VEk~UTMbrw%Tvt*56Zqp}U@o;YIcr28NIOvtQQympH|!(8#clcYd=2lj536 zxdj1vo9wT*+JFC{AoX+W!t2XdRy~NCY5q^bYX6Mu*Pi>^`dF|yFf8bmpVHbVU-iGV zwodrrMCOKF&#vy7^5UD?eQ~+wd)p;m+jl&^{*j~X<;Pl;f7Ill{iyN5yKJ=O%{e7UF}S>xnHRLE}s13 zshoWMI>!^vhgRjwt`PxUT2T^;gep!{&?R1(|#o)1e1qVaYC-0WN;PCoY)4drP z&N4InkXzhTA8=?s6T{1Yt_`Mf3=X&NM5fOT1t(Y`28R3xzh1AOy@f#`gta04&vHwK zrceeoO9qRoFDt&QNYA#s+v_`DOyu8{Ek+Hy%iq_Rt>5#hOZ8u~(^@_Thokm3N(>8b z&v;$GdeP3$=k53JKE(I+g=2^R-e0QuA7r*wn*aJ{7yT|GvoxBScaoKdp;hM{%5XeC&Y1qox$lR zgAt>H_9MAYCWiXcVt3w%otG~D{H$+hbiQP>YrQms$D{d74FS{Y!=f)qvodJJJrrcs zyvxSm(9Cd!nSn=vA!4p2!v$l;eGc<2-#@+=pb~tZ^~SbufBY*i|62Po-uAsu{+9b% zm(!>I-280$Uv9hanfY7xb1`&iW{a(|pC2T|w`S$k(pQWOO`Yl9xz8CAA`aO<|0~L1 zAi?19IgLk(!9z~x@mXdD28M{j|N3i9EO`=`IRDE%K2&eS$RO~BgHeO$LDKyE_S%=H zKn=FL`)59%Uw_ZhGjhYmjfUlSORrnXF)2LR^QlQp{KU2O@$bXdL|mM!o)j8(XWjJp zE=Gy^)o(P8iQoCfEy{vBop1C@zP3>>Tz7#x`87}g%SelWS=!SieE3@di;XJN?L&&rS?>8Q!F zK#O5Q5kp2OL(OsRj2R43~H}&29 zk6a9=Ue#x^GJJ6Y4cMwNIB+yDFw|E+F8b z`60%rE%MtMH%vK{lUSW{rrw}!e;2!hw+ZDo8m)% zd}m;ob%DSBS>D@SXQaQfHPi|)To7P*vAg=XqB|qQ_nrg$HZQk($GbPp;YTtDgU#^( zP={ECjUmJJ2`7gf^MY{qABVrYd@r33YIG@bOkiRN`1APn903L{hBaCYHY^Hhs-nLX z_svl|&As!2`PKMv4u`9cy%`wR%d<1g`f>f#wWC)!ub-;F`|#hw&!;0jkDs4^JiyzM z!RfffwN>>QbH4l(Ww>C&ut3y=jh8`#Wx@Ua$EGX`zzul~hJ;1T40$^?`71J*i|F4` z6J_}EuX&M0)AxJVRBKoNk@aWbXLwQBbkHo~W&F0BnU6hBU0)lmKK0*bqaEgt7zHlB zVq(a?zV7Y1Q_G|T?=DEgdl^ft;96eHz8~SZ%<5A-Z=B$)+P0N;tUQI%n~)_cZyEy z#{D_GI6vl1Fhj%bN_+k9;-|nZsH19(OX3*5RIXufn8tjdYw@yrONJ#z6)_Ix7vtT9 z4$OJn%yG>{?aRd(f2aKVSZaI7^1$ReBeO#DqxzSZ zmev|LeC|0g?Ga%`pf-gfh$zmLo3i!vTq7rWbqf#K$3N1JmqjhFK? zB#1KbFfU+WI3n-M$DzoUJMl&GlY_|&$85gcNPc&uQy4VL_J!?yqV$)8b{pPL_P2|i zeIlEKp}ypJ&8y(~MXSTtzw64ds2BK?An~1_?f08|yKJo45A3}i{;i|zZN-ag=MO8c zh~$0rKa-E)%dUmz71j1v4}}VvIjt&+)_YkHhiTuC@#Z7U(Z8 zv+bxp?es|g*TY}`{!G=5vX4K$W-sqyS*Q8-r3<&MV(?&!{@k^)#6j?9sl zsNinMoImwy{MDrM>sLMe+qg)c_kI7ad+t|XC;$B$`Pti2=f~gK#eYEU+_el1olFe3 zo=rQm>x}M>Z!zU3cmA)JJo4Xw!QsL){kxCW&HX-OcfuRrlJIY!aLRlvrW^HS^7Y05 z*<-bbSog9pn1t8o%KwsiUj1#N?mPDTm7yi`?d$$j_?Ojd{5)3QzkZ(iVcQK)vbKN! z7A?cSnBgMdb%7Rjes%{A28D&p+y<2kGD_qnoPWnvzuoG_(7?pB=%{_YRBUQ{He-@J z&%Dc1%hmqray(GsXW(J(u#C%_U3R%f;6LAcV}U<^EbnCRyYryS_c0&Cf%1k|j0&73 zF( z@&9sgiO72yv|sJj{a+WY?B&)t%;n&um znWEjRzQ5OKV-NwA0Dlu1SQ(x$H+1XGeN^}V|1Y2G|EBl-}oyf1y587I}fd4R;%7hC`1g5yu_WW3XOG%-DJJM%0^R$v9`^~?wEig;{ zR>t3Z>-%hW^T)glH9t?smnr;mv@S1V+{4eXq~4`2sLB4;dR5LRmQ1Vqw*Pv~z_3vC z!5x*FQq_;2AM!M+)a{8Edh)|#^S8^NYHeoD|0Z?UU4!9)0K@MU2Ru59mp9q5fUYTE zXoy_H&ai&5UMS;*NpCKmpCmtjy%qmuwckH8zq@-)I=SnVNtUPC&-+`Zu>`M5QViYH zv9sP?mouS0@~{3{lb^?v85ypw(f!h%l&ff`Q@sDz`?}%)fd;PCj?4_9DUQs7{Z8|` z3N${uhdp*JymwQbgCQaBgT}>YjdrXm73JU5S`(Zs9Jc*%pBTf+utaXL9s|QXhU<5B ztaAV3+<72#^ZSD0=j+P^#FzvQoce6Q!)@yGr~O(mtHY^B_4%nkuOG2wWw3m__Ma8Q zfyvcnE42@V&D=Z1sCH%QpBGvC>udkbuXG8Wp&4b z3EA~)6d4YLCjV(_eg0#q^kYVasezNj0(LUEFf<%r)^GE!CgK?Dw-jspcc0F`?-ywZ zo&Mo^*n_S6?YS8W_!)G=+FMKi1ZprxfLg4g3;_%datsm+#2c*te!1+*aNwwol;^YS z$u;(Td_jy1JDe{csk7NB&sBcyy84Tq?TgrimQ<=tV2I;nV2?6YZ@=vGk?qCCU45x* zr|+K3C@XsX($9Z^QH*lu861Ro49_o}TDMO>e)`+Vj0%i@r}Ir=l&g>bJO5v-sy4#| z5r!Y1yY)(MZ_6#79#hnry{q)j|5%?$djp-<$9-?h3(L4MG|byS6+8g?S^Nvb0;T<% zyYib_F4%|&G-O*I*(UvHo7u$QSJ&5FRX^X%(jgvOa?yd|K=*mscQ^k%yT9kfzMo&0 zAGcH1*M#j1 zcl)_(&-_@wcEM+X29fei&29E7-}a{BGbz`(mbTJKNA|13G3Yk#c|DKadb8U~)y3t?bTuv;j_FolKXV7<<*gUjzO z@?F2n`}zL`vvUOI9X5Lrd|c4_`<>$0-{1c`Ff!~>_vUeGUa~1;bK2QgYEQUT-%aKz zVsdF>5wyV}RetsB-LazWrhA9(h(W~c)U`GGbY+jZO;C#iw z$iTrMc8by9lqbVdo|hs_2||YTnMdcv3-6lyZv9(!rTAz^cLoPnOWzOboqq$E4#@B` z==}(?VMq!KP*rqiY!EBXQ@y!b``?`J;l4~Ws?Mx>9vMFA(3J1~;s1BCJFNL#?{&yc z+cF_X$RW4x#pG3>mix7j?uwib7P)eBGiaXPqv4 zb9Y}+^MFSO}iM>IIGo5rl~Rgu=)O~V`}c>LA( zBg28vka^4pw2ayRPuU{+{KAcmFN3AtE`PrKri~dLJu>sT=XPY@m;d*_e-tZtneSLu!6&Hj+Ddb`#@WSD%zE#1lcQye zr-w{uG^yXet1EcoU0`_>0CwAs~Xvzk7u+47j*J7DVd$FEKA zxyTK-D`P% z-;_aW$rCwNhF7OnurQohG>4Pn$0HXVmyQO`!%PgJtb4o*CZGTL-?qn~Es%TrgX_EO z_!<5%KR5iNaY)s$Py1s?$!CdwbN=0V-0q_BB{Pn};CV%_y0D-KgYNV z7k$^&XU?cR@MNC-c|(O(sUtQ2b^d+weK&KtygtK%Etf!kU697$5dKb;fnopBj}w_0 z?6z?+%rGwt7H6m_eQUbt$N$x(!Mhj|9^Sbb`;q_a89|1eJ39(rFdUdwubi~*BNs!| z+FvnmUvm{oq&zy{*uwCkQ1`W6`=m*ezM8J>kw4F?aP&j_sUJ_jJeQl#%%EkrkUy+? z=Yz%eHHE5j+gc}mFukgO^xk4^gR>{jyV`LjiE!FzD)e6T*DV$kKE77<`C|r!EBcHK z|Nd3@G2J=$dPy;3pW(S{&-1Sa_KVg#{rTbgVA^qs1+DM?2TkSWVptM1wW9x0?n*g^ z84ep>{r1pg&Z=3zLwtVyHim{vUcq%PPIn4Gy~_|*28qZ0`<)+Ja@D_LWH|q`&Q8aH zks+%3{p;&2x?7i?3oqw5V8O-^dUTudk#`H68KRE;ygK#B2GK&@*$fPU$vc1C-&0v! zKmW(^+&~6}tv&MMKP><5JieTvf!m*pA)uw0k>l@-#Qz`9|5MM`xO6dA=6CtL|Htob zsmuOj{Ji##w9Rs*_|?qoQr|vTaENwXlKLy_N8`QA@!>-DD}ObFOqwvwbMftse!Ey1 zww%9Tv`~*>fsNV66Yo>szMsy-kjKHegk{>5qxMhg84o=6;4wVQ6qnBX*^z;Pv&)HN{3?eKSj2bR4ulFy%yv(<|jUncbvny-%84h(u zpS#PaZt&Mm-WS6VAezCW6|-PkOT83B#OAu?;@?1HlHY|m{_m~+Ue)}31Me@8f6g;2 zxHh!AROHpa`xwl?pu6z6Gef|VY`s6F-=mYlwND>E{VkDaV~P!nL)6*zQPcdo-tu?W zuAJY_aO6Ne-@f@`Obw-%)}EigZo)KyhEEo2WEgJU*OzZ*n*WSp!=yi^GnpNJ)pc^3 zKDuuYnurEhdsny_95%eawe|Gvlatl=1{(Ii`@dFq{%1EuhMva1OA2Ed3f?y_N@%bC z8o%$a!vzhN4Ga$b998je{?G2~WJowS$8uka{0-1L!TNW*UdzPqogVhp`sv?4`Cq@j zzFy7H@OjHtcM+EZuNa&)m}hMEU$tO<2ZO_&XZ*HbCfwef-v9qk5YMp#H^PpB!sSNA z_r&d`-!ChC*nOz}?YHZD9%jD)t@F6QW5zPWACm-@GtHUyc5>36)!&}W&2MM;Aj4qj z$*@#H(euFdyYf->j6Q+_vlu2YI83$r9J1y5)?eot8>T(cSL2-dj1d$pDh-!jWqoAO z=x=6XSh)ZG43&nkncmv$4tYWcri9kNtxmi8$Py7O3=Gfo?c8=^Z3<%b_R95 z_xt#IUotf``&%1$Zis$l&&u?LvB6ya3Bv+@mIF2Qo72vIl4B64Kkr=xN=kbzmK*-Q zu|MbLCIOEZn*}@?c1C^d{CuZ_f#K}m+6kTvtM@#ABh)Pa>q7gcHKEC!t{K()KegTd zdcgdy^Z9?<->tH@VRz1FsNnm3V4p2R16P*Fy(<9|4lBzmbAAF9%aHM@6; z;n_67%PbA`p=<`r`YKl(dGw9>d-~VQMh#K2UuGCJEEH&%Vbq`~{_4j0X8HY7c51JG z{o{A9=F#bo83V#453IVczlA4(5!4IizNW7Sx!!NVB*|R`}EY*(jC|NjaU{;RXCtszu%RKJvS!r zN7IhEoj=1CWeO~q^yBz{M)vtX+y(y2F+5mh&&2SB?ZuvNzkeO`e$#mIhWyC_*I&1? z*Net(T6(Eg^K*3C+5EZlg}*ZI@{_nwz z4C|HzU-XTSuXxxR7Zcp*8~@nL_Ka$0X?ES}`?Ky_?Y|)ragsH*>u7)Fk;2g9?dO!Q zh2Fc>A1>cfdw&bh{ICC#)-lKaeZG`$A-}VYQ|Xb1r5p@Lc6fNtyt+~Unen6J_0ON) z-cq>m->R86UnUoBmA~JwIFY}0=i{~e`Lk_E1Si8l5VaK0Or@0w47!;%! z5+oSP-@0#hV)#)P`@NopA$KzaL;g*M2l4k`|BYYB(D3cON4^Y$LtTq>nodFR`u$t^ z84j4eXs&f)U~oLNNB{n!rk2!h@h$~LCC>VUzTZ`{zn4@f@h~x5k@OR=Eqirk<<3X@ z`I$8K7f-TFXJL5#zUF}XPv(vL6EEA|)nfRthnew@T`Yrw>aEL@-Yn!;&NN$^m0{A+ zGx=YmKSw_omuA+!I`jIb3zvoJcGPREw%7fw%9&YZQzxVO+SYd4mE)&-Jm34TvQS99 z^}H!gx_-H;?vwZ0A?$PIuukj7fhF$b7JoGCh63F-=8mE{nnzzi*uH&A~7wEGqM2 z9s?_b2!jACL&hTem=NKk_3R8D0uBA@aSY153}SjQFP_z(o&7v)ZPZn!*_Op={jtW$ ztIgkvjE*#vYa`aS8aC9Du;E}(si;Q7UVGquv%mZ z{Hs-9SP=N`dCywqf3-#qC4Y|xH25_g`0mY6#2}!x`8Z>P+9pAUCDT5F+9>&F^XJBU ziREY;*H7J7FCft&_-myqqpG9+!R9H&OIClr74fn*@_qAp-K&o53ANV!}nmu103xPYhrg_o9?uln?a`Du4YH}jN{z#D=B|GAn;)J+93SgmEkBev;~?_ z;mNRbMgK}Bg}3JOCNbpQ-gfr(zrVku6F8VI?A!CoF#lFIAD|j$mWy=-H$#HPfitG}%ir$zzUspuAoQ?);nlAZ3<9(6zv^dXSonE$ z6nnJRarLQhFULGno&Hc zh6W~vSGvp|j12$mEDt`ZvkhhF_-*gc|hl&C9e8;}Y2Zbt*&3@6yQ(^Nh-`_qm50jBEcP&UoOr(|-RSi+{9ldwv>!0taeSeMj-GyzXe2NS=9#?PQ zk^K8TAJ>Ix6L;=oXvktvNIj##$Z+F)-M7tl3=Fq7%x7*;^`2J2QSkJJ^1DelkMDh9 z|NB?CjD*epk~b~8)7}~C|FdOSpd=-~lHoa<(j2awJcUh#ep#BIqyJv}bM0p&)A}h7 zrQce;kYOlTtGFkoMT_V?|dO+{p5oF(y)KK&)+_@|0*lPr0#7Y zQTbZTK&i7w@^^gBPzqfniwyz7_ zU9D>#-PC9ZeSTlRYWj!${AXWTwZl_0Bg6B$mz>MH>eXHxd@KJuF zwfjChLy)AsZ_i^d8_};eZT464&*pz^eqdkry7XS$J+tfkkL5BjzIY`PzqYA6VdsJ< z`wf*myF$A9^1a{k&omK>Kg|Cjaku92xc$r4u3~5C@Ot}e*`8^A^2T=0Kd*oN@AOXl z`r`BU-%GC+Z#ed#_UpC&dz|;m!|K<}{c(@K`pxeDfAr;;Oe7DvJiqYI?m@oC?z+<2 z|5F4&c^$Oon318OZ^H2j3_p7Bu`=WyzJ6;*1j7N>-6<<|OYJxr8YWB+`=i>=VAaOd zAgUeKku+cG{re}JFVq-oUR*dBvArbpMq1a;`YV@eHJfZj8J;*bgefr?toP(%@L0j| z;JGr>eeU1$|9x5hPX6CR`CU!&^FCkSz|Y8fZ{41ce&s*E&aP%)s0t5aFsT0M$o_1L zQJsx>gUDfdt;gc8wm;kc%kjOp8J5ap-KL~#l;|ohOJGystdaXrB?o3 z`TOV}hWlIVOX^;qwB7wjIq&)Um&#u<|6clb<8}D-1($vm+c3Nm*d@iV!rA1yX#x~L?DBX1d#j9(;eY@`h-cC)CWfT{c0ayu-dDfoy=)C{SXAax@CfQ= zMFx$32MZ?pF)<{(aP5Bl!IYoz#}jpXOOeeEhvqe(|K7?_AHc9+>hHx24e!it9;DxcqDBTTMBJkUt(yE3)eS_!baO3%$*Zc2rGX$Obk*WI2gu&tWYZlO6JAnmy3<+x!j(4@s`KKlJ zn6aTrE|KA`U;JnJcTx8D>*u{Jd#ht-dM%rIyPsb@%-cYFau!!;&` z1*~ce3}0j7^*lJ;^u2<&j}6Nwe<1vp7qMh_MR+gPz(F}nkV!G@AM1D+F9(xe}~m;u`qnJ ze^rB$e~F5+N&$!z5dTWp7>JnhyBL}28TCaPse&*{pDCt%*e-f z{kWC=4fQWSk8uRP{bBex+3x?8(6`GzF+AzX7Gk+FalgawKd-l4_}javeugE(fe33dpp-1`DY0l z^1QRiwOdB*Q1#D$(Rb&@?@Lf&bhw+!;Oxn;GQ5F>r9$Yy_5aVdPMDD!%ED0E@qdEE z^Uu7@3`h30TQN3pF)6LJcE8K8pf=uWkG&ph~}K#CcoqU8~&*mq-NV4 zeY4%Gqt{9Dz>U}CFHQvJ-}*ZFmo6jg@9C`T81?F{_;-D8o@&&vbZQu=f8D&j`dRI{ zzxD3R_^yPV{c-06V?*tCIi`>LObrs385pK9-r4c5FG14bMRQPNkmP~$~2SlE8&%F5uo@_!D>KXG8NfA^?czf9rCYd*#o z3=Ho%59FSzfAco)-=ClNqMv@donPKJ{l;al_e;NAuK2Iz<}LiFIP2QSwI9R&YjC_! zW8G2u_oTSg|EAArvHy3yp0NChK!csd{rUTgT*FHiA2hT0Z&f6cp?zH8-`dBQC0^|k zy5j4W{pc@$(4XVS{skYdzOzZdg-4RLgMr~f&_BCMdk2Phjtp$|^UMyqAAa?Z(i|N*Lx4j@4kB4efQSw zunLX?dl^2McKp{Zzw>U}n%I|&49_&GYu<}}->NRZP9`~i;g;A{1$nL=Pr?rUxbt_G zz<<3myAyZ#m3|e^{~cpg&#>d;G3l4JFXU>!1b)9$eE#Y*mJc^JCg+G25$GS(%Nul+_gVz!t=O=~Uv%m9uXZ`%J`WOF>3p5Ea z!3HcC9C~X1JeDs{2xVlDI`X|abG<18!?rKeX3ID*i2PRAu;dSe!asBU`OnSnTCHVg zkhxu7UY1k6df%G=h71c#T^Q;oX#ZQi=l{EZr>EKM~e_@ekXd|1o{T{5yz4AXw0em0@G# zkJVE+pD-v~@&7EC!Jq)~^3{!gqFbW&zSjDDps{=Qq1EXIrQ-f6z!x09Cmp?W#D1`qon=7xd?9GD>58Z-4{x@V$`1+%? zjUlA=e`yti!jHr6w>@G6C(;mBhSDhTp3p6=e;smia?Ui?KID!snE&%kdJIQBLxVe$ zS%n-EL(o5_28Njo3;eu=PLy)LQ`{&2vF4W5O7+`z{wkff1O9mb&R}5h5%_bb#$H$3 zlaZl%eX-TGdVW(@1}CMT_p<;Et(Gv!a*#9#G8kR9Sjf#xeR459b_D-;K+b?>NX9y>Sr_4?@Uy7 zzf(WYw))Pd)YE@z5(E})$h&K`J@fLibsJj#1|K!;IVpMm?bh$}%KGc-7gxWlZogyx z?_uWmWB-3QRsU7j-|>8ZqYq+0+=j3PdGLizy7ho|J15dFQ?s8 zEdC}QfB(E^^S%93Qv93Ed@C4R`uNxL-`Tw2xQaf5=6Z*$rbAc0+jqz?Ec`A13tHNP zCi4>NuP_{7xBqdlk+Gq_=83W#L%_%WHim}o`m+h~63S;;AIU$uDahdA^lPKD^qtxD zTh%f)_^3=iQm|nAMVe+ZN|^AVnY4HR`Fj93^|uu_A@ZlHP=V#Gb~W*o_9kUHzoxwTcQQ)MAneF>LAD8xSDim<-^#Euk-?Bb;pyMYMh&6= z?TU*Trhx1GdQhd$%FrXlAo1nCWAuX6Vp@fIkr{d`@0f4Dc6;@n)_KulR({bp%6s=u z+a`JZ6T^a)+me3u=Kbw8|MC>nt5auua*v^eXI^>qp6`;VZy<1s8VIcjmLW5X3g04YpvDi zYZX$9?R!3Nj(M+#_;~bl@#0!XoeNx?D-C}sGE5Mj zClI|U(7H}GQI3IW%WwW)Y>W&+hlF0ui+{B^@7+r6hhiQFuCILbzyA39e`}8GaD2EF z9KE-YL&1^p?QhrBA_|q9AHK^Q+t<_|tzc-`Cl6W&v4x4jjhErisr7%nuCL-Mv~t&B zSn+#D=&>t_tPDNz3=Xz6vsoCf74F!vJ#INu!z#W1QVb7H{&V=!{NsAe-;2Tw-;cjF zzhAT2{GZ(xm4=%Arx+ZxH;dP=`kePQy7bxFk5QBMInJFwmF4^|Mjrb~TV3KrK!xeG zpOKH(exGlB&uagS*WSh*mo1Vw89YjF3o->T_8rnu*&t{U@S~ys%F>xl40q+65* z=}K?-@?C(5VPh!A0RhlpXF!GcY?cl15i=%+B1VU+-;E*}8APA|`EI#3ZK>^YF(!sv zh2Qquo|nEa&a*9Xf2j-~!>g{p@+=GvhW?BU>`XQGd<=_bt8I9#$9#g(VX9GGvz7g7 zp12_U~#)p!Ql{mX#cxY48BMIWq)^{`ZJl;;!d)A{Ew*VX*(Hp>gU`qgx00@ z3=Y!^4RWh{S{WGr&eiMuKjZo=i|u{Rzr$>dwjOtS?8LAjv^etc4FQIEKkY@B41^dI zww;L+ZS=i+bT!ul$GP!c4!$25POvhVuset{G=QUGA0NYk@N2U6@8#<(erqAaQ^2v{E4xC&?QPZFEDT!p z`-2z+CbLX%VmQOOK$Y=|6GO!v`)vUn2NpWFxA8MLDH*yk)Mu|g-)ylzLj0=;KZ6|8 zgwOw0w=o1T%2)m0`S>*lwqr-o< z3Q32zQNau!rmnA1{XW0GEOV)E#l!VSSN2`M#>minv+4BzBj6DhMg}v64#7W{UW+g^ z{JtEMFfH@*0>@5=4Gll~OMi-UcTy?g)f-0bX$!2wU+p06t2!y?za@%pPh_0fOB=2m9! z3EP$*@LYWM61}6$4ebn1_WYlr(y;L3wcqDuL2I^e$S^E4$`E6?5O$+@@|TB;Sr}e< zF?oCzy&xJki#Z|S-T%RY*`D+N!<(#8$K1k+xeU? zWX1f-XEWb@I;~&6{Z5hh^#`Uo>u>%0asPi;bnV6c>vi6JKl^XX?k|_Tf9L$|onQL< zxZUGvf1WOP_yby&bYKSq!$OO?FY|g$xfp)ziF%nYa_!F1`R}{`h8?Tlwdnls~6M;J-+6Ua|39b=vTH!|5v`RxvN|M(@v6M!HLbU_CSKv#(ufu{Q2LGPng8qaO|@MPE)`p_xFs}XMfn$_n7_o z_UHYY%pCRI450O{VT>G-=M1y2tpPRBbeR}#OorBHn{(A#g_U#T`ZnwQ}uj#)##piQt|9zexuDiW& zZ|R>qR^PAvxWHdv@cPf~|3{*vm)9-aT7J;<-%r))F_$)IU3)v^?(IOMm;|?6XwK;{MPtk$?zeL!R5>9^O6h;)*obJWk~pX zpNXOKeyJL0neI{hwE_)n@BV$b!NO4S^Xc?jrX#NGwk#Qn^$Z!^|F5;B`*;4gV>pof z#kurV@#`DWj~{kVa$?|PDSCY)`uO>^#reI*1sLBQ>b7KB^O^l%J@jaWb@jcs8JHp- z&wh7)uf59l^0+GfXA`*#?)2$>32}M+=Z5$!>DZmR+e_>I%&91^;bUMpH!&^J{qTnd zt3y0a{nzY#<@eXy@O=*~{`|bbLHPT%KR@Pj+1~)y&KDUN*8a%3<=n`?!Eo!E6T<-o z2hifFo@|B&!>0+e^X6VN^;HOGWw@g~otfd)&ePj39}VPVSaPYZlz~BSQma6N*Zt`$ z85*SgnLK`1T-tBt%rLLw5$E-lO&1<9EYSTE&B3tl-yQ!sm#2T(&!|yvey5;W{Ex=J zLg@;acb2eJ?pY$y+c=$paStCO!>eg)c3xi2z@RzLl9l1ev5U$K2VOg!pSf@QgaePJ zvTSj$id}pBhGpitre)9iEdK}E+`mxSf9dC;?=qr)L^uxY{H$VI@>KrUkNbwT_diJe z-x*(W(G|4%O1w(rOGgW2HPS-=Cwsj6&v}=;W?y);-?Q-UsnrW@1M(TT z7)10Nm>57AQo)d=Vcox|^2z@+*FXGUlf%Iv`X-c-ftAaK{XlZPy#T|7t*h7VIyJZU z+fCgq+511VzI(kbe*gYA-`dSQ3LXF1X5i>8}nlDn^F?&D3zNOn7971Uza;DWUW{{d;b&A`9<|FUS-8D<@4riIQh3jk>So> zdovb=v^pL}i-HFZpxJjZfotJ5e?A9_{$plkCkY#=9zII!-J>R|MyIK%G6-h z2X2(BvMjhMz>wqeqxhr#%=`HQ3<=*ivhU(#$mC#XXmF3S`nQjrL436ddqWtcOq*BD z@F06$J@bMr+3MFnc9yymri|pqyOqgZ5@8!Qczmo+R96mEF@MPE-&dAOX zB>Pi@zac`v;rn43hFR6dm+Fe_1xc=oAJiZ;^n)G|Chxx z6`bD8Zp+e87x9(-WBvQr{=b77cF(iC!_BZFVe6|a&#!IVe#|?5jtJ9}Ya6%s&5wS5 zPbz;Z+{=QyEs82C@}gUl*&&?~u8awWTDMVL{?Uc^1azEKpD|WV*O=5Jo?`kzW~H%lX=t&;!OXC4#Ysj6e&z)X z4$&W{ALHd>aNuL$W@y=$&vd_ChGEO9h+qbWJOAJBdabAb@45Ysle)G$j;`HZKl}Ed z3v%24z5JVZH~f8d{GVV3hsPV%T73)KANpMO?(e`4#R>^mC!V*R{`V-OaQ)u>{`J2u z+X^%=H{SU^{dT@=JoiF}kAJWA-}~4f(-6H#mWPqS-^hctgoj~5-+IpfA_5JH3?cym z{0uLg8{KUsRvciy82k0d!tVmV|82Scr})Rm-S)35H zVSg8S;j>K7Iq&#n`>oUG-FoHws`mn5y zF73-OOk~TL9$O|^u|gCylfn4?zYK$c%g6f~EEk^fGB!9yTve%me3gqqVm?E|@o$f_ z>`MPGU&wI7|1iUkeIg7O9*49t{HdwrV-RC{;B?=z?Q8F828O(!O_}dzw&$1UK79Xn zYJK{)UADKwc5R#RezWgvvz_hobvL%Q?l-UCyZyqhTTB>Z=S68;PgU1bJb3`sqG zOD!3G>=CMGSRlZ#moX{+vZV&Ygg$3bE0~EbcivuibL$NUzDKY6v7VP#6b(jH`*}>K!eGYp+R7Q*UWuW zzyY;COVUQh|MZ{6K>db*nfq^?*H`%`$nd0o&;DhnJj@swUYLo@(+I1mbUex2(C^R5 zxI$@R{807OA8TRp;#$3Gqi@A75?(Xd^Nk6sg%{RXJzqaey zasIuYsW;X@@{{c`W(84(C3E86{fzz_%Bpvk(E(a%9;vIF#ooZmkaRyjn4#er!v+?) zj-7J^7@}s&$jv^%IAJo2z!4#R=II?L871m}h3?!ZwqV68#sG7Mh|2HVqj$^yjRaLe zW)2JnhaT-W10B8h`^Rznci^Q*TnrN!8&n=uax{F668?6rP~{_k+EgWx2M*<`HlMBi z|Jcp?z@VU_w6<>bx!v1-f$qpGuG{%M|Lbv%I~OVhe%gGS-^LvAcKNtMzoO=GhT1YL5n!12K;Mid;a8Kp4THmGCkBTHBB0#6 z>32O3g9Q7b{bm9JpBOCue!0At*K0=6>RC@j^Y=tCGJIuf2$0#Ix^U-`C3_V)7T8YT ze2Oukey=?j!w==J3@d(t%VA}P2{C(}nPQ3=uNN_V5MThMmVRF*iOsLHIqi5E7BqC9 zWMuGUSS@F7s+h3q#8d@_S!{7Hc7$*&sIAlV{8x0ee$|KT-+C?ezB4q0R>X8Jmt*i% zXWWzbjyYC9z>A6F7=uG2!_ui&|FbX{@Exret7qZ(+wgzx{hzV||3Q^e07C;K!;Xcs z?R6MD&TlOYnG=0{fhSXPT=wzE?Rj&Vy?^F)2>xSaco1~-{|QD0k;pLF>ecsGeU>Yl zxJZs63Z>MT_J_Ioc}9fT1*!bK_ACsO7+Fr#f{To;0u5G-3|twzy4F3dXPCf}a#@j~ z!tV}u!!w2ji9Dgj3_Q1Xn6qu25&GMTp~2KW?~i>)!CI3?H@;{dywtU@ZbtdpOPPVG z41rsk1>89f2><5Y&GX>v_4xM-L=r%0DvXz*Cm2+{T81)MJ=zaS`dW+){B<9i??$gl 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 MPgg&ebxsLQ0I7eCtN;K2 diff --git a/core/src/io/anuke/mindustry/entities/TileEntity.java b/core/src/io/anuke/mindustry/entities/TileEntity.java index f7df09b001..5445bf8b95 100644 --- a/core/src/io/anuke/mindustry/entities/TileEntity.java +++ b/core/src/io/anuke/mindustry/entities/TileEntity.java @@ -209,7 +209,9 @@ public class TileEntity extends BaseEntity implements TargetTrait { } tile.block().update(tile); - cons.update(this); + if(cons != null){ + cons.update(this); + } } } diff --git a/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java b/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java index b4b69e5c19..6a7f4d8cae 100644 --- a/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java +++ b/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java @@ -58,7 +58,12 @@ public interface BuilderTrait extends Entity{ /**Build power, can be any float. 1 = builds recipes in normal time, 0 = doesn't build at all.*/ float getBuildPower(Tile tile); - default void writeBuilding(DataOutput output) throws IOException{ + /**Whether this type of builder can begin creating new blocks.*/ + default boolean canCreateBlocks(){ + return true; + } + + default void writeBuilding(DataOutput output) throws IOException { BuildRequest request = getCurrentRequest(); if(request != null){ @@ -172,9 +177,9 @@ public interface BuilderTrait extends Entity{ Tile tile = world.tile(current.x, current.y); if (!(tile.block() instanceof BuildBlock)) { - if(!current.remove && Build.validPlace(unit.getTeam(), current.x, current.y, current.recipe.result, current.rotation)) { + if(canCreateBlocks() && !current.remove && Build.validPlace(unit.getTeam(), current.x, current.y, current.recipe.result, current.rotation)) { Build.beginPlace(unit.getTeam(), current.x, current.y, current.recipe, current.rotation); - }else if(current.remove && Build.validBreak(unit.getTeam(), current.x, current.y)){ + }else if(canCreateBlocks() && current.remove && Build.validBreak(unit.getTeam(), current.x, current.y)){ Build.beginBreak(unit.getTeam(), current.x, current.y); }else{ getPlaceQueue().removeFirst(); diff --git a/core/src/io/anuke/mindustry/entities/units/types/Drone.java b/core/src/io/anuke/mindustry/entities/units/types/Drone.java index f26a6796df..e814aef0a5 100644 --- a/core/src/io/anuke/mindustry/entities/units/types/Drone.java +++ b/core/src/io/anuke/mindustry/entities/units/types/Drone.java @@ -201,6 +201,11 @@ public class Drone extends FlyingUnit implements BuilderTrait { return false; } + @Override + public boolean canCreateBlocks() { + return false; + } + @Override public void write(DataOutput data) throws IOException { super.write(data); diff --git a/core/src/io/anuke/mindustry/world/Tile.java b/core/src/io/anuke/mindustry/world/Tile.java index 99cd9d987b..afa5feca5d 100644 --- a/core/src/io/anuke/mindustry/world/Tile.java +++ b/core/src/io/anuke/mindustry/world/Tile.java @@ -10,6 +10,7 @@ import io.anuke.mindustry.entities.traits.TargetTrait; import io.anuke.mindustry.game.Team; import io.anuke.mindustry.type.Recipe; import io.anuke.mindustry.world.blocks.Floor; +import io.anuke.mindustry.world.modules.ConsumeModule; import io.anuke.mindustry.world.modules.InventoryModule; import io.anuke.mindustry.world.modules.LiquidModule; import io.anuke.mindustry.world.modules.PowerModule; @@ -391,12 +392,13 @@ public class Tile implements PosTrait, TargetTrait { if (block.hasEntity()) { entity = block.getEntity().init(this, block.update); + if(block.consumes.hasAny()) entity.cons = new ConsumeModule(); if(block.hasItems) entity.items = new InventoryModule(); if(block.hasLiquids) entity.liquids = new LiquidModule(); if(block.hasPower) entity.power = new PowerModule(); + entity.updateProximity(); } - entity.updateProximity(); updateOcclusion(); } diff --git a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java index 5b03df2d21..48f9f5adb2 100644 --- a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java @@ -22,8 +22,8 @@ import io.anuke.mindustry.type.Recipe; import io.anuke.mindustry.world.BarType; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.modules.InventoryModule; import io.anuke.mindustry.world.meta.BlockBar; +import io.anuke.mindustry.world.modules.InventoryModule; import io.anuke.ucore.core.Effects; import io.anuke.ucore.core.Graphics; import io.anuke.ucore.graphics.Draw; @@ -222,7 +222,7 @@ public class BuildBlock extends Block { progress = Mathf.clamp(progress - amount); if(progress <= 0){ - CallBlocks.onDeconstructFinish(tile, recipe == null ? previous : recipe.result); + CallBlocks.onDeconstructFinish(tile, this.recipe == null ? previous : this.recipe.result); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/Rock.java b/core/src/io/anuke/mindustry/world/blocks/Rock.java index 40066e08ec..976e398a68 100644 --- a/core/src/io/anuke/mindustry/world/blocks/Rock.java +++ b/core/src/io/anuke/mindustry/world/blocks/Rock.java @@ -43,7 +43,7 @@ public class Rock extends Block { regions = new TextureRegion[variants]; for (int i = 0; i < variants; i++) { - shadowRegions[i] = Draw.region(name + "-shadow" + (i+1)); + shadowRegions[i] = Draw.region(name + "shadow" + (i+1)); regions[i] = Draw.region(name + (i+1)); } } diff --git a/core/src/io/anuke/mindustry/world/consumers/Consumers.java b/core/src/io/anuke/mindustry/world/consumers/Consumers.java index 552b41bce3..6873f3f7be 100644 --- a/core/src/io/anuke/mindustry/world/consumers/Consumers.java +++ b/core/src/io/anuke/mindustry/world/consumers/Consumers.java @@ -93,6 +93,10 @@ public class Consumers { return map.values(); } + public boolean hasAny(){ + return map.size > 0; + } + public void forEach(Consumer cons){ for(Consume c : all()){ cons.accept(c); From 1e8206757d6e42d80846c0ea59f436b732b7c704 Mon Sep 17 00:00:00 2001 From: Anuken Date: Sun, 8 Jul 2018 23:59:02 -0400 Subject: [PATCH 12/47] Various bugfixes / Fixed persistent inventory, block crashes --- core/assets/bundles/bundle.properties | 4 +-- .../content/blocks/CraftingBlocks.java | 1 + core/src/io/anuke/mindustry/core/World.java | 4 +++ .../io/anuke/mindustry/editor/MapEditor.java | 10 +++--- .../mindustry/editor/MapEditorDialog.java | 10 +++--- .../ui/fragments/BlockInventoryFragment.java | 10 ++++-- .../mindustry/ui/fragments/DebugFragment.java | 1 - .../io/anuke/mindustry/world/BaseBlock.java | 5 +-- core/src/io/anuke/mindustry/world/Tile.java | 2 +- .../mindustry/world/blocks/BuildBlock.java | 18 +++++++--- .../world/blocks/distribution/ItemBridge.java | 25 ++++++++++++++ .../world/blocks/production/Drill.java | 9 +---- .../world/blocks/production/LiquidMixer.java | 33 ++++++++++++++++++- .../mindustry/world/modules/LiquidModule.java | 1 + 14 files changed, 102 insertions(+), 31 deletions(-) diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index de455f1004..fc0c41f561 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -461,8 +461,8 @@ block.splitter.name=Splitter block.splitter.description=Outputs items into two opposite directions immediately after they are recieved. block.router.name=Router block.router.description=Splits items into all 4 directions. Can store items as a buffer. -block.multiplexer.name=Multiplexer -block.multiplexer.description=A router that can split items into 8 directions. +block.distributor.name=Distributor +block.distributor.description=A splitter that can split items into 8 directions. block.sorter.name=Sorter block.sorter.description=Sorts items. If an item matches the selection, it is allowed to pass. Otherwise, the item is outputted to the left and right. block.overflow-gate.name=Overflow Gate diff --git a/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java b/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java index 2c76f2638b..cc651b2476 100644 --- a/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java @@ -117,6 +117,7 @@ public class CraftingBlocks extends BlockList implements ContentList { liquidPerItem = 50f; itemCapacity = 50; size = 2; + hasPower = true; consumes.power(0.1f); consumes.item(Items.titanium); diff --git a/core/src/io/anuke/mindustry/core/World.java b/core/src/io/anuke/mindustry/core/World.java index 7dfaa3977f..1859dd94b1 100644 --- a/core/src/io/anuke/mindustry/core/World.java +++ b/core/src/io/anuke/mindustry/core/World.java @@ -170,6 +170,10 @@ public class World extends Module{ for(int x = 0; x < tiles.length; x ++) { for (int y = 0; y < tiles[0].length; y++) { tiles[x][y].updateOcclusion(); + + if(tiles[x][y].entity != null){ + tiles[x][y].entity.updateProximity(); + } } } diff --git a/core/src/io/anuke/mindustry/editor/MapEditor.java b/core/src/io/anuke/mindustry/editor/MapEditor.java index 441c077f09..6ad61bd62c 100644 --- a/core/src/io/anuke/mindustry/editor/MapEditor.java +++ b/core/src/io/anuke/mindustry/editor/MapEditor.java @@ -39,14 +39,16 @@ public class MapEditor{ return tags; } - public void beginEdit(MapTileData map, ObjectMap tags){ + public void beginEdit(MapTileData map, ObjectMap tags, boolean clear){ this.map = map; this.brushSize = 1; this.tags = tags; - for (int x = 0; x < map.width(); x++) { - for (int y = 0; y < map.height(); y++) { - map.write(x, y, DataPosition.floor, (byte)Blocks.stone.id); + if(clear) { + for (int x = 0; x < map.width(); x++) { + for (int y = 0; y < map.height(); y++) { + map.write(x, y, DataPosition.floor, (byte) Blocks.stone.id); + } } } diff --git a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java index f1d0fa47db..777d844540 100644 --- a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java +++ b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java @@ -107,7 +107,7 @@ public class MapEditorDialog extends Dialog implements Disposable{ MapMeta meta = MapIO.readMapMeta(stream); MapTileData data = MapIO.readTileData(stream, meta, false); - editor.beginEdit(data, meta.tags); + editor.beginEdit(data, meta.tags, false); view.clearStack(); }catch (Exception e){ ui.showError(Bundles.format("text.editor.errorimageload", Strings.parseException(e, false))); @@ -125,7 +125,7 @@ public class MapEditorDialog extends Dialog implements Disposable{ try{ MapTileData data = MapIO.readPixmap(new Pixmap(file)); - editor.beginEdit(data, editor.getTags()); + editor.beginEdit(data, editor.getTags(), false); view.clearStack(); }catch (Exception e){ ui.showError(Bundles.format("text.editor.errorimageload", Strings.parseException(e, false))); @@ -213,7 +213,7 @@ public class MapEditorDialog extends Dialog implements Disposable{ MapMeta meta = MapIO.readMapMeta(stream); MapTileData data = MapIO.readTileData(stream, meta, false); - editor.beginEdit(data, meta.tags); + editor.beginEdit(data, meta.tags, false); view.clearStack(); }catch (IOException e){ ui.showError(Bundles.format("text.editor.errormapload", Strings.parseException(e, false))); @@ -254,7 +254,7 @@ public class MapEditorDialog extends Dialog implements Disposable{ view.clearStack(); Core.scene.setScrollFocus(view); if(!shownWithMap){ - editor.beginEdit(new MapTileData(256, 256), new ObjectMap<>()); + editor.beginEdit(new MapTileData(256, 256), new ObjectMap<>(), true); } shownWithMap = false; @@ -348,7 +348,7 @@ public class MapEditorDialog extends Dialog implements Disposable{ shownWithMap = true; DataInputStream stream = new DataInputStream(is); MapMeta meta = MapIO.readMapMeta(stream); - editor.beginEdit(MapIO.readTileData(stream, meta, false), meta.tags); + editor.beginEdit(MapIO.readTileData(stream, meta, false), meta.tags, false); is.close(); show(); }catch (Exception e){ diff --git a/core/src/io/anuke/mindustry/ui/fragments/BlockInventoryFragment.java b/core/src/io/anuke/mindustry/ui/fragments/BlockInventoryFragment.java index bbfc861d77..3e65ac45de 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/BlockInventoryFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/BlockInventoryFragment.java @@ -69,11 +69,12 @@ public class BlockInventoryFragment extends Fragment { } private void rebuild(boolean actions){ + Player player = input.player; IntSet container = new IntSet(); - table.clear(); + table.clearChildren(); table.background("inventory"); table.setTouchable(Touchable.enabled); table.update(() -> { @@ -121,7 +122,12 @@ public class BlockInventoryFragment extends Fragment { HandCursorListener l = new HandCursorListener(); l.setEnabled(canPick); - ItemImage image = new ItemImage(item.region, () -> round(tile.entity.items.get(item))); + ItemImage image = new ItemImage(item.region, () -> { + if(tile == null || tile.entity == null){ + return ""; + } + return round(tile.entity.items.get(item)); + }); image.addListener(l); image.addListener(new InputListener(){ diff --git a/core/src/io/anuke/mindustry/ui/fragments/DebugFragment.java b/core/src/io/anuke/mindustry/ui/fragments/DebugFragment.java index fde6ff3527..7a19c45852 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/DebugFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/DebugFragment.java @@ -84,7 +84,6 @@ public class DebugFragment extends Fragment { FloatingDialog dialog = new FloatingDialog("debug spawn"); for(UnitType type : UnitType.all()){ dialog.content().addImageButton("white", 40, () -> { - dialog.hide(); BaseUnit unit = type.create(player.getTeam()); unit.inventory.addAmmo(type.weapon.getAmmoType(type.weapon.getAcceptedItems().iterator().next())); unit.setWave(); diff --git a/core/src/io/anuke/mindustry/world/BaseBlock.java b/core/src/io/anuke/mindustry/world/BaseBlock.java index 70634e45c6..00863d5f2c 100644 --- a/core/src/io/anuke/mindustry/world/BaseBlock.java +++ b/core/src/io/anuke/mindustry/world/BaseBlock.java @@ -7,6 +7,7 @@ import io.anuke.mindustry.entities.Unit; import io.anuke.mindustry.entities.effect.Puddle; import io.anuke.mindustry.type.Item; import io.anuke.mindustry.type.Liquid; +import io.anuke.mindustry.world.consumers.ConsumeItem; import io.anuke.mindustry.world.consumers.ConsumeLiquid; import io.anuke.mindustry.world.consumers.Consumers; import io.anuke.ucore.core.Effects; @@ -68,7 +69,7 @@ public abstract class BaseBlock { } public boolean acceptItem(Item item, Tile tile, Tile source){ - return tile.entity != null && consumes.item() == item && tile.entity.items.total() < itemCapacity; + return tile.entity != null && consumes.has(ConsumeItem.class) && consumes.item() == item && tile.entity.items.total() < itemCapacity; } public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){ @@ -210,7 +211,7 @@ public abstract class BaseBlock { for (int ii = 0; ii < Item.all().size; ii++) { Item item = Item.getByID(ii); - if (other.block().acceptItem(item, other, in) && canDump(tile, other, item)) { + if (entity.items.has(item) && other.block().acceptItem(item, other, in) && canDump(tile, other, item)) { other.block().handleItem(item, other, in); tile.entity.items.remove(item, 1); incrementDump(tile, proximity.size); diff --git a/core/src/io/anuke/mindustry/world/Tile.java b/core/src/io/anuke/mindustry/world/Tile.java index afa5feca5d..28b0fd0217 100644 --- a/core/src/io/anuke/mindustry/world/Tile.java +++ b/core/src/io/anuke/mindustry/world/Tile.java @@ -392,7 +392,7 @@ public class Tile implements PosTrait, TargetTrait { if (block.hasEntity()) { entity = block.getEntity().init(this, block.update); - if(block.consumes.hasAny()) entity.cons = new ConsumeModule(); + entity.cons = new ConsumeModule(); if(block.hasItems) entity.items = new InventoryModule(); if(block.hasLiquids) entity.liquids = new LiquidModule(); if(block.hasPower) entity.power = new PowerModule(); diff --git a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java index 48f9f5adb2..ab692b560a 100644 --- a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java @@ -133,10 +133,13 @@ public class BuildBlock extends Block { public void drawShadow(Tile tile) { BuildEntity entity = tile.entity(); - if(entity.recipe != null){ - entity.recipe.result.drawShadow(tile); - }else if(entity.previous != null){ - entity.previous.drawShadow(tile); + Recipe recipe = entity.recipe; + Block previous = entity.previous; + + if(recipe != null){ + recipe.result.drawShadow(tile); + }else if(previous != null){ + previous.drawShadow(tile); } } @@ -181,6 +184,7 @@ public class BuildBlock extends Block { /**The block that used to be here. * If a non-recipe block is being deconstructed, this is the block that is being deconstructed.*/ public Block previous; + public int builderID = -1; private float[] accumulator; @@ -195,8 +199,12 @@ public class BuildBlock extends Block { progress = Mathf.clamp(progress + maxProgress); + if(builder instanceof Player){ + builderID = builder.getID(); + } + if(progress >= 1f){ - CallBlocks.onConstructFinish(tile, recipe.result, builder.getID(), tile.getRotation(), tile.getTeam()); + CallBlocks.onConstructFinish(tile, recipe.result, builderID, tile.getRotation(), tile.getTeam()); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java b/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java index 6f5b4b2e30..089e01ced8 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java @@ -54,6 +54,8 @@ public class ItemBridge extends Block { @Override public void load() { + super.load(); + endRegion = Draw.region(name + "-end"); bridgeRegion = Draw.region(name + "-bridge"); arrowRegion = Draw.region(name + "-arrow"); @@ -221,6 +223,29 @@ public class ItemBridge extends Block { @Override public boolean acceptItem(Item item, Tile tile, Tile source) { + ItemBridgeEntity entity = tile.entity(); + Tile other = world.tile(entity.link); + + if(linkValid(tile, other)){ + int rel = tile.absoluteRelativeTo(other.x, other.y); + int rel2 = tile.relativeTo(source.x, source.y); + + if(rel == rel2) return false; + }else{ + int i = tile.relativeTo(source.x, source.y); + + IntSetIterator it = entity.incoming.iterator(); + + while(it.hasNext){ + int v = it.next(); + int x = v % world.width(); + int y = v / world.width(); + if(tile.absoluteRelativeTo(x, y) == i){ + return false; + } + } + } + return tile.entity.items.total() < itemCapacity; } diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Drill.java b/core/src/io/anuke/mindustry/world/blocks/production/Drill.java index 87445d4762..999818157b 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Drill.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Drill.java @@ -13,7 +13,6 @@ import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.consumers.ConsumeLiquid; import io.anuke.mindustry.world.meta.BlockGroup; import io.anuke.mindustry.world.meta.BlockStat; -import io.anuke.mindustry.world.meta.BlockStats; import io.anuke.mindustry.world.meta.StatUnit; import io.anuke.ucore.core.Effects; import io.anuke.ucore.core.Effects.Effect; @@ -70,13 +69,7 @@ public class Drill extends Block{ liquidCapacity = 5f; hasItems = true; - consumes.add(new ConsumeLiquid(Liquids.water, 0.05f){ - @Override - public void display(BlockStats stats) { - stats.add(BlockStat.coolantUse, use * 60f, StatUnit.liquidSecond); - stats.add(BlockStat.coolant, liquid); - } - }).optional(true); + consumes.add(new ConsumeLiquid(Liquids.water, 0.05f)).optional(true); } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/production/LiquidMixer.java b/core/src/io/anuke/mindustry/world/blocks/production/LiquidMixer.java index befc9cc980..36d6b9c0ce 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/LiquidMixer.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/LiquidMixer.java @@ -2,11 +2,15 @@ package io.anuke.mindustry.world.blocks.production; import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.type.Liquid; +import io.anuke.mindustry.world.BarType; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.LiquidBlock; import io.anuke.mindustry.world.consumers.ConsumeLiquid; +import io.anuke.mindustry.world.meta.BlockBar; import io.anuke.mindustry.world.meta.BlockStat; +import io.anuke.mindustry.world.modules.LiquidModule; import io.anuke.ucore.core.Timers; +import io.anuke.ucore.graphics.Draw; public class LiquidMixer extends LiquidBlock{ protected Liquid outputLiquid; @@ -26,6 +30,15 @@ public class LiquidMixer extends LiquidBlock{ stats.add(BlockStat.liquidOutput, outputLiquid); } + @Override + public void setBars() { + super.setBars(); + + bars.remove(BarType.liquid); + bars.add(new BlockBar(BarType.liquid, true, tile -> tile.entity.liquids.get(consumes.liquid()) / liquidCapacity)); + bars.add(new BlockBar(BarType.liquid, true, tile -> tile.entity.liquids.get(outputLiquid) / liquidCapacity)); + } + @Override public boolean shouldConsume(Tile tile){ return tile.entity.liquids.get(outputLiquid) < liquidCapacity; @@ -42,13 +55,31 @@ public class LiquidMixer extends LiquidBlock{ for (int i = 0; i < (int)(entity.accumulator / liquidPerItem); i++) { if(!entity.items.has(consumes.item())) break; entity.items.remove(consumes.item(), 1); - entity.accumulator --; + entity.accumulator -= liquidPerItem; } } tryDumpLiquid(tile, outputLiquid); } + @Override + public void draw(Tile tile){ + LiquidModule mod = tile.entity.liquids; + + int rotation = rotate ? tile.getRotation() * 90 : 0; + + Draw.rect(bottomRegion, tile.drawx(), tile.drawy(), rotation); + + if(mod.total() > 0.001f){ + Draw.color(outputLiquid.color); + Draw.alpha(mod.get(outputLiquid) / liquidCapacity); + Draw.rect(liquidRegion, tile.drawx(), tile.drawy(), rotation); + Draw.color(); + } + + Draw.rect(topRegion, tile.drawx(), tile.drawy(), rotation); + } + @Override public TileEntity getEntity() { return new LiquidMixerEntity(); diff --git a/core/src/io/anuke/mindustry/world/modules/LiquidModule.java b/core/src/io/anuke/mindustry/world/modules/LiquidModule.java index 42358a1ca5..dfd6a4d86e 100644 --- a/core/src/io/anuke/mindustry/world/modules/LiquidModule.java +++ b/core/src/io/anuke/mindustry/world/modules/LiquidModule.java @@ -32,6 +32,7 @@ public class LiquidModule extends BlockModule { public void add(Liquid liquid, float amount){ liquids[liquid.id] += amount; total += amount; + current = liquid; } public void remove(Liquid liquid, float amount){ From b7e28a73ba85cdb4532d8d8400e3f94dcfff7820 Mon Sep 17 00:00:00 2001 From: Anuken Date: Mon, 9 Jul 2018 11:02:30 -0400 Subject: [PATCH 13/47] Bugfixes / Added tool to re-format bundles with missing text --- .gitignore | 1 + .../src/io/anuke/annotations/Annotations.java | 2 +- core/assets/bundles/bundle_de.properties | 994 +++++++-------- core/assets/bundles/bundle_es.properties | 1067 ++++++++--------- core/assets/bundles/bundle_fr.properties | 1043 ++++++++-------- core/assets/bundles/bundle_in_ID.properties | 1006 ++++++++-------- core/assets/bundles/bundle_ita.properties | 1066 ++++++++-------- core/assets/bundles/bundle_ko.properties | 1034 ++++++++-------- core/assets/bundles/bundle_pl.properties | 1003 ++++++++-------- core/assets/bundles/bundle_pt_BR.properties | 664 +++++----- core/assets/bundles/bundle_ru.properties | 1050 ++++++++-------- core/assets/bundles/bundle_tk.properties | 1066 ++++++++-------- core/assets/bundles/bundle_uk_UA.properties | 1013 ++++++++-------- .../anuke/mindustry/entities/effect/Fire.java | 1 + core/src/io/anuke/mindustry/type/Recipe.java | 5 + .../mindustry/world/consumers/Consume.java | 2 +- .../world/consumers/ConsumeItems.java | 4 +- .../world/modules/InventoryModule.java | 1 - .../io/anuke/mindustry/BundleLauncher.java | 63 +- 19 files changed, 5563 insertions(+), 5522 deletions(-) diff --git a/.gitignore b/.gitignore index 6d6cc255d6..5a3526c397 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /core/assets/mindustry-saves/ /core/assets/mindustry-maps/ +/core/assets/bundles/output/ /deploy/ /desktop/packr-out/ /desktop/packr-export/ diff --git a/annotations/src/io/anuke/annotations/Annotations.java b/annotations/src/io/anuke/annotations/Annotations.java index 325663f951..5798d13cd8 100644 --- a/annotations/src/io/anuke/annotations/Annotations.java +++ b/annotations/src/io/anuke/annotations/Annotations.java @@ -21,7 +21,7 @@ public class Annotations { Variant variants() default Variant.all; /**The local locations where this method is called locally, when invoked.*/ Loc called() default Loc.none; - /**Whether to forward this packet to all other clients upon recieval. Server only.*/ + /**Whether to forward this packet to all other clients upon recieval. Client only.*/ boolean forward() default false; /**Whether the packet for this method is sent with UDP instead of TCP. * UDP is faster, but is prone to packet loss and duplication.*/ diff --git a/core/assets/bundles/bundle_de.properties b/core/assets/bundles/bundle_de.properties index 2439a197cd..72efcaad72 100644 --- a/core/assets/bundles/bundle_de.properties +++ b/core/assets/bundles/bundle_de.properties @@ -1,479 +1,515 @@ -text.about = Erstellt von [ROYAL] Anuken. [] \nUrsprünglich ein Eintrag im [orange] GDL [] MM Jam.\n\nCredits: \n- SFX gemacht mit [yellow] bfxr [] - Musik gemacht von [green] RoccoW [] / gefunden auf [lime] FreeMusicArchive.org [] \n\nBesonderer Dank geht an: \n- [coral] MitchellFJN []: Umfangreicher Spieletest und Feedback \n- [sky] Luxray5474 []: Wiki-Arbeit, Code-Beiträge \n- Alle Beta-Tester auf itch.io und Google Play\n -text.discord = Trete dem Mindustry Discord bei! -text.gameover = Der Kern wurde zerstört. -text.highscore = [YELLOW] Neuer Highscore! -text.lasted = Du hast bis zur folgenden Welle überlebt -text.level.highscore = High Score: [accent] {0} -text.level.delete.title = Löschen bestätigen -text.level.delete = Bist du sicher, dass du die Karte \"[orange] {0}\" löschen möchtest? -text.level.select = Level Auswahl -text.level.mode = Spielmodus: -text.savegame = Spiel speichern -text.loadgame = Spiel laden -text.joingame = Spiel beitreten -text.quit = Verlassen -text.about.button = Info -text.name = Name: -text.public = Öffentlich -text.players = {0} Spieler online -text.players.single = {0} Spieler online -text.server.mismatch = Paketfehler: Mögliche Client / Server-Version stimmt nicht überein. Stell sicher, dass du und der Host die neueste Version von Mindustry haben! -.server.closing = [accent] Server wird geschlossen... -text.server.kicked.kick = Du wurdest vom Server gekickt! -text.server.kicked.invalidPassword = Falsches Passwort. -text.server.connected = {0} ist beigetreten -text.server.disconnected = {0} hat die Verbindung getrennt. -text.nohost = Server kann nicht auf einer benutzerdefinierten Karte gehostet werden! -text.hostserver = Server hosten -text.host = Host -text.hosting = [accent] Server wird geöffnet... -text.hosts.refresh = Aktualisieren -text.hosts.discovering = Suche nach LAN-Spielen -text.server.refreshing = Server wird aktualisiert -text.hosts.none = [lightgray] Keine LAN Spiele gefunden! -text.host.invalid = [scarlet] Kann keine Verbindung zum Host herstellen. -text.server.add = Server hinzufügen -text.server.delete = Bist du dir sicher das du diesen Server löschen möchtest? -text.server.hostname = Host: {0} -text.server.edit = Server bearbeiten -text.joingame.byip = Über IP beitreten ... -text.joingame.title = Spiel beitreten -text.joingame.ip = IP: -text.disconnect = Verbindung unterbrochen. -text.connecting = [accent] Verbindet... -text.connecting.data = [accent] Weltdaten werden geladen... -text.connectfail = [crimson] Verbindung zum Server konnte nicht hergestellt werden: [orange]{0} -text.server.port = Port: -text.server.invalidport = Falscher Port! -text.server.error = [crimson] Fehler beim Hosten des Servers: [orange] {0} -text.tutorial.back = < Zurück -text.tutorial.next = Weiter > -text.save.new = Neuer Spielstand -text.save.overwrite = Möchten du diesen Spielstand wirklich überschreiben? -text.overwrite = Überschreiben -text.save.none = Keine Spielstände gefunden! -text.saveload = [accent] Speichern ... -text.savefail = Fehler beim Speichern des Spiels! -text.save.delete.confirm = Möchtest du diesen Spielstand wirklich löschen? -text.save.delete = Löschen -text.save.export = Spielstand exportieren -text.save.import.invalid = [orange] Dieser Spielstand ist ungültig! -text.save.import.fail = [crimson] Spielstand konnte nicht importiert werden: [orange] {0} -text.save.export.fail = [crimson] Spielstand konnte nicht exportiert werden: [orange] {0} -text.save.import = Spielstand importieren -text.save.newslot = Name speichern: -text.save.rename = Umbenennen -text.save.rename.text = Neuer Name -text.selectslot = Wähle einen Spielstand -text.slot = [accent] Platz {0} -text.save.corrupted = [orange] Datei beschädigt oder ungültig! -text.empty = -text.on = An -text.off = Aus -text.save.autosave = Automatisches Speichern: {0} -text.save.map = Karte: {0} -text.save.wave = Welle: {0} -text.save.date = Zuletzt gespeichert: {0} -text.confirm = Bestätigen -text.delete = Löschen -text.ok = OK -text.open = Öffnen -text.cancel = Abbruch -text.openlink = Link öffnen -text.back = Zurück -text.quit.confirm = Willst du wirklich aufhören? -text.loading = [accent] Wird geladen ... -text.wave = [orange] Welle {0} -text.wave.waiting = Welle in {0} -text.waiting = Warten... -text.enemies = {0} Feinde -text.enemies.single = {0} Feind -text.loadimage = Bild laden -text.saveimage = Bild speichern -text.editor.badsize = [orange]Ungültige Bildabmessungen! [] Gültige Kartenabmessungen: {0} -text.editor.errorimageload = Fehler beim Laden des Bildes: [orange] {0} -text.editor.errorimagesave = Fehler beim Speichern des Bildes: [orange] {0} -text.editor.generate = Generieren -text.editor.resize = Grösse\nanpassen -text.editor.loadmap = Karte\nladen -text.editor.savemap = Karte\nspeichern -text.editor.loadimage = Bild\nladen -text.editor.saveimage = Bild\nspeichern -text.editor.unsaved = [crimson] Du hast Änderungen nicht gespeichert [] Möchtest du wirklich aufhören? -text.editor.brushsize = Pinselgrösse: {0} -text.editor.noplayerspawn = Diese Karte hat keinen Spielerspawnpunkt! -text.editor.manyplayerspawns = Maps können nicht mehr als einen Spawnpunkt des Spielers haben! -text.editor.manyenemyspawns = Kann nicht mehr als {0} feindliche Spawnpunkte haben! -text.editor.resizemap = Grösse der Karte ändern -text.editor.resizebig = [crimson] Warnung! [] Karten, die grösser als 256 Einheiten sind, können ruckeln und instabil sein. -text.editor.mapname = Map Name -text.editor.overwrite = [accent] Warnung! Dies überschreibt eine vorhandene Map. -text.editor.failoverwrite = [crimson] Die Standardkarte kann nicht überschrieben werden! -text.editor.selectmap = Wähle eine Map zum Laden: -text.width = Breite: -text.height = Höhe: -text.randomize = Zufällig -text.apply = Anwenden -text.update = Aktualisieren -text.menu = Menü -text.play = Spielen -text.load = Laden -text.save = Speichern -text.settings = Einstellungen -text.tutorial = Tutorial -text.editor = Bearbeiter -text.mapeditor = Karten Bearbeiter -text.donate = Spenden -text.settings.reset = Auf Standard zurücksetzen -text.settings.controls = Steuerung -text.settings.game = Spiel -text.settings.sound = Audio -text.settings.graphics = Grafiken -text.upgrades = Verbesserungen -text.purchased = [LIME] Erstellt! -text.weapons = Waffen -text.paused = Pausiert -text.respawn = Respawn in -text.error.title = [crimson] Ein Fehler ist aufgetreten -text.error.crashmessage = [SCARLET] Es ist ein unerwarteter Fehler aufgetreten, der einen Absturz verursacht hätte. [] Bitte geben Sie die genauen Umstände an, unter denen dieser Fehler passiert ist, für den Entwickler: [ORANGE] anukendev@gmail.com [] -text.error.crashtitle = EIn Fehler ist aufgetreten! -text.mode.break = Zerstörungsmodus: {0} -text.mode.place = Platzierungsmodus: {0} -placemode.hold.name = Zeile -placemode.areadelete.name = Gebiet -placemode.touchdelete.name = berühren -placemode.holddelete.name = halten -placemode.none.name = keine -placemode.touch.name = berühren -placemode.cursor.name = Mauszeiger -text.blocks.extrainfo = [accent] Extra Blockinfo: -text.blocks.blockinfo = Blockinfo: -text.blocks.powercapacity = Energiekapazität -text.blocks.powershot = Energie / Schuss -text.blocks.powersecond = Energie / Sekunde -text.blocks.powerdraindamage = Energieabnahme / Schaden -text.blocks.shieldradius = Schildradius -text.blocks.itemspeedsecond = Gegenstands Geschwindigkeit / Sekunde -text.blocks.range = Reichweite -text.blocks.size = Grösse -text.blocks.powerliquid = Energie / Flüssigkeit -text.blocks.maxliquidsecond = Max Flüssigkeit / Sekunde -text.blocks.liquidcapacity = Flüssigkeitskapazität -text.blocks.liquidsecond = Flüssigkeit / Sekunde -text.blocks.damageshot = Schaden / Schuss -text.blocks.ammocapacity = Munitionskapazität -text.blocks.ammo = Munition -text.blocks.ammoitem = Munition / Gegenstand -text.blocks.maxitemssecond = Max Gegenstände / Sekunde -text.blocks.powerrange = Energiereichweite -text.blocks.lasertilerange = Laser Reichweite -text.blocks.capacity = Kapazität -text.blocks.itemcapacity = Gegenstand Kapazität -text.blocks.maxpowergenerationsecond = Max Energieerzeugung / Sekunde -text.blocks.powergenerationsecond = Energieerzeugung / Sekunde -text.blocks.generationsecondsitem = Generation Sekunden / Gegenstand -text.blocks.input = Eingabe -text.blocks.inputliquid = Flüssigkeiten Eingabe -text.blocks.inputitem = Eingabe Gegenstand -text.blocks.output = Ausgabe -text.blocks.secondsitem = Sekunden / Item -text.blocks.maxpowertransfersecond = Max Energieübertragung / Sekunde -text.blocks.explosive = Hochexplosiv! -text.blocks.repairssecond = Reparaturen / Sekunde -text.blocks.health = Lebenspunkte -text.blocks.inaccuracy = Ungenauigkeit -text.blocks.shots = Schüsse -text.blocks.shotssecond = Schüsse / Sekunde -text.blocks.fuel = Treibstoff -text.blocks.fuelduration = Treibstoffdauer -text.blocks.maxoutputsecond = Max Ausgabe / Sekunde -text.blocks.inputcapacity = Eingabekapazität -text.blocks.outputcapacity = Ausgabekapazität -text.blocks.poweritem = Energie / Gegenstand -text.placemode = Platzierungsmodus -text.breakmode = Zerstörungsmodus -text.health = Lebenspunkte -setting.difficulty.easy = Leicht -setting.difficulty.normal = Normal -setting.difficulty.hard = Schwer -setting.difficulty.insane = Unmöglich -setting.difficulty.purge = Auslöschung -setting.difficulty.name = Schwierigkeit -setting.screenshake.name = Bildschirm wackeln -setting.smoothcam.name = Glatte Kamera -setting.indicators.name = Feind Indikatoren -setting.effects.name = Effekte anzeigen -setting.sensitivity.name = Kontroller Empfindlichkeit -setting.saveinterval.name = Autosave Häufigkeit -setting.seconds = {0} Sekunden -setting.fps.name = Zeige FPS -setting.vsync.name = VSync -setting.lasers.name = Zeige Energielaser -setting.healthbars.name = Zeige Objekt Lebensbalken -setting.pixelate.name = Pixel Bildschirm -setting.musicvol.name = Musiklautstärke -setting.mutemusic.name = Musik stummschalten -setting.sfxvol.name = Audioeffekte Lautstärke -setting.mutesound.name = Audioeffekte stummschalten -map.maze.name = Labyrinth -map.fortress.name = Festung -map.sinkhole.name = Sinkloch -map.caves.name = Höhlen -map.volcano.name = Vulkan -map.caldera.name = Lavakessel -map.scorch.name = Flammen -map.desert.name = Wüste -map.island.name = Insel -map.grassland.name = Grasland -map.tundra.name = Kältesteppe -map.spiral.name = Spirale -map.tutorial.name = Tutorial -tutorial.intro.text = [gelb] Willkommen zum Tutorial [] Um zu beginnen, drücke 'weiter'. -tutorial.moveDesktop.text = Verwende zum Verschieben die Tasten [orange] [[WASD] []. Halte [orange] Shift [] gedrückt, um zu erhöhen. Halte [orange] CTRL/STRG [] gedrückt, während du das [orange] Scrollrad [] zum Vergrössern oder Verkleinern verwendest. -tutorial.shoot.text = Ziele mit der Maus, halte die [orange] linke Maustaste [] gedrückt, um zu schiessen. Versuche es mit dem [gelben] Ziel []. -tutorial.moveAndroid.text = Um die Ansicht zu verschieben, ziehe einen Finger über den Bildschirm. Drücke und ziehe, um zu vergrössern oder zu verkleinern. -tutorial.placeSelect.text = Versuche, ein [gelbes] Förderband [] aus dem Blockmenü unten rechts auszuwählen. -tutorial.placeConveyorDesktop.text = Verwende das [orange] [[scrollwheel] [], um das Förderband nach vorne zu bewegen [orange] vorwärts [], und platziere es dann an der [gelben] markierten Stelle [] mit [orange] [[linke Maustaste] []. -tutorial.placeConveyorAndroid.text = Verwende die [orange] [[rotieren-Taste] [], um das Förderband nach vorne [orange] zu drehen [], ziehe es mit einem Finger in Position und platziere es dann an der [gelben] markierten Stelle [] mit der [Orange] [[Häkchen][]. -tutorial.placeConveyorAndroidInfo.text = Alternativ kannst du das Fadenkreuzsymbol unten links drücken, um zum [orange] [[touch mode] [] zu wechseln, und Blöcke durch Tippen auf den Bildschirm platzieren. Im Touch-Modus können Blöcke mit dem Pfeil unten links gedreht werden. Drücke [gelb] neben [], um es auszuprobieren. -tutorial.placeDrill.text = Wähle nun einen [gelben] Steinbohrer [] an der markierten Stelle aus und platziere ihn. -tutorial.blockInfo.text = Wenn du mehr über einen Block erfahren möchtest, tippe oben rechts auf das [orange] Fragezeichen [], um dessen Beschreibung zu lesen. -tutorial.deselectDesktop.text = Du kannst einen Block mit [Orange] [[Rechte Maustaste] [] abwählen. -tutorial.deselectAndroid.text = Du kannst einen Block abwählen, indem du die [orange] X [] -Taste drücken. -tutorial.drillPlaced.text = Der Bohrer erzeugt nun [gelben] Stein, [] gib den Stein nun auf das Förderband aus und bewege ihn dann in den [gelben] Kern []. -tutorial.drillInfo.text = Verschiedene Erze benötigen unterschiedliche Bohrer. Stein erfordert Steinbohrer, Eisen erfordert Eisenbohrer usw. -tutorial.drillPlaced2.text = Wenn du Gegenstände in den Kern verschiebst, steckst du sie in dein [gelbes] Inventar [] oben links. Das Platzieren von Blöcken verwendet Gegenstände aus deinem Inventar. -tutorial.moreDrills.text = Du kannst so viele Bohrer und Förderer miteinander verbinden wie du lust hast. -tutorial.deleteBlock.text = Du kannst Blöcke löschen, indem du mit der [orange] rechte Maustaste [] auf dem Block klickst, den du löschen möchtest. Versuche, dieses Förderband zu löschen. -tutorial.deleteBlockAndroid.text = Du kannst Blöcke löschen, indem du [orange] das Fadenkreuz [] im [orange] Pausenmodusmenü [] links unten auswählst und auf einen Block tippst. Versuche, dieses Fliessband zu löschen. -tutorial.placeTurret.text = Wähle nun einen [gelben] Turm [] an der [gelben] markierten Stelle [] und platziere ihn. -tutorial.placedTurretAmmo.text = Dieser Turm nimmt nun [gelbe] Munition [] vom Förderband an. Du kannst sehen, wie viel Munition es hat, indem du darüber schweben und den [grünen] grünen Balken [] prüfen. -tutorial.turretExplanation.text = Geschütze schiessen automatisch auf den nächsten Feind in Reichweite, solange sie genug Munition haben. -tutorial.waves.text = Jede [yellow] 60 [] Sekunden wird eine Welle von [coral] Feinden [] an einem bestimmten Orten erscheinen und versuchen, den Kern zu zerstören. -tutorial.coreDestruction.text = Dein Ziel ist es, den Kern [yellow] zu verteidigen. Wenn der Kern zerstört wird, verlierst du [coral] das Spiel []. -tutorial.pausingDesktop.text = Wenn du jemals eine Pause machen möchtest, drücke die [orange] Pause-Taste [] oben links oder [orange]space[], um das Spiel anzuhalten. Du kannst auch Blöcke immer noch auswählen und platzieren, während du das Spiel pausiert ist, aber Sie können sich nicht bewegen oder schiessen. -tutorial.pausingAndroid.text = Wenn du jemals eine Pause machen willst, drück einfach die [orange] Pause-Taste [] oben links, um das Spiel anzuhalten. Sie können immer noch Sachen auswählen und Blöcke während der Pause platzieren. -tutorial.purchaseWeapons.text = Du kannst neue [yellow] Waffen [] für deinen Mech kaufen, indem du das Verbesserungs-Menü unten links öffnest. -tutorial.switchWeapons.text = Schalte Waffen, indem du entweder auf das Symbol unten links klickst oder Nummern verwendest [orange] [[1-9] []. -tutorial.spawnWave.text = Hier kommt die erste Welle. Zerstöre sie. -tutorial.pumpDesc.text = In späteren Wellen müsst du möglicherweise [yellow] Pumpen [] verwenden, um Flüssigkeiten für Generatoren oder Extraktoren zu verteilen. -tutorial.pumpPlace.text = Pumpen arbeiten ähnlich wie Bohrer, ausser dass sie anstelle von Gegenständen Flüssigkeiten produzieren. Versuch mal, eine Pumpe auf das [yellow] gekennzeichnete Öl [] zu setzen. -tutorial.conduitUse.text = Stellen Sie nun eine [orange] Leitungsrohr [] von der Pumpe weg. -tutorial.conduitUse2.text = Und noch ein paar mehr ... -tutorial.conduitUse3.text = Und noch ein paar mehr ... -tutorial.generator.text = Stellen Sie nun einen [orange] Verbrennungsgenerator [] Block am Ende des Leitungsrohres auf. -tutorial.generatorExplain.text = Dieser Generator erzeugt nun [yellow] Energie [] aus dem Öl. -tutorial.lasers.text = Die Energie wird mit [yellow] Energielasern [] verteilt. Drehe und platziere einen hier. -tutorial.laserExplain.text = Der Generator wird nun Energie in den Laserblock bewegen. Ein [yellow] undurchsichtiger [] Strahl bedeutet, dass er gerade Leistung überträgt, und ein [yellow] transparenter [] Strahl bedeutet, dass dies nicht der Fall ist. -tutorial.laserMore.text = Du kannst überprüfen, wie viel Energie ein Block hat, indem du darüber schweben und den [yellow] gelben Balken [] oben prüfen. -tutorial.healingTurret.text = Dieser Laser kann verwendet werden, um einen [lime] -Reparaturgeschütz [] anzutreiben. Platziere einen hier. -tutorial.healingTurretExplain.text = Solange er Kraft hat, repariert dieser Turm in der Nähe Blöcke. [] Wenn du spielst, stelle sicher, dass du so schnell wie möglich einen in deiner Basis bekommst! -tutorial.smeltery.text = Viele Blöcke benötigen [orange] Stahl [], um eine [orange] Schmelzer [] herzustellen. Platziere einen hier. -tutorial.smelterySetup.text = Diese Schmelzer wird nun [orange] Stahl [] aus dem Eingangs-Eisen produzieren, wobei Kohle als Brennstoff verwendet wird. -tutorial.end.text = Und damit ist das Tutorial abgeschlossen! Viel Glück! -keybind.move_x.name = bewege_x -keybind.move_y.name = bewege_y -keybind.select.name = wählen -keybind.break.name = Unterbrechung -keybind.shoot.name = Schiess -keybind.zoom_hold.name = zoomen_halten -keybind.zoom.name = zoomen -keybind.menu.name = Menü -keybind.pause.name = Pause -keybind.dash.name = Bindestrich -keybind.rotate_alt.name = drehen_alt -keybind.rotate.name = Drehen -keybind.weapon_1.name = Waffe_1 -keybind.weapon_2.name = Waffe_2 -keybind.weapon_3.name = Waffe_3 -keybind.weapon_4.name = Waffe_4 -keybind.weapon_5.name = Waffe_5 -keybind.weapon_6.name = Waffe_6 -mode.waves.name = Wellen -mode.sandbox.name = Sandkasten -mode.freebuild.name = Freier Bau -upgrade.standard.name = Standard -upgrade.standard.description = Der Standardmech. -upgrade.blaster.name = Blaster -upgrade.blaster.description = Schiesst eine langsame, schwache Kugel. -upgrade.triblaster.name = Dreifach-Blaster -upgrade.triblaster.description = Schiesst 3 Kugeln in einer Ausbreitung. -upgrade.clustergun.name = Klumpen-Waffe -upgrade.clustergun.description = Schiesst eine ungenaue Verbreitung von explosiven Granaten. -upgrade.beam.name = Strahlkanone -upgrade.beam.description = Schiesst einen weitreichenden durchdringenden Laserstrahl. -upgrade.vulcan.name = Vulkan -upgrade.vulcan.description = Schiesst eine Flut von schnellen Kugeln. -upgrade.shockgun.name = Schock-Waffe -upgrade.shockgun.description = Erschiesst eine verheerende Explosion von geladenen Granatsplittern. -item.stone.name = Stein -item.iron.name = Eisen -item.coal.name = Kohle -item.steel.name = Stahl -item.titanium.name = Titan -item.dirium.name = Dirium -item.thorium.name = Uran -item.sand.name = Sand -liquid.water.name = Wasser -liquid.plasma.name = Plasma -liquid.lava.name = Lava -liquid.oil.name = Öl -block.weaponfactory.name = Waffenfabrik -block.air.name = Luft -block.blockpart.name = Blockteil -block.deepwater.name = tiefes Wasser -block.water.name = Wasser -block.lava.name = Lava -block.oil.name = Öl -block.stone.name = Stein -block.blackstone.name = schwarzer Stein -block.iron.name = Eisen -block.coal.name = Kohle -block.titanium.name = Titan -block.thorium.name = Uran -block.dirt.name = Erde -block.sand.name = Sand -block.ice.name = Eis -block.snow.name = Schnee -block.grass.name = Gras -block.sandblock.name = Sandstein -block.snowblock.name = Schneeblock -block.stoneblock.name = Steinblock -block.blackstoneblock.name = Schwarzer Stein -block.grassblock.name = Grasblock -block.mossblock.name = Moosblock -block.shrub.name = Busch -block.rock.name = Felsen -block.icerock.name = Eisfelsen -block.blackrock.name = Schwarzer Felsen -block.dirtblock.name = Erdblock -block.stonewall.name = Steinwand -block.stonewall.fulldescription = Ein billiger Verteidigungsblock. Nützlich zum Schutz des Kerns und der Geschütztürme in den ersten Wellen. -block.ironwall.name = Eisenwand -block.ironwall.fulldescription = Ein grundlegender Verteidigungsblock. Bietet Schutz vor Feinden. -block.steelwall.name = Stahlwand -block.steelwall.fulldescription = Ein Standard-Verteidigungsblock. Bietet angemessen Schutz vor Feinden. -block.titaniumwall.name = Titanwand -block.titaniumwall.fulldescription = Eine starke Abwehrblockade. Bietet Schutz vor Feinden. -block.duriumwall.name = Diriumwand -block.duriumwall.fulldescription = Eine sehr starke Abwehrblockade. Bietet guten Schutz vor Feinden. -block.compositewall.name = Verbundende Wand -block.steelwall-large.name = grosse Stahlwand -block.steelwall-large.fulldescription = Ein Standard-Verteidigungsblock. Mehrere Blöcke gross. -block.titaniumwall-large.name = grosse Titanwand -block.titaniumwall-large.fulldescription = Eine starke Abwehrblockade. Mehrere Blöcke gross. -block.duriumwall-large.name = grosse Diriumwand -block.duriumwall-large.fulldescription = Eine sehr starke Abwehrblockade. Mehrere Blöcke gross. -block.titaniumshieldwall.name = geschützte Wand -block.titaniumshieldwall.fulldescription = Ein starker Abwehrblock mit einem extra eingebauten Schild. Benötigt Energie. Verwendet Energie, um feindliche Kugeln zu absorbieren. Es wird empfohlen, Verstärker zu verwenden, um diesem Block Energie zuzuführen. -block.repairturret.name = Reparaturgeschütz -block.repairturret.fulldescription = Repariert beschädigte Blöcke in der Nähe in einem langsamen Tempo. Nutzt geringe Mengen an Energie. -block.megarepairturret.name = Reparaturgeschütz II -block.megarepairturret.fulldescription = Repariert in der Nähe beschädigte Blöcke in Reichweite zu einem vernünftigen Preis. Verwendet Energie. -block.shieldgenerator.name = Schildgenerator -block.shieldgenerator.fulldescription = Ein fortgeschrittener Verteidigungsblock. Schützt alle Blöcke in einem Radius vor Angriffen. Bei keinem Betrieb langsamer Strom verbrauch, verliert bei Kugelkontakt jedoch schnell Energie. -block.door.name = Tür -block.door.fulldescription = Ein Block, der durch Antippen geöffnet und geschlossen werden kann. -block.door-large.name = grosse Tür -block.door-large.fulldescription = Ein Block der mehrere Felder gross ist und der durch Antippen geöffnet und geschlossen werden kann. -block.conduit.name = Leitungsrohr -block.conduit.fulldescription = Grundlegender Flüssigkeitstransportblock. Funktioniert wie ein Förderband, aber mit Flüssigkeiten. Am besten mit Pumpen oder anderen Leitungen verwenden. Kann als Brücke für Gegner und Spieler verwendet werden. -block.pulseconduit.name = Pulsleitungsrohr -block.pulseconduit.fulldescription = Fortschrittlicher Flüssigkeitstransportblock. Transportiert Flüssigkeiten schneller und speichert mehr als normale Leitungsrohre. -block.liquidrouter.name = flüssigkeiten verteiler -block.liquidrouter.fulldescription = Funktioniert ähnlich wie ein Router. Akzeptiert Flüssigkeit von einer Seite und gibt sie auf die anderen Seiten aus. Nützlich zum Aufspalten von Flüssigkeit aus eines einzelnen Leitungsrohres in mehrere andere Leitungensrohre. -block.conveyor.name = Förderband -block.conveyor.fulldescription = Grundelement Transport Block. Bewegt Gegenstände nach vorne und legt sie automatisch in Türmen oder ähnliches. Drehbar. Kann als Brücke für Gegner und Spieler verwendet werden. -block.steelconveyor.name = Stahlförderband -block.steelconveyor.fulldescription = Erweiterter Transportblock Bewegt Gegenstände schneller als Standardförderer. -block.poweredconveyor.name = Impulsförderband -block.poweredconveyor.fulldescription = Der ultimative Transportblock für Gegenstände. Bewegt Gegenstände noch schneller als Stahlförderer. -block.router.name = Verteiler -block.router.fulldescription = Akzeptiert Objekte aus einer Richtung und gibt sie in 3 andere Richtungen aus. Kann auch eine bestimmte Anzahl von Gegenständen speichern. Geeignet zum Aufteilen der Materialien von einem Bohrer in mehrere Geschütze. -block.junction.name = Kreuzung -block.junction.fulldescription = Fungiert als Brücke für zwei kreuzende Förderbänder. Nützlich in Situationen mit zwei verschiedenen Förderbänder, die unterschiedliche Materialien zu verschiedenen Orten transportieren. -block.conveyortunnel.name = Förderbandtunnel -block.conveyortunnel.fulldescription = Transportiert Artikel unter Blöcken. Verwendung für einen Tunnel, der in den zu untertunnelnden Block führt, und einen auf der anderen Seite. Stellen Sie sicher, dass beide Tunnel in entgegengesetzte Richtungen weisen, das heisst das die Tunnel voneinander weggucken. -block.liquidjunction.name = flüssigkeite Kreuzung -block.liquidjunction.fulldescription = Funktioniert als Brücke für zwei kreuzende Leitungsrohre. Nützlich in Situationen mit zwei verschiedenen Leitungen, die unterschiedliche Flüssigkeiten zu verschiedenen Orten transportieren. -block.liquiditemjunction.name = Flüssigkeit-Gegenstand-Kreuzung -block.liquiditemjunction.fulldescription = Fungiert als Brücke zum Überqueren von Leitungsrohre und Förderbändern. -block.powerbooster.name = Energieverstärker -block.powerbooster.fulldescription = Verteilt die Energie an alle Blöcke innerhalb seines Radius. -block.powerlaser.name = Energielaser -block.powerlaser.fulldescription = Erzeugt einen Laser, der die Kraft auf den Block davor überträgt. Erzeugt selbst keine Energie. Am besten mit Generatoren oder anderen Lasern verwendet. -block.powerlaserrouter.name = Laser Verteiler -block.powerlaserrouter.fulldescription = Laser, der die Kraft in drei Richtungen gleichzeitig verteilt. Nützlich in Situationen, in denen mehrere Blöcke von einem Generator mit Strom versorgt werden müssen. -block.powerlasercorner.name = Laser-Ecke -block.powerlasercorner.fulldescription = Laser, der die Kraft in zwei Richtungen gleichzeitig verteilt. Nützlich in Situationen, in denen mehrere Blöcke von einem Generator mit Strom versorgt werden müssen und ein Router ungenau ist. -block.teleporter.name = Teleporter -block.teleporter.fulldescription = Erweiterter Transportblock Teleporter geben Gegenstände in andere Teleporter derselben Farbe ein. Tut nichts, wenn keine Teleporter derselben Farbe existieren. Wenn mehrere Teleporter mit derselben Farbe existieren, wird eine zufällige ausgewählt. Verwendet Energie. Tippen, um die Farbe zu ändern. -block.sorter.name = Sortierer -block.sorter.fulldescription = Sortiert den Gegenstand nach Materialart. Das zu akzeptierende Material wird durch die Farbe im Block angezeigt. Alle Artikel, die dem Sortiermaterial entsprechen, werden vorwärts ausgegeben, alles andere wird nach links und rechts ausgegeben. -block.core.name = Kern -block.pump.name = Pumpe -block.pump.fulldescription = Pumpen Flüssigkeiten aus einem Quellblock - meist Wasser, Lava oder Öl. Gibt Flüssigkeit in benachbarte Leitungsrohre aus. -block.fluxpump.name = flux Pumpe -block.fluxpump.fulldescription = Eine erweiterte Version der Pumpe. Speichert mehr Flüssigkeit und pumpt Flüssigkeit schneller. -block.smelter.name = Schmelzer -block.smelter.fulldescription = Der essentielle Bastelblock. Wenn 1x Eisen und 1x Kohle eingegeben wird, wird 1x Stahl ausgegeben. -block.crucible.name = Tiegel -block.crucible.fulldescription = Ein fortgeschrittener Handwerksblock. Braucht Kohle um zu funktionieren. Wenn 1x Titan und 1x Stahl eingegeben wird, wird 1x Dirium ausgegeben. -block.coalpurifier.name = Kohle-Extraktor -block.coalpurifier.fulldescription = Ein einfacher Extraktorblock. Gibt Kohle aus, wenn der Block mit grossen Mengen Wasser und Stein gefüllt wird. -block.titaniumpurifier.name = Titan-Extraktor -block.titaniumpurifier.fulldescription = Ein Standard-Extraktorblock. Gibt Titan aus wenn er mit grossen Mengen Wasser und Eisen gefüllt wird. -block.oilrefinery.name = Ölraffinerie -block.oilrefinery.fulldescription = Veredelt grosse Mengen Öl zu Kohle. Nützlich für die Betankung von Blöcken die Kohle benutzen wie z.b. Waffentürme, wenn Kohleadern knapp sind. -block.stoneformer.name = Steinformer -block.stoneformer.fulldescription = Verfestigt flüssige Lava zu Stein. Nützlich für die Herstellung von grossen Mengen von Stein für Kohle-Extraktor. -block.lavasmelter.name = Lava-Schmelzer -block.lavasmelter.fulldescription = Verwendet Lava, um Eisen zu Stahl schmelzen. Eine Alternative zum Schmelzer. Nützlich in Situationen, in denen Kohle knapp ist. -block.stonedrill.name = Steinbohrer -block.stonedrill.fulldescription = Der wesentliche Bohrer. Wenn er auf Steinplatten gelegt wird, gibt er Steine ​​mit einer langsamen Geschwindigkeit auf endlosen Zeit aus. -block.irondrill.name = Eisenbohrer -block.irondrill.fulldescription = Eine grundlegender Bohrer. Wenn es auf Eisenerz gelegt wird, gibt er Eisen auf endloser Zeit langsam aus. -block.coaldrill.name = Kohlenbohrer -block.coaldrill.fulldescription = Eine grundlegender Bohrer. Wenn es auf Kohleerz platziert wird, gibt er für endloser Zeit langsam Kohle aus. -block.thoriumdrill.name = Uran-Bohrer -block.thoriumdrill.fulldescription = Ein fortgeschrittener Bohrer. Wenn es auf Uranerz platziert wird, gibt er Uran mit einer langsamen Geschwindigkeit auf endloser Zeit ab. -block.titaniumdrill.name = Titan-Bohrer -block.titaniumdrill.fulldescription = Ein fortgeschrittener Bohrer. Wenn es auf Titanerz platziert wird, gibt er Titan langsam aus für undendlich langer Zeit -block.omnidrill.name = Omni-Bohrer -block.omnidrill.fulldescription = Der ultimative Bohrer. Baut in schnellem Tempo jegliches Erz ab. -block.coalgenerator.name = Kohle-Generator -block.coalgenerator.fulldescription = Der wesentliche Generator. Erzeugt Energie aus Kohle. Gibt Energie als Laser an seine 4 Seiten aus. -block.thermalgenerator.name = thermischer Generator -block.thermalgenerator.fulldescription = Erzeugt Energie aus Lava. Gibt Energie als Laser an seine 4 Seiten aus. -block.combustiongenerator.name = Verbrennungsgenerator -block.combustiongenerator.fulldescription = Erzeugt Energie aus Öl. Gibt Energie als Laser an seine 4 Seiten aus. -block.rtgenerator.name = RTG-Generator -block.rtgenerator.fulldescription = Erzeugt geringe Mengen an Energie aus dem radioaktiven Zerfall von Uran. Gibt Energie als Laser an seine 4 Seiten aus. -block.nuclearreactor.name = Kernreaktor -block.nuclearreactor.fulldescription = Eine erweiterte Version des RTG-Generators und der ultimative Energie Generator. Erzeugt Strom aus Uran. Erfordert konstante Wasserkühlung. Sehr heiss; explodiert heftig, wenn zu wenig Kühlmittel zugeführt wird. -block.turret.name = Geschütz -block.turret.fulldescription = Ein einfacher, billiger Turm. Verwendet Stein für Munition. Hat etwas mehr Reichweite als das Doppelgeschütz. -block.doubleturret.name = Doppelgeschütz -block.doubleturret.fulldescription = Eine etwas stärkere Version des Geschützes. Verwendet Stein für Munition. Hat deutlich mehr Schaden, hat aber eine geringere Reichweite. Schiesst zwei Kugeln. -block.machineturret.name = Gatling Geschütz -block.machineturret.fulldescription = Ein Standard-Allround-Turm. Verwendet Eisen für Munition. Hat eine schnelle Feuerrate mit gutem Schaden. -block.shotgunturret.name = Splittergeschütz -block.shotgunturret.fulldescription = Ein Standard-Turm. Verwendet Eisen für Munition. Schiesst 7 Kugeln auf einmal. Geringere Reichweite, aber höhere Schadensleistung als das Gatling Geschütz -block.flameturret.name = Flammenwerfer -block.flameturret.fulldescription = Fortschrittlicher Nahbereichswaffe. Verwendet Kohle für Munition. Hat eine sehr geringe Reichweite, aber sehr hohen Schaden. Gut für Nahkampf. Empfohlen für den Einsatz hinter Mauern. -block.sniperturret.name = Schienenkanone -block.sniperturret.fulldescription = Fortschrittliches Reichweitengeschütz. Verwendet Stahl für Munition. Sehr hoher Schaden, aber niedrige Feuerrate. Teuer zu verwenden, kann aber aufgrund seiner Reichweite weit entfernt von den feindlichen Linien platziert werden. -block.mortarturret.name = Flakgeschütz -block.mortarturret.fulldescription = Fortschrittlicher Flächen-Schaden Turm. Verwendet Kohle für Munition. Sehr langsame Feuerrate und Geschosse, aber sehr hoher Einzelziel- und Flächenschaden. Nützlich gegen grosse Mengen von Feinden. -block.laserturret.name = Laserturm -block.laserturret.fulldescription = Fortschrittlicher Einzelziel-Turm. Verwendet Strom. Guter Allround-Revolver für mittlere Reichweiten. Nur Einzelziel. Verfehlt nie. -block.waveturret.name = Teslakanone -block.waveturret.fulldescription = Erweiterter Mehrfach-Ziele-Turm. Verwendet Strom. Mittlere Reichweite. Verfehlt nie. Im Durchschnitt zu wenig Schaden, aber kann mehrere Feinde gleichzeitig mit Kettenblitz treffen. -block.plasmaturret.name = Plasmageschütz -block.plasmaturret.fulldescription = Hochentwickelte Version des Flammenwerfers. Verwendet Kohle als Munition. Sehr hoher Schaden, niedriger bis mittlerer Reichweite. -block.chainturret.name = Kettengeschütz -block.chainturret.fulldescription = Die ultimative Schnellfeuer Waffe. Verwendet Uran als Munition. Schiesst grosse Kugeln mit hoher Feuerrate. Mittlere Reichweite. Mehrere Felder gross. Hält extrem viel aus. -block.titancannon.name = Titan Kanone -block.titancannon.fulldescription = Die ultimative Langstrecken Kanone. Verwendet Uran als Munition. Schiesst grosse Flächen-Schadenden-Granaten mit einer mittleren Feuerrate ab. Lange Reichweite. Ist mehrere Felder gross. Hält extrem viel aus. -block.playerspawn.name = Spielerspawn -block.enemyspawn.name = Gegnerspawn +text.about=Erstellt von [ROYAL] Anuken. [] \nUrsprünglich ein Eintrag im [orange] GDL [] MM Jam.\n\nCredits: \n- SFX gemacht mit [yellow] bfxr [] - Musik gemacht von [green] RoccoW [] / gefunden auf [lime] FreeMusicArchive.org [] \n\nBesonderer Dank geht an: \n- [coral] MitchellFJN []: Umfangreicher Spieletest und Feedback \n- [sky] Luxray5474 []: Wiki-Arbeit, Code-Beiträge \n- Alle Beta-Tester auf itch.io und Google Play\n +text.discord=Trete dem Mindustry Discord bei! +text.gameover=Der Kern wurde zerstört. +text.highscore=[YELLOW] Neuer Highscore! +text.lasted=Du hast bis zur folgenden Welle überlebt +text.level.highscore=High Score: [accent] {0} +text.level.delete.title=Löschen bestätigen +text.level.select=Level Auswahl +text.level.mode=Spielmodus: +text.savegame=Spiel speichern +text.loadgame=Spiel laden +text.joingame=Spiel beitreten +text.quit=Verlassen +text.about.button=Info +text.name=Name: +text.public=Öffentlich +text.players={0} Spieler online +text.players.single={0} Spieler online +text.server.mismatch=Paketfehler: Mögliche Client / Server-Version stimmt nicht überein. Stell sicher, dass du und der Host die neueste Version von Mindustry haben! +text.server.kicked.kick=Du wurdest vom Server gekickt! +text.server.kicked.invalidPassword=Falsches Passwort. +text.server.connected={0} ist beigetreten +text.server.disconnected={0} hat die Verbindung getrennt. +text.nohost=Server kann nicht auf einer benutzerdefinierten Karte gehostet werden! +text.hostserver=Server hosten +text.host=Host +text.hosting=[accent] Server wird geöffnet... +text.hosts.refresh=Aktualisieren +text.hosts.discovering=Suche nach LAN-Spielen +text.server.refreshing=Server wird aktualisiert +text.hosts.none=[lightgray] Keine LAN Spiele gefunden! +text.host.invalid=[scarlet] Kann keine Verbindung zum Host herstellen. +text.server.add=Server hinzufügen +text.server.delete=Bist du dir sicher das du diesen Server löschen möchtest? +text.server.hostname=Host: {0} +text.server.edit=Server bearbeiten +text.joingame.byip=Über IP beitreten ... +text.joingame.title=Spiel beitreten +text.joingame.ip=IP: +text.disconnect=Verbindung unterbrochen. +text.connecting=[accent] Verbindet... +text.connecting.data=[accent] Weltdaten werden geladen... +text.connectfail=[crimson] Verbindung zum Server konnte nicht hergestellt werden: [orange]{0} +text.server.port=Port: +text.server.invalidport=Falscher Port! +text.server.error=[crimson] Fehler beim Hosten des Servers: [orange] {0} +text.tutorial.back=< Zurück +text.tutorial.next=Weiter > +text.save.new=Neuer Spielstand +text.save.overwrite=Möchten du diesen Spielstand wirklich überschreiben? +text.overwrite=Überschreiben +text.save.none=Keine Spielstände gefunden! +text.saveload=[accent] Speichern ... +text.savefail=Fehler beim Speichern des Spiels! +text.save.delete.confirm=Möchtest du diesen Spielstand wirklich löschen? +text.save.delete=Löschen +text.save.export=Spielstand exportieren +text.save.import.invalid=[orange] Dieser Spielstand ist ungültig! +text.save.import.fail=[crimson] Spielstand konnte nicht importiert werden: [orange] {0} +text.save.export.fail=[crimson] Spielstand konnte nicht exportiert werden: [orange] {0} +text.save.import=Spielstand importieren +text.save.newslot=Name speichern: +text.save.rename=Umbenennen +text.save.rename.text=Neuer Name +text.selectslot=Wähle einen Spielstand +text.slot=[accent] Platz {0} +text.save.corrupted=[orange] Datei beschädigt oder ungültig! +text.empty= +text.on=An +text.off=Aus +text.save.autosave=Automatisches Speichern: {0} +text.save.map=Karte: {0} +text.save.wave=Welle: {0} +text.save.date=Zuletzt gespeichert: {0} +text.confirm=Bestätigen +text.delete=Löschen +text.ok=OK +text.open=Öffnen +text.cancel=Abbruch +text.openlink=Link öffnen +text.back=Zurück +text.quit.confirm=Willst du wirklich aufhören? +text.loading=[accent] Wird geladen ... +text.wave=[orange] Welle {0} +text.wave.waiting=Welle in {0} +text.waiting=Warten... +text.enemies={0} Feinde +text.enemies.single={0} Feind +text.loadimage=Bild laden +text.saveimage=Bild speichern +text.editor.badsize=[orange]Ungültige Bildabmessungen! [] Gültige Kartenabmessungen: {0} +text.editor.errorimageload=Fehler beim Laden des Bildes: [orange] {0} +text.editor.errorimagesave=Fehler beim Speichern des Bildes: [orange] {0} +text.editor.generate=Generieren +text.editor.resize=Grösse\nanpassen +text.editor.loadmap=Karte\nladen +text.editor.savemap=Karte\nspeichern +text.editor.loadimage=Bild\nladen +text.editor.saveimage=Bild\nspeichern +text.editor.unsaved=[crimson] Du hast Änderungen nicht gespeichert [] Möchtest du wirklich aufhören? +text.editor.brushsize=Pinselgrösse: {0} +text.editor.noplayerspawn=Diese Karte hat keinen Spielerspawnpunkt! +text.editor.manyplayerspawns=Maps können nicht mehr als einen Spawnpunkt des Spielers haben! +text.editor.manyenemyspawns=Kann nicht mehr als {0} feindliche Spawnpunkte haben! +text.editor.resizemap=Grösse der Karte ändern +text.editor.resizebig=[crimson] Warnung! [] Karten, die grösser als 256 Einheiten sind, können ruckeln und instabil sein. +text.editor.mapname=Map Name +text.editor.overwrite=[accent] Warnung! Dies überschreibt eine vorhandene Map. +text.editor.selectmap=Wähle eine Map zum Laden: +text.width=Breite: +text.height=Höhe: +text.randomize=Zufällig +text.apply=Anwenden +text.update=Aktualisieren +text.menu=Menü +text.play=Spielen +text.load=Laden +text.save=Speichern +text.settings=Einstellungen +text.tutorial=Tutorial +text.editor=Bearbeiter +text.mapeditor=Karten Bearbeiter +text.donate=Spenden +text.settings.reset=Auf Standard zurücksetzen +text.settings.controls=Steuerung +text.settings.game=Spiel +text.settings.sound=Audio +text.settings.graphics=Grafiken +text.upgrades=Verbesserungen +text.purchased=[LIME] Erstellt! +text.weapons=Waffen +text.paused=Pausiert +text.error.title=[crimson] Ein Fehler ist aufgetreten +text.error.crashmessage=[SCARLET] Es ist ein unerwarteter Fehler aufgetreten, der einen Absturz verursacht hätte. [] Bitte geben Sie die genauen Umstände an, unter denen dieser Fehler passiert ist, für den Entwickler: [ORANGE] anukendev@gmail.com [] +text.error.crashtitle=EIn Fehler ist aufgetreten! +text.blocks.blockinfo=Blockinfo: +text.blocks.powercapacity=Energiekapazität +text.blocks.powershot=Energie / Schuss +text.blocks.size=Grösse +text.blocks.liquidcapacity=Flüssigkeitskapazität +text.blocks.maxitemssecond=Max Gegenstände / Sekunde +text.blocks.powerrange=Energiereichweite +text.blocks.itemcapacity=Gegenstand Kapazität +text.blocks.inputliquid=Flüssigkeiten Eingabe +text.blocks.inputitem=Eingabe Gegenstand +text.blocks.explosive=Hochexplosiv! +text.blocks.health=Lebenspunkte +text.blocks.inaccuracy=Ungenauigkeit +text.blocks.shots=Schüsse +text.blocks.inputcapacity=Eingabekapazität +text.blocks.outputcapacity=Ausgabekapazität +setting.difficulty.easy=Leicht +setting.difficulty.normal=Normal +setting.difficulty.hard=Schwer +setting.difficulty.insane=Unmöglich +setting.difficulty.purge=Auslöschung +setting.difficulty.name=Schwierigkeit +setting.screenshake.name=Bildschirm wackeln +setting.smoothcam.name=Glatte Kamera +setting.indicators.name=Feind Indikatoren +setting.effects.name=Effekte anzeigen +setting.sensitivity.name=Kontroller Empfindlichkeit +setting.saveinterval.name=Autosave Häufigkeit +setting.seconds={0} Sekunden +setting.fps.name=Zeige FPS +setting.vsync.name=VSync +setting.lasers.name=Zeige Energielaser +setting.healthbars.name=Zeige Objekt Lebensbalken +setting.pixelate.name=Pixel Bildschirm +setting.musicvol.name=Musiklautstärke +setting.mutemusic.name=Musik stummschalten +setting.sfxvol.name=Audioeffekte Lautstärke +setting.mutesound.name=Audioeffekte stummschalten +map.maze.name=Labyrinth +map.fortress.name=Festung +map.sinkhole.name=Sinkloch +map.caves.name=Höhlen +map.volcano.name=Vulkan +map.caldera.name=Lavakessel +map.scorch.name=Flammen +map.desert.name=Wüste +map.island.name=Insel +map.grassland.name=Grasland +map.tundra.name=Kältesteppe +map.spiral.name=Spirale +map.tutorial.name=Tutorial +keybind.move_x.name=bewege_x +keybind.move_y.name=bewege_y +keybind.select.name=wählen +keybind.break.name=Unterbrechung +keybind.shoot.name=Schiess +keybind.zoom_hold.name=zoomen_halten +keybind.zoom.name=zoomen +keybind.menu.name=Menü +keybind.pause.name=Pause +keybind.dash.name=Bindestrich +keybind.rotate_alt.name=drehen_alt +keybind.rotate.name=Drehen +mode.waves.name=Wellen +mode.sandbox.name=Sandkasten +mode.freebuild.name=Freier Bau +item.stone.name=Stein +item.coal.name=Kohle +item.titanium.name=Titan +item.thorium.name=Uran +item.sand.name=Sand +liquid.water.name=Wasser +liquid.lava.name=Lava +liquid.oil.name=Öl +block.door.name=Tür +block.door-large.name=grosse Tür +block.conduit.name=Leitungsrohr +block.pulseconduit.name=Pulsleitungsrohr +block.liquidrouter.name=flüssigkeiten verteiler +block.conveyor.name=Förderband +block.router.name=Verteiler +block.junction.name=Kreuzung +block.liquidjunction.name=flüssigkeite Kreuzung +block.sorter.name=Sortierer +block.smelter.name=Schmelzer +text.credits=Credits +text.link.discord.description=the official Mindustry discord chatroom +text.link.github.description=Game source code +text.link.dev-builds.description=Unstable development builds +text.link.trello.description=Official trello board for planned features +text.link.itch.io.description=itch.io page with PC downloads and web version +text.link.google-play.description=Google Play store listing +text.link.wiki.description=official Mindustry wiki +text.linkfail=Failed to open link!\nThe URL has been copied to your cliboard. +text.editor.web=The web version does not support the editor!\nDownload the game to use it. +text.web.unsupported=The web version does not support this feature! Download the game to use it. +text.multiplayer.web=This version of the game does not support multiplayer!\nTo play multiplayer from your browser, use the "multiplayer web version" link at the itch.io page. +text.host.web=The web version does not support hosting games! Download the game to use this feature. +text.map.delete=Are you sure you want to delete the map "[orange]{0}[]"? +text.construction.title=Block Construction Guide +text.construction=You've just selected [accent]block construction mode[].\n\nTo begin placing, simply tap a valid location near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Shift the selection[] by holding and dragging any block in the selection.\n- [accent]Place blocks in a line[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel construction or selection[] by pressing the X at the bottom left. +text.deconstruction.title=Block Deconstruction Guide +text.deconstruction=You've just selected [accent]block deconstruction mode[].\n\nTo begin breaking, simply tap a block near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin de-constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Remove blocks in an area[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel deconstruction or selection[] by pressing the X at the bottom left. +text.showagain=Don't show again next session +text.unlocks=Unlocks +text.addplayers=Add/Remove Players +text.newgame=New Game +text.maps=Maps +text.maps.none=[LIGHT_GRAY]No maps found! +text.unlocked=New Block Unlocked! +text.unlocked.plural=New Blocks Unlocked! +text.server.rollback=Rollback +text.server.rollback.numberfield=Rollback Amount: +text.blocks.editlogs=Edit Logs +text.block.editlogsnotfound=[red]There are no edit logs for this location. +text.server.player.host={0} (host) +text.server.closing=[accent]Closing server... +text.server.kicked.fastShoot=You are shooting too quickly. +text.server.kicked.clientOutdated=Outdated client! Update your game! +text.server.kicked.serverOutdated=Outdated server! Ask the host to update! +text.server.kicked.banned=You are banned on this server. +text.server.kicked.recentKick=You have been kicked recently.\nWait before connecting again. +text.server.kicked.nameInUse=There is someone with that name\nalready on this server. +text.server.kicked.nameEmpty=Your name must contain at least one character or number. +text.server.kicked.idInUse=You are already on this server! Connecting with two accounts is not permitted. +text.server.kicked.customClient=This server does not support custom builds. Download an official version. +text.host.info=The [accent]host[] button hosts a server on ports [scarlet]6567[] and [scarlet]6568.[]\nAnybody on the same [LIGHT_GRAY]wifi or local network[] should be able to see your server in their server list.\n\nIf you want people to be able to connect from anywhere by IP, [accent]port forwarding[] is required.\n\n[LIGHT_GRAY]Note: If someone is experiencing trouble connecting to your LAN game, make sure you have allowed Mindustry access to your local network in your firewall settings. +text.join.info=Here, you can enter a [accent]server IP[] to connect to, or discover [accent]local network[] servers to connect to.\nBoth LAN and WAN multiplayer is supported.\n\n[LIGHT_GRAY]Note: There is no automatic global server list; if you want to connect to someone by IP, you would need to ask the host for their IP. +text.server.friendlyfire=Friendly Fire +text.trace=Trace Player +text.trace.playername=Player name: [accent]{0} +text.trace.ip=IP: [accent]{0} +text.trace.id=Unique ID: [accent]{0} +text.trace.android=Android Client: [accent]{0} +text.trace.modclient=Custom Client: [accent]{0} +text.trace.totalblocksbroken=Total blocks broken: [accent]{0} +text.trace.structureblocksbroken=Structure blocks broken: [accent]{0} +text.trace.lastblockbroken=Last block broken: [accent]{0} +text.trace.totalblocksplaced=Total blocks placed: [accent]{0} +text.trace.lastblockplaced=Last block placed: [accent]{0} +text.invalidid=Invalid client ID! Submit a bug report. +text.server.bans=Bans +text.server.bans.none=No banned players found! +text.server.admins=Admins +text.server.admins.none=No admins found! +text.server.outdated=[crimson]Outdated Server![] +text.server.outdated.client=[crimson]Outdated Client![] +text.server.version=[lightgray]Version: {0} +text.server.custombuild=[yellow]Custom Build +text.confirmban=Are you sure you want to ban this player? +text.confirmunban=Are you sure you want to unban this player? +text.confirmadmin=Are you sure you want to make this player an admin? +text.confirmunadmin=Are you sure you want to remove admin status from this player? +text.disconnect.data=Failed to load world data! +text.server.addressinuse=Address already in use! +text.save.difficulty=Difficulty: {0} +text.copylink=Copy Link +text.changelog.title=Changelog +text.changelog.loading=Getting changelog... +text.changelog.error.android=[orange]Note that the changelog sometimes does not work on Android 4.4 and below!\nThis is due to an internal Android bug. +text.changelog.error.ios=[orange]The changelog is currently not supported in iOS. +text.changelog.error=[scarlet]Error getting changelog!\nCheck your internet connection. +text.changelog.current=[yellow][[Current version] +text.changelog.latest=[orange][[Latest version] +text.saving=[accent]Saving... +text.unknown=Unknown +text.custom=Custom +text.builtin=Built-In +text.map.delete.confirm=Are you sure you want to delete this map? This action cannot be undone! +text.map.random=[accent]Random Map +text.map.nospawn=This map does not have any cores for the player to spawn in! Add a [ROYAL]blue[] core to this map in the editor. +text.editor.slope=\\ +text.editor.openin=Open In Editor +text.editor.oregen=Ore Generation +text.editor.oregen.info=Ore Generation: +text.editor.mapinfo=Map Info +text.editor.author=Author: +text.editor.description=Description: +text.editor.name=Name: +text.editor.teams=Teams +text.editor.elevation=Elevation +text.editor.saved=Saved! +text.editor.save.noname=Your map does not have a name! Set one in the 'map info' menu. +text.editor.save.overwrite=Your map overwrites a built-in map! Pick a different name in the 'map info' menu. +text.editor.import.exists=[scarlet]Unable to import:[] a built-in map named '{0}' already exists! +text.editor.import=Import... +text.editor.importmap=Import Map +text.editor.importmap.description=Import an already existing map +text.editor.importfile=Import File +text.editor.importfile.description=Import an external map file +text.editor.importimage=Import Terrain Image +text.editor.importimage.description=Import an external map image file +text.editor.export=Export... +text.editor.exportfile=Export File +text.editor.exportfile.description=Export a map file +text.editor.exportimage=Export Terrain Image +text.editor.exportimage.description=Export a map image file +text.editor.overwrite.confirm=[scarlet]Warning![] A map with this name already exists. Are you sure you want to overwrite it? +text.fps=FPS: {0} +text.tps=TPS: {0} +text.ping=Ping: {0}ms +text.language.restart=Please restart your game for the language settings to take effect. +text.settings.language=Language +text.settings.rebind=Rebind +text.yes=Yes +text.no=No +text.info.title=[accent]Info +text.blocks.targetsair=Targets Air +text.blocks.itemspeed=Units Moved +text.blocks.shootrange=Range +text.blocks.poweruse=Power Use +text.blocks.inputitemcapacity=Input Item Capacity +text.blocks.outputitemcapacity=Input Item Capacity +text.blocks.maxpowergeneration=Max Power Generation +text.blocks.powertransferspeed=Power Transfer +text.blocks.craftspeed=Production Speed +text.blocks.inputliquidaux=Aux Liquid +text.blocks.inputitems=Input Items +text.blocks.outputitem=Output Item +text.blocks.drilltier=Drillables +text.blocks.drillspeed=Base Drill Speed +text.blocks.liquidoutput=Liquid Output +text.blocks.liquiduse=Liquid Use +text.blocks.coolant=Coolant +text.blocks.coolantuse=Coolant Use +text.blocks.inputliquidfuel=Fuel Liquid +text.blocks.liquidfueluse=Liquid Fuel Use +text.blocks.reload=Reload +text.blocks.inputfuel=Fuel +text.blocks.fuelburntime=Fuel Burn Time +text.unit.blocks=blocks +text.unit.powersecond=power units/second +text.unit.liquidsecond=liquid units/second +text.unit.itemssecond=items/second +text.unit.pixelssecond=pixels/second +text.unit.liquidunits=liquid units +text.unit.powerunits=power units +text.unit.degrees=degrees +text.unit.seconds=seconds +text.unit.none= +text.unit.items=items +text.category.general=General +text.category.power=Power +text.category.liquids=Liquids +text.category.items=Items +text.category.crafting=Crafting +text.category.shooting=Shooting +setting.fullscreen.name=Fullscreen +setting.multithread.name=Multithreading +setting.previewopacity.name=Placing Preview Opacity +setting.minimap.name=Show Minimap +text.keybind.title=Rebind Keys +keybind.block_info.name=block_info +keybind.chat.name=chat +keybind.player_list.name=player_list +keybind.console.name=console +mode.text.help.title=Description of modes +mode.waves.description=the normal mode. limited resources and automatic incoming waves. +mode.sandbox.description=infinite resources and no timer for waves. +mode.freebuild.description=limited resources and no timer for waves. +content.item.name=Items +content.liquid.name=Liquids +content.unit-type.name=Units +content.recipe.name=Blocks +item.stone.description=A common raw material. Used for separating and refining into other materials, or melting into lava. +item.tungsten.name=Tungsten +item.tungsten.description=A common, but very useful structure material. Used in drills and heat-resistant blocks such as generators and smelteries. +item.lead.name=Lead +item.lead.description=A basic starter material. Used extensively in electronics and liquid transportation blocks. +item.coal.description=A common and readily available fuel. +item.carbide.name=Carbide +item.carbide.description=A tough alloy made with tungsten and carbon. Used in advanced transportation blocks and high-tier drills. +item.titanium.description=A rare super-light metal used extensively in liquid transportation, drills and aircraft. +item.thorium.description=A dense, radioactive metal used as structural support and nuclear fuel. +item.silicon.name=Silicon +item.silcion.description=An extremely useful semiconductor, with applications in solar panels and many complex electronics. +item.plastanium.name=Plastanium +item.plastanium.description=A light, ductile material used in advanced aircraft and fragmentation ammunition. +item.phase-matter.name=Phase Matter +item.surge-alloy.name=Surge Alloy +item.biomatter.name=Biomatter +item.biomatter.description=A clump of organic mush; used for conversion into oil or as a basic fuel. +item.sand.description=A common material that is used extensively in smelting, both in alloying and as a flux. +item.blast-compound.name=Blast Compound +item.blast-compound.description=A volatile compound used in bombs and explosives. While it can burned as fuel, this is not advised. +item.pyratite.name=Pyratite +item.pyratite.description=An extremely flammable substance used in incendiary weapons. +liquid.cryofluid.name=Cryofluid +text.item.explosiveness=[LIGHT_GRAY]Explosiveness: {0} +text.item.flammability=[LIGHT_GRAY]Flammability: {0} +text.item.radioactivity=[LIGHT_GRAY]Radioactivity: {0} +text.item.fluxiness=[LIGHT_GRAY]Flux Power: {0} +text.item.hardness=[LIGHT_GRAY]Hardness: {0} +text.liquid.heatcapacity=[LIGHT_GRAY]Heat Capacity: {0} +text.liquid.viscosity=[LIGHT_GRAY]Viscosity: {0} +text.liquid.temperature=[LIGHT_GRAY]Temperature: {0} +block.tungsten-wall.name=Tungsten Wall +block.tungsten-wall-large.name=Large Tungsten Wall +block.carbide-wall.name=Carbide Wall +block.carbide-wall-large.name=Large Carbide Wall +block.thorium-wall.name=Thorium Wall +block.thorium-wall-large.name=Large Thorium Wall +block.duo.name=Duo +block.scorch.name=Scorch +block.hail.name=Hail +block.lancer.name=Lancer +block.titanium-conveyor.name=Titanium Conveyor +block.splitter.name=Splitter +block.splitter.description=Outputs items into two opposite directions immediately after they are recieved. +block.router.description=Splits items into all 4 directions. Can store items as a buffer. +block.distributor.name=Distributor +block.distributor.description=A splitter that can split items into 8 directions. +block.sorter.description=Sorts items. If an item matches the selection, it is allowed to pass. Otherwise, the item is outputted to the left and right. +block.overflow-gate.name=Overflow Gate +block.overflow-gate.description=A combination splitter and router that only outputs to the left and right if the front path is blocked. +block.bridgeconveyor.name=Bridge Conveyor +block.bridgeconveyor.description=A conveyor that can go over other blocks, for up to two total blocks. +block.arc-smelter.name=Arc Smelter +block.silicon-smelter.name=Silicon Smelter +block.phase-weaver.name=Phase Weaver +block.pulverizer.name=Pulverizer +block.cryofluidmixer.name=Cryofluid Mixer +block.melter.name=Melter +block.incinerator.name=Incinerator +block.biomattercompressor.name=Biomatter Compressor +block.separator.name=Separator +block.centrifuge.name=Centrifuge +block.power-node.name=Power Node +block.power-node-large.name=Large Power Node +block.battery.name=Battery +block.battery-large.name=Large Battery +block.combustion-generator.name=Combustion Generator +block.turbine-generator.name=Turbine Generator +block.tungsten-drill.name=Tungsten Drill +block.carbide-drill.name=Carbide Drill +block.laser-drill.name=Laser Drill +block.water-extractor.name=Water Extractor +block.cultivator.name=Cultivator +block.dart-ship-factory.name=Dart Ship Factory +block.delta-mech-factory.name=Delta Mech Factory +block.dronefactory.name=Drone Factory +block.repairpoint.name=Repair Point +block.resupplypoint.name=Resupply Point +block.liquidtank.name=Liquid Tank +block.bridgeconduit.name=Bridge Conduit +block.mechanical-pump.name=Mechanical Pump +block.itemsource.name=Item Source +block.itemvoid.name=Item Void +block.liquidsource.name=Liquid Source +block.powervoid.name=Power Void +block.powerinfinite.name=Power Infinite +block.unloader.name=Unloader +block.sortedunloader.name=Sorted Unloader +block.vault.name=Vault +block.wave.name=Wave +block.swarmer.name=Swarmer +block.salvo.name=Salvo +block.ripple.name=Ripple +block.phase-conveyor.name=Phase Conveyor +block.bridge-conveyor.name=Bridge Conveyor +block.plastanium-compressor.name=Plastanium Compressor +block.pyratite-mixer.name=Pyratite Mixer +block.blast-mixer.name=Blast Mixer +block.solidifer.name=Solidifer +block.solar-panel.name=Solar Panel +block.solar-panel-large.name=Large Solar Panel +block.oil-extractor.name=Oil Extractor +block.javelin-ship-factory.name=Javelin Ship factory +block.drone-factory.name=Drone Factory +block.fabricator-factory.name=Fabricator Factory +block.repair-point.name=Repair Point +block.resupply-point.name=Resupply Point +block.pulse-conduit.name=Pulse Conduit +block.phase-conduit.name=Phase Conduit +block.liquid-router.name=Liquid Router +block.liquid-tank.name=Liquid Tank +block.liquid-junction.name=Liquid Junction +block.bridge-conduit.name=Bridge Conduit +block.rotary-pump.name=Rotary Pump +block.nuclear-reactor.name=Nuclear Reactor diff --git a/core/assets/bundles/bundle_es.properties b/core/assets/bundles/bundle_es.properties index af95e6b530..33cd83d919 100644 --- a/core/assets/bundles/bundle_es.properties +++ b/core/assets/bundles/bundle_es.properties @@ -1,552 +1,515 @@ -text.about = Creado por [ROYAL]Anuken [] - [SKY] anukendev@gmail.com [] Originalmente una entrada en el [naranja] GDL [] Metal Monstrosity Jam. Créditos: - SFX hecho con [AMARILLO] bfxr [] - Música hecha por [VERDE] RoccoW [] / encontrado en [lime] FreeMusicArchive.org [] Agradecimientos especiales a: - [coral] MitchellFJN []: extensa prueba de juego y comentarios - [cielo] Luxray5474 []: trabajo wiki, contribuciones de código - [lime] Epowerj []: sistema de compilación de código, icono - Todos los probadores beta en itch.io y Google Play\n -text.credits = Créditos -text.discord = ¡Únete al Discord de Mindustry! -text.changes = [SCARLET] ¡Atención! [] Algunas mecánicas importantes del juego han sido cambiadas. - [acento] Los teletransportadores [] ahora usan energía. - [acento]Los talleres de fundición[] y [acento]los crisoles [] ahora tienen una capacidad máxima de artículos. - [acento] Los crisoles[] ahora requieren carbón como combustible. -text.link.discord.description = La sala oficial del discord de Mindustry -text.link.github.description = Código fuente del juego -text.link.dev-builds.description = Estados en desarrollo inestables -text.link.trello.description = Tablero trello oficial para las características planificadas -text.link.itch.io.description = itch.io és la página con descargas para PC y la versión web -text.link.google-play.description = Listado en la tienda de Google Play -text.link.wiki.description = Wiki oficial de Mindustry -text.linkfail = ¡Error al abrir el enlace!\nLa URL ha sido copiada a su portapapeles -text.editor.web = ¡La versión web no es compatible con el editor!\nDescargue el juego para usarlo. -text.multiplayer.web = ¡Esta versión del juego no admite multijugador!\nPara jugar al modo multijugador desde su navegador, use el enlace \"versión de varios jugadores\" en la página itch.io. -text.gameover = El núcleo fue destruido. -text.highscore = [YELLOW]¡Nueva mejor puntuación! -text.lasted = Duró hasta la ronda -text.level.highscore = Puntuación màs alta: [accent] -text.level.delete.title = Confirmar Eliminación -text.level.delete = ¿Seguro que quieres eliminar el mapa \"[ORANGE] \" {0}? -text.level.select = Selección de nivel -text.level.mode = Modo de juego: -text.savegame = Guardar Partida -text.loadgame = Cargar Partida -text.joingame = Unirse a una Partida -text.newgame = Nueva Partida -text.quit = Salir -text.about.button = Acerca de -text.name = Nombre -text.public = Público -text.players = {0} Jugadores en línea -text.server.player.host = {0} ANFITRIÓN -text.players.single = {0} jugador en línea -text.server.mismatch = Error de paquete: posible desajuste de la versión cliente / servidor.\n¡Asegúrate de que tú y el anfitrión tengáis la última versión de Mindustry! -text.server.closing = [accent] Cerrando servidor ... -text.server.kicked.kick = ¡Has sido expulsado del servidor! -text.server.kicked.invalidPassword = ¡Contraseña inválida! -text.server.kicked.clientOutdated = Cliente desactualizado ¡Actualiza tu juego! -text.server.kicked.serverOutdated = Servidor desactualizado ¡Pidele actualizar al anfitrión! -text.server.kicked.banned = Tu entrada está prohibida en este servidor. -text.server.kicked.recentKick = Has sido echado recientemente.\nEspera antes de conectarte de nuevo. -text.server.connected = se ha unido. -text.server.disconnected = se ha desconectado -text.nohost = ¡No se puede alojar el servidor en un mapa personalizado! -text.host.info = El botón [acento] host [] aloja un servidor en los puertos [escarlata] 6567 [] y [escarlata] 6568. [] Cualquiera en el mismo [LIGHT_GRAY] wifi o red local [] debería poder ver su servidor en su servidor lista. Si desea que las personas puedan conectarse desde cualquier lugar mediante IP, se requiere [acento] reenvío de puerto []. [LIGHT_GRAY] Nota: Si alguien tiene problemas para conectarse a su juego LAN, asegúrese de haber permitido a Mindustry el acceso a su red local en la configuración de su firewall. -text.join.info = Aquí puede ingresar un servidor [acento] IP [] para conectarse, o descubrir servidores de [acento] red local [] para conectarse. Tanto el modo multijugador LAN como WAN son compatibles. [LIGHT_GRAY] Nota: no hay una lista de servidores global automática; si desea conectarse con alguien por IP, deberá solicitar al host su IP. -text.hostserver = Hostear servidor -text.host = Hostear -text.hosting = [acento] Abriendo servidor ... -text.hosts.refresh = Refrescar -text.hosts.discovering = Descubriendo juegos en LAN -text.server.refreshing = Servidor refrescante -text.hosts.none = [lightgray] ¡No se encontraron juegos LAN! -text.host.invalid = [escarlata] No se puede conectar al host. -text.server.friendlyfire = Fuego amigo -text.trace = Rastro del jugador -text.trace.playername = Nombre del jugador: [acento] {0} -text.trace.ip = IP: [acento] {0} -text.trace.id = ID único: [acento] {0} -text.trace.android = Cliente de Android: [acento] {0} -text.trace.modclient = Cliente personalizado: [acento] {0} -text.trace.totalblocksbroken = Total de bloques rotos: [acento] {0} -text.trace.structureblocksbroken = Bloques de estructura rotos: [acento] {0} -text.trace.lastblockbroken = Último bloque roto: [acento] {0} -text.trace.totalblocksplaced = Total de bloques colocados: [acento] {0} -text.trace.lastblockplaced = Último bloque colocado: [acento] {0} -text.invalidid = ID de cliente no válido Presente un informe del error. -text.server.bans = Baneos -text.server.bans.none = ¡No se encontraron jugadores baneados! -text.server.admins = Admins -text.server.admins.none = ¡No se encontraron administradores! -text.server.add = Agregar servidor -text.server.delete = ¿Seguro que quieres eliminar este servidor? -text.server.hostname = Anfitrión: {0} -text.server.edit = Editar servidor -text.server.outdated = [crimson] ¡Servidor obsoleto! [] -text.server.outdated.client = [carmesí] Cliente desactualizado! [] -text.server.version = [lightgray] Versión: {0} -text.server.custombuild = [amarillo] Creación personalizada -text.confirmban = ¿Estás seguro de que quieres prohibir este jugador? -text.confirmunban = ¿Estás seguro de que quieres desbloquear a este jugador? -text.confirmadmin = ¿Seguro que quieres que este jugador sea un administrador? -text.confirmunadmin = ¿Seguro que quieres eliminar el estado de administrador de este reproductor? -text.joingame.byip = Unirse por IP ... -text.joingame.title = Unirse a una partida -text.joingame.ip = IP: -text.disconnect = Desconectado. -text.disconnect.data = ¡Fallo al cargar datos mundiales! -text.connecting = [accent] Conectando ... -text.connecting.data = [accent] Cargando información del mapa... -text.connectfail = [crimson] Fallo al conectar al servidor: [orange] -text.server.port = Puerto: -text.server.addressinuse = ¡Dirección ya en uso! -text.server.invalidport = ¡Número de puerto inválido! -text.server.error = [crimson] Error en la creación del servidor: [orange] -text.tutorial.back = < Anterior -text.tutorial.next = Siguiente > -text.save.new = Nuevo Guardado -text.save.overwrite = ¿Seguro que quieres sobrescribir este juego guardado? -text.overwrite = Sobreescribir -text.save.none = ¡No hay juegos guardados! -text.saveload = [accent] Guardando... -text.savefail = ¡Error al guardar el juego! -text.save.delete.confirm = ¿Estás seguro de que deseas eliminar este guardado? -text.save.delete = Borrar -text.save.export = Exportar guardado -text.save.import.invalid = [orange] ¡Este guardado es inválido! -text.save.import.fail = [crimson] Fallo al importar guardado: [orange] {0} -text.save.export.fail = [crimson] Fallo al exportar guardado: [orange] {0} -text.save.import = Importar Guardado -text.save.newslot = Nombre del guardado: -text.save.rename = Renombrar -text.save.rename.text = Nuevo nombre -text.selectslot = Seleccionar una guardado -text.slot = [accent] Casilla {0} -text.save.corrupted = [orange] ¡Arhivo de guardado corrupto o inválido! -text.empty = -text.on = Encendido -text.off = Apagado -text.save.autosave = Guardado automático: {0} -text.save.map = Mapa: {0} -text.save.wave = Horda: {0} -text.save.difficulty = Dificultad: {0} -text.save.date = Guardado por última vez: {0} -text.confirm = Confirmar -text.delete = Eliiminar -text.ok = OK -text.open = Abrir -text.cancel = Cancelar -text.openlink = Abrir enlace -text.copylink = Copiar link -text.back = Atrás -text.quit.confirm = ¿Seguro que quieres salir? -text.changelog.title = Changelog -text.changelog.loading = Obteniendo changelog ... -text.changelog.error.android = [naranja] Tenga en cuenta que el registro de cambios no funciona en Android 4.4 y versiones posteriores. Esto se debe a un error interno de Android. -text.changelog.error = [escarlata] ¡Error al obtener el registro de cambios! Comprueba tu conexión a Internet. -text.changelog.current = [amarillo] [[Versión actual] -text.changelog.latest = [naranja] [[Última versión] -text.loading = [accent] Cargando... -text.wave = [orange] Horda {0} -text.wave.waiting = Horda en {0} -text.waiting = Esperando... -text.enemies = {0} Enemigos -text.enemies.single = {0} Enemigo -text.loadimage = Cargar imagen -text.saveimage = Guardar imagen -text.oregen = Generación de mineral {0} -text.editor.badsize = [orange]¡Dimensiones de imagen inválidas![]\nDimensiones de mapa válidas: {0} -text.editor.errorimageload = Error al cargar el archivo de imagen: [orange] {0} -text.editor.errorimagesave = Error al guardar el archivo de imagen: [orange] {0} -text.editor.generate = Generar -text.editor.resize = Cambiar tamaño -text.editor.loadmap = Cargar mapa -text.editor.savemap = Guardar mapa -text.editor.loadimage = Cargar imagen -text.editor.saveimage = Guardar imagen -text.editor.unsaved = [scarlet] ¡Tienes cambios sin guardar! [] ¿Estás seguro de que quieres salir? -text.editor.brushsize = Tamaño del pincel: {0} -text.editor.noplayerspawn = ¡Este mapa no tiene punto de aparición del jugador! -text.editor.manyplayerspawns = ¡Los mapas no pueden tener más de un punto de spawn de jugador! -text.editor.manyenemyspawns = {0 }¡No puede tener más de puntos de aparición enemiga! -text.editor.resizemap = Cambiar el tamaño del mapa -text.editor.resizebig = [escarlata] ¡Advertencia! [] Los mapas de más de 256 unidades pueden ser inestables. -text.editor.mapname = Nombre del mapa -text.editor.overwrite = [acento] ¡Advertencia!\nEsto sobrescribe un mapa existente. -text.editor.failoverwrite = [carmesí] ¡No se puede sobrescribir el mapa por defecto! -text.editor.selectmap = Seleccione un mapa para cargar: -text.width = Ancho: -text.height = Altura: -text.randomize = Aleatorizar -text.apply = Aplicar -text.update = Refrescar -text.menu = Menú -text.play = Jugar -text.load = Cargar -text.save = Salvar -text.language.restart = Por favor, reinicie su juego para que la configuración de idioma surta efecto. -text.settings.language = Idioma -text.settings = Ajustes -text.tutorial = Tutorial -text.editor = Editor -text.mapeditor = Editor de Mapas -text.donate = Donar -text.settings.reset = Restablecer los valores predeterminados -text.settings.controls = Controles -text.settings.game = Juego -text.settings.sound = Sonido -text.settings.graphics = Gráficos -text.upgrades = Mejoras -text.purchased = [LIME] Creado! -text.weapons = Armas -text.paused = Pausado -text.respawn = Reapareciendo en -text.info.title = [acento] Información -text.error.title = [carmesí] Se ha producido un error -text.error.crashmessage = [SCARLET] Se ha producido un error inesperado, que habría causado un bloqueo. [] Informe las circunstancias exactas bajo las cuales se produjo este error al desarrollador: [ORANGE] anukendev@gmail.com [] -text.error.crashtitle = Ha ocurrido un error -text.mode.break = Modo de eliminación: -text.mode.place = Modo de colocación: -placemode.hold.name = Línea -placemode.areadelete.name = Àrea -placemode.touchdelete.name = Toque -placemode.holddelete.name = Mantener -placemode.none.name = Ninguno -placemode.touch.name = Toque -placemode.cursor.name = Cursor -text.blocks.extrainfo = [acento] información adicional del bloque: -text.blocks.blockinfo = Información de bloque -text.blocks.powercapacity = Capacidad de energía -text.blocks.powershot = Energía/disparo -text.blocks.powersecond = energía drenada/segundo -text.blocks.powerdraindamage = Energía drenada/daño -text.blocks.shieldradius = Radio del escudo -text.blocks.itemspeedsecond = Velocidad del objeto/segundo -text.blocks.range = Rango -text.blocks.size = Tamaño -text.blocks.powerliquid = Poder/Líquido -text.blocks.maxliquidsecond = máximo líquido/segundo -text.blocks.liquidcapacity = Capacidad de liquido -text.blocks.liquidsecond = Líquido/segundo -text.blocks.damageshot = Daño/ disparo -text.blocks.ammocapacity = Capacidad de munición -text.blocks.ammo = Munición: -text.blocks.ammoitem = Munición/objeto -text.blocks.maxitemssecond = Objetos máximos/segundo -text.blocks.powerrange = Rango de energía -text.blocks.lasertilerange = Rango de poder en casilllas -text.blocks.capacity = Capacidad -text.blocks.itemcapacity = Capacidad de items -text.blocks.maxpowergenerationsecond = Máxima generación de energía/segundo -text.blocks.powergenerationsecond = Generación de energía/segundo -text.blocks.generationsecondsitem = Segundos de generación/Item -text.blocks.input = Ingreso -text.blocks.inputliquid = Entrada de líquidos -text.blocks.inputitem = Entrada de ítems -text.blocks.output = Salida -text.blocks.secondsitem = Segundos/Ítem -text.blocks.maxpowertransfersecond = Máxima transferencia de poder/segundo -text.blocks.explosive = ¡Altamente explosivo! -text.blocks.repairssecond = Reparado / segundo -text.blocks.health = Vida -text.blocks.inaccuracy = Inexactitud -text.blocks.shots = Disparos -text.blocks.shotssecond = Disparos / segundo -text.blocks.fuel = Combustible -text.blocks.fuelduration = Duración del combustible -text.blocks.maxoutputsecond = Máxima salida/segundo -text.blocks.inputcapacity = Capacidad de entrada -text.blocks.outputcapacity = Capacidad de salida -text.blocks.poweritem = Poder/Ítem -text.placemode = Modo colocar -text.breakmode = Modo romper -text.health = Salud -setting.difficulty.easy = Fácil -setting.difficulty.normal = Mormal -setting.difficulty.hard = Difícil -setting.difficulty.insane = Insano -setting.difficulty.purge = Purga -setting.difficulty.name = Dificultad: -setting.screenshake.name = Shake de pantalla -setting.smoothcam.name = Cámara lisa -setting.indicators.name = Indicador del enemigo -setting.effects.name = Mostrar efectos -setting.sensitivity.name = Sensibilidad del controlador -setting.saveinterval.name = Intervalo de autoguardado -setting.seconds = Segundos -setting.fullscreen.name = Pantalla completa -setting.multithread.name = Multithreading -setting.fps.name = Mostrar fps -setting.vsync.name = VSync -setting.lasers.name = Mostrar láseres de poder -setting.previewopacity.name = Colocando Vista Previa Opacidad -setting.healthbars.name = Mostrar barras de vida de enemigos y jugadores -setting.pixelate.name = Pixelear pantalla -setting.musicvol.name = Volumen de la música -setting.mutemusic.name = Apagar música -setting.sfxvol.name = Volumen de los efectos de sonido -setting.mutesound.name = Apagar sonidos -map.maze.name = Laberinto -map.fortress.name = Fortaleza -map.sinkhole.name = Sumidero -map.caves.name = Cuevas -map.volcano.name = Volcán -map.caldera.name = Caldera -map.scorch.name = Desierto volcánico -map.desert.name = Desierto -map.island.name = Isla -map.grassland.name = Pastizal -map.tundra.name = Tundra -map.spiral.name = Espiral -map.tutorial.name = Tutorial -tutorial.intro.text = [amarillo] Bienvenido al tutorial. [] Para comenzar, presione 'siguiente'. -tutorial.moveDesktop.text = Para moverse, use las teclas [naranja] [[WASD] []. Mantenga [naranja] shift [] para impulsar. Mantenga presionada la tecla [naranja] CTRL [] mientras usa la rueda de desplazamiento [naranja] [] para acercar o alejar la imagen. -tutorial.shoot.text = Usa el mouse para apuntar, mantén presionado [naranja] el botón izquierdo del mouse [] para disparar. Intenta practicar en el objetivo [amarillo] []. -tutorial.moveAndroid.text = Para recorrer la vista, arrastre un dedo por la pantalla. Pellizque y arrastre para acercar o alejar. -tutorial.placeSelect.text = Intente seleccionar un transportador [amarillo] [] desde el menú del bloque en la parte inferior derecha. -tutorial.placeConveyorDesktop.text = Utilice [naranja] [[rueda de desplazamiento] [] para girar la cinta transportadora hacia [naranja] hacia adelante [], luego colóquela en la ubicación [amarilla] marcada [] usando [naranja] [[botón izquierdo del mouse] []. -tutorial.placeConveyorAndroid.text = Utilice [naranja] [[girar el botón] [] para girar el transportador hacia [naranja] hacia delante [], arrástrelo con un dedo, luego colóquelo en la ubicación [amarilla] marcada [] usando [naranja] [[marca de verificación][]. -tutorial.placeConveyorAndroidInfo.text = Alternativamente, puede presionar el icono de la cruz en la parte inferior izquierda para cambiar a [naranja] [[modo táctil] [] y colocar bloques tocando en la pantalla. En modo táctil, los bloques se pueden girar con la flecha en la parte inferior izquierda. Presione [amarillo] al lado [] para probarlo. -tutorial.placeDrill.text = Ahora, seleccione y coloque un taladro de piedra [amarillo] [] en la ubicación marcada. -tutorial.blockInfo.text = Si desea obtener más información sobre un bloque, puede tocar el signo de interrogación [naranja] [] en la parte superior derecha para leer su descripción. -tutorial.deselectDesktop.text = Puede deseleccionar un bloque usando el [naranja] [[botón derecho del mouse] []. -tutorial.deselectAndroid.text = Puede deseleccionar un bloque presionando el botón [naranja] X []. -tutorial.drillPlaced.text = El taladro ahora producirá piedra [amarilla], [] la enviará al transportador y luego la moverá al núcleo [amarillo] []. -tutorial.drillInfo.text = Diferentes minerales necesitan diferentes ejercicios. La piedra requiere taladros de piedra, el hierro requiere taladros de hierro, etc. -tutorial.drillPlaced2.text = Mover elementos al núcleo los coloca en su inventario de elementos [amarillo] [], en la esquina superior izquierda. Colocar bloques utiliza elementos de tu inventario. -tutorial.moreDrills.text = Puede vincular muchos taladros y cintas transportadoras juntas, de esa manera. -tutorial.deleteBlock.text = Puede eliminar bloques haciendo clic en el botón [naranja] del botón derecho del mouse [] en el bloque que desea eliminar. Intente eliminar este transportador. -tutorial.deleteBlockAndroid.text = Puede eliminar bloques mediante [naranja] seleccionando la cruz [] en el menú [naranja] del modo de interrupción [] en la parte inferior izquierda y tocando un bloque. Intente eliminar este transportador. -tutorial.placeTurret.text = Ahora, seleccione y coloque una torreta [amarilla] [] en la ubicación marcada [amarilla] []. -tutorial.placedTurretAmmo.text = Esta torre ahora aceptará [amarillo] munición [] del transportador. Puedes ver la cantidad de munición que tiene al pasar el mouse sobre ella y verificar la barra verde [verde] []. -tutorial.turretExplanation.text = Las torretas dispararán automáticamente al enemigo más cercano al alcance, siempre que tengan suficiente munición. -tutorial.waves.text = Cada [amarillo] 60 [] segundos, una ola de [coral] enemigos [] aparecerán en lugares específicos e intentarán destruir el núcleo. -tutorial.coreDestruction.text = Tu objetivo es [amarillo] defender el núcleo []. Si se destruye el núcleo, tú [coral] pierdes el juego []. -tutorial.pausingDesktop.text = Si alguna vez necesita tomar un descanso, presione el botón de pausa [naranja] [] en el espacio superior izquierdo o [naranja] [] para detener el juego. Aún puede seleccionar y colocar bloques mientras está en pausa, pero no puede mover o disparar. -tutorial.pausingAndroid.text = Si alguna vez necesita tomarse un descanso, presione el botón de pausa [naranja] [] en la parte superior izquierda para pausar el juego. Todavía puede romper y colocar bloques mientras está en pausa. -tutorial.purchaseWeapons.text = Puedes comprar nuevas armas [amarillas] [] para tu mech abriendo el menú de actualización en la esquina inferior izquierda. -tutorial.switchWeapons.text = Cambie las armas haciendo clic en su icono en la esquina inferior izquierda o usando números [naranja] [[1-9] []. -tutorial.spawnWave.text = Aquí viene una ola ahora. Destruyelos. -tutorial.pumpDesc.text = En olas posteriores, es posible que deba usar bombas [amarillas] [] para distribuir líquidos para generadores o extractores. -tutorial.pumpPlace.text = Las bombas funcionan de manera similar a los taladros, excepto que producen líquidos en lugar de artículos. Intente colocar una bomba en el aceite designado [amarillo] []. -tutorial.conduitUse.text = Ahora coloque un conducto [naranja] [] que se aleje de la bomba. -tutorial.conduitUse2.text = Y algunos más ... -tutorial.conduitUse3.text = Y algunos más ... -tutorial.generator.text = Ahora, coloque un bloque [naranja] de generador de combustión [] al final del conducto. -tutorial.generatorExplain.text = Este generador ahora creará energía [amarilla] [] del aceite. -tutorial.lasers.text = La potencia se distribuye usando láseres de potencia [amarillos] []. Gira y coloca uno aquí. -tutorial.laserExplain.text = El generador ahora moverá energía al bloque láser. Un haz [amarillo] opaco [] significa que está transmitiendo corriente, y un haz [amarillo] transparente [] significa que no lo está. -tutorial.laserMore.text = Puedes verificar cuánta potencia tiene un bloque al pasar el mouse sobre él y verificar la barra amarilla [amarilla] [] en la parte superior. -tutorial.healingTurret.text = Este láser se puede usar para alimentar una torreta de reparación de [cal] []. Coloca uno aquí. -tutorial.healingTurretExplain.text = Mientras tenga energía, esta torreta [cal] reparará los bloques cercanos. [] ¡Al jugar, asegúrate de obtener uno en tu base lo más rápido posible! -tutorial.smeltery.text = Muchos bloques requieren [naranja] acero [] para fabricar, lo que requiere una fundición [naranja] [] para fabricar. Coloca uno aquí. -tutorial.smelterySetup.text = Esta fundición producirá acero [naranja] [] a partir de la plancha de entrada, utilizando carbón como combustible. -tutorial.tunnelExplain.text = También tenga en cuenta que los artículos pasan por un bloque de túnel [naranja] [] y salen del otro lado, pasando por el bloque de piedra. Tenga en cuenta que los túneles solo pueden atravesar hasta 2 bloques. -tutorial.end.text = ¡Y eso concluye el tutorial! ¡Buena suerte! -text.keybind.title = Vuelva a conectar las llaves -keybind.move_x.name = mover_x -keybind.move_y.name = mover_y -keybind.select.name = Elija -keybind.break.name = Romper -keybind.shoot.name = ¡Dispara! -keybind.zoom_hold.name = Enfoque_Mantener -keybind.zoom.name = Enfoquè -keybind.block_info.name = Bloque_informacion -keybind.menu.name = Menú -keybind.pause.name = Pausa -keybind.dash.name = Deslizar -keybind.chat.name = Chat -keybind.player_list.name = Jugadores_lista -keybind.console.name = Console -keybind.rotate_alt.name = Rotacion_alt -keybind.rotate.name = Girar -keybind.weapon_1.name = Arma_1 -keybind.weapon_2.name = Arma_2 -keybind.weapon_3.name = Arma_3\n -keybind.weapon_4.name = Arma_4 -keybind.weapon_5.name = Arma_5 -keybind.weapon_6.name = Arma6 -mode.text.help.title = Descripción de modos -mode.waves.name = Hordas -mode.waves.description = El modo normal. Recursos limitados y las hordas vendrán automáticamente -mode.sandbox.name = Sandbox -mode.sandbox.description = Recursos infinitos y sin temporizador para las olas. -mode.freebuild.name = Construcción libre -mode.freebuild.description = Recursos limitados y sin tiempo definido para las hordas -upgrade.standard.name = Estandar -upgrade.standard.description = El mech estándar. -upgrade.blaster.name = Blaster -upgrade.blaster.description = Dispara una bala lenta y débil. -upgrade.triblaster.name = Triblaster -upgrade.triblaster.description = Dispara 3 balas en una extensión. -upgrade.clustergun.name = Cleaster Gun -upgrade.clustergun.description = Dispara una propagación inexacta de granadas explosivas. -upgrade.beam.name = Cañòn Beam -upgrade.beam.description = Dispara un rayo láser perforante de largo alcance. -upgrade.vulcan.name = Volcán -upgrade.vulcan.description = Dispara una ráfaga de balas rapidas -upgrade.shockgun.name = Pistola Shock\n -upgrade.shockgun.description = Dispara una ráfaga devastadora de metralla cargada. -item.stone.name = Piedra -item.iron.name = Hierro -item.coal.name = Carbón -item.steel.name = Acero -item.titanium.name = Titanio -item.dirium.name = Dirio -item.uranium.name = Uranio -item.sand.name = Arena -liquid.water.name = Agua -liquid.plasma.name = Plasma -liquid.lava.name = Lava -liquid.oil.name = Aceite -block.weaponfactory.name = Fábrica de armas -block.weaponfactory.fulldescription = Se usa para crear armas para el jugador mech. Haga clic para usar. Automáticamente toma recursos del núcleo. -block.air.name = Aire -block.blockpart.name = Bloque -block.deepwater.name = Aguas profundas -block.water.name = Agua -block.lava.name = Lava -block.oil.name = Aceite -block.stone.name = Piedra -block.blackstone.name = Piedra Negra -block.iron.name = Hierro -block.coal.name = Carbón -block.titanium.name = Titanio -block.uranium.name = Uranio -block.dirt.name = Sucio -block.sand.name = Arena -block.ice.name = Hielo -block.snow.name = Nieve -block.grass.name = Césped -block.sandblock.name = Bloque de arena -block.snowblock.name = Bloque de nieve -block.stoneblock.name = Bloque de piedra -block.blackstoneblock.name = Bloque de piedra negra -block.grassblock.name = Bloque de césped -block.mossblock.name = Bloque de musgo -block.shrub.name = Arbusto -block.rock.name = Piedra -block.icerock.name = Piedra congelada -block.blackrock.name = Roca Negra -block.dirtblock.name = Bloque de suciedad -block.stonewall.name = Pared de piedra -block.stonewall.fulldescription = Un bloque defensivo barato. Útil para proteger el núcleo y las torrecillas en las primeras olas. -block.ironwall.name = Muro de hierro -block.ironwall.fulldescription = Un bloque defensivo básico. Proporciona protección de los enemigos. -block.steelwall.name = Muro de acero -block.steelwall.fulldescription = Un bloque defensivo estándar. protección adecuada de los enemigos. -block.titaniumwall.name = Muro de titanio -block.titaniumwall.fulldescription = Un fuerte bloqueo defensivo. Proporciona protección de los enemigos. -block.duriumwall.name = Muro de Dirio -block.duriumwall.fulldescription = Un bloque defensivo muy fuerte. Proporciona protección de los enemigos. -block.compositewall.name = Muro compuesto -block.steelwall-large.name = Pared de acero grande -block.steelwall-large.fulldescription = Un bloque defensivo estándar. Se extiende por múltiples mosaicos. -block.titaniumwall-large.name = Gran pared de titanio -block.titaniumwall-large.fulldescription = Un fuerte bloqueo defensivo. Se extiende por múltiples mosaicos. -block.duriumwall-large.name = Gran pared de dirio -block.duriumwall-large.fulldescription = Un bloque defensivo muy fuerte. Se extiende por múltiples mosaicos. -block.titaniumshieldwall.name = Muro blindado -block.titaniumshieldwall.fulldescription = Un fuerte bloque defensivo, con un escudo extra incorporado. Requiere poder Usa energía para absorber las balas enemigas. Se recomienda utilizar potenciadores de potencia para proporcionar energía a este bloque. -block.repairturret.name = Torreta de reparación -block.repairturret.fulldescription = Repara los bloques dañados cercanos en el rango a una velocidad lenta. Utiliza pequeñas cantidades de energía. -block.megarepairturret.name = Torreta de reparación II -block.megarepairturret.fulldescription = Repara los bloques dañados cercanos en el rango a una tasa decente. Utiliza el poder -block.shieldgenerator.name = Generador de escudo -block.shieldgenerator.fulldescription = Un bloque defensivo avanzado. Protege todos los bloques en un radio del ataque. Utiliza potencia a un ritmo lento cuando está inactivo, pero consume energía rápidamente al contacto con balas. -block.door.name = Puerta -block.door.fulldescription = Un bloque que se puede abrir y cerrar tocando. -block.door-large.name = Puerta grande -block.door-large.fulldescription = Un bloque que se puede abrir y cerrar tocando. -block.conduit.name = Conducto -block.conduit.fulldescription = Bloque de transporte de líquido básico. Funciona como un transportador, pero con líquidos. Se usa mejor con bombas u otros conductos. Se puede usar como puente sobre líquidos para enemigos y jugadores. -block.pulseconduit.name = Conducto de pulso -block.pulseconduit.fulldescription = Bloque de transporte de líquidos avanzado. Transporta líquidos más rápido y almacena más que los conductos estándar. -block.liquidrouter.name = Enrutador líquido -block.liquidrouter.fulldescription = Funciona de manera similar a un enrutador. Acepta entrada de líquido desde un lado y lo envía a los otros lados. Útil para dividir el líquido de un solo conducto en otros múltiples conductos. -block.conveyor.name = Transportador -block.conveyor.fulldescription = Bloque de transporte de elementos básicos. Mueve los artículos hacia adelante y los deposita automáticamente en torretas o crafters. Giratorio. Se puede usar como puente sobre líquidos para enemigos y jugadores. -block.steelconveyor.name = Transportador de acero -block.steelconveyor.fulldescription = Bloque de transporte de elementos avanzados. Mueve los artículos más rápido que los transportadores estándar. -block.poweredconveyor.name = Transportador de pulso -block.poweredconveyor.fulldescription = El último bloque de transporte de elementos. Mueve los artículos más rápido que los transportadores de acero. -block.router.name = Enrutador -block.router.fulldescription = Acepta elementos de una dirección y los envía a otras 3 direcciones. También puede almacenar una cierta cantidad de artículos. Útil para dividir los materiales de un taladro en múltiples torretas. -block.junction.name = Union -block.junction.fulldescription = Actúa como un puente para cruzar dos cintas transportadoras. Útil en situaciones con dos transportadores diferentes que llevan diferentes materiales a diferentes ubicaciones. -block.conveyortunnel.name = Túnel transportador -block.conveyortunnel.fulldescription = Transporta el artículo debajo de bloques. Para usarlo, coloque un túnel que conduce al bloque por el que se colocará el túnel, y otro del otro lado. Asegúrate de que ambos túneles estén orientados en direcciones opuestas, que se dirigen hacia los bloques a los que están ingresando o enviando. -block.liquidjunction.name = Unión líquida -block.liquidjunction.fulldescription = Actúa como un puente para dos conductos que cruzan. Útil en situaciones con dos conductos diferentes que llevan diferentes líquidos a diferentes lugares. -block.liquiditemjunction.name = Unión líquidos-elementos -block.liquiditemjunction.fulldescription = Actúa como un puente para cruzar conductos y transportadores. -block.powerbooster.name = Ampliación de potencia -block.powerbooster.fulldescription = Distribuye energía a todos los bloques dentro de su radio. -block.powerlaser.name = Láser de potencia -block.powerlaser.fulldescription = Crea un láser que transmite potencia al bloque que está delante de él. No genera ningún poder en sí mismo. Se usa mejor con generadores u otros láseres. -block.powerlaserrouter.name = Enrutador láser -block.powerlaserrouter.fulldescription = Láser que distribuye la potencia en tres direcciones a la vez. Útil en situaciones donde se requiere para alimentar múltiples bloques de un generador. -block.powerlasercorner.name = Laser esquinero -block.powerlasercorner.fulldescription = Láser que distribuye la potencia en dos direcciones a la vez. Útil en situaciones donde se requiere alimentar múltiples bloques desde un generador, y un enrutador es impreciso. -block.teleporter.name = Teletransportador -block.teleporter.fulldescription = Bloque de transporte de elementos avanzados. Los teleportadores ingresan elementos a otros teletransportadores del mismo color. No hace nada si no existen teletransportadores del mismo color. Si existen múltiples teleportadores del mismo color, se selecciona uno aleatorio. Utiliza el poder Toca para cambiar el color. -block.sorter.name = Clasificador -block.sorter.fulldescription = Ordena el artículo por tipo de material. El material a aceptar se indica por el color en el bloque. Todos los elementos que coinciden con el material de clasificación se envían hacia adelante, todo lo demás se envía a la izquierda y a la derecha. -block.core.name = Nùcleo -block.pump.name = Bomba -block.pump.fulldescription = Bombea líquidos de un bloque fuente, generalmente agua, lava o aceite. Emite líquido en los conductos cercanos. -block.fluxpump.name = Bomba de flujo -block.fluxpump.fulldescription = Una versión avanzada de la bomba. Almacena más líquido y bombea líquido más rápido. -block.smelter.name = horno de fundición -block.smelter.fulldescription = El bloque de elaboración esencial. Cuando se ingresa 1 hierro y 1 carbón como combustible, se emite un acero. Se aconseja ingresar hierro y carbón en diferentes bandas para evitar obstrucciones. -block.crucible.name = Crisol -block.crucible.fulldescription = Un bloque de elaboración avanzada. Cuando se ingresa 1 titanio, 1 acero y 1 carbón como combustible, se emite un dirium. Se aconseja ingresar carbón, acero y titanio en diferentes bandas para evitar obstrucciones. -block.coalpurifier.name = Extractor de carbón -block.coalpurifier.fulldescription = Un bloque extractor básico. Salidas de carbón cuando se suministra con grandes cantidades de agua y piedra. -block.titaniumpurifier.name = extractor de titanio -block.titaniumpurifier.fulldescription = Un bloque extractor estándar. Salidas de titanio cuando se suministra con grandes cantidades de agua y hierro. -block.oilrefinery.name = Refinería de petróleo -block.oilrefinery.fulldescription = Refina grandes cantidades de aceite en artículos de carbón. Útil para alimentar torretas a base de carbón cuando las vetas de carbón son escasas. -block.stoneformer.name = Piedra antigua -block.stoneformer.fulldescription = Se vende lava líquida en piedra. Útil para producir cantidades masivas de piedra para purificadores de carbón. -block.lavasmelter.name = Fundición de lava -block.lavasmelter.fulldescription = Utiliza lava para convertir el hierro en acero. Una alternativa a las fundiciones. Útil en situaciones donde el carbón es escaso. -block.stonedrill.name = Perforadora de piedra -block.stonedrill.fulldescription = El taladro esencial. Cuando se coloca en baldosas de piedra, las impresiones de piedra a un ritmo lento de forma indefinida. -block.irondrill.name = Perforadora de hierro -block.irondrill.fulldescription = Un ejercicio básico. Cuando se coloca sobre baldosas de mineral de hierro, emite hierro a un ritmo lento indefinidamente. -block.coaldrill.name = Perforadora de carbón -block.coaldrill.fulldescription = Un ejercicio básico. Cuando se coloca en las baldosas de mineral de carbón, emite carbón a un ritmo lento indefinidamente. -block.uraniumdrill.name = Taladro de uranio -block.uraniumdrill.fulldescription = Un taladro avanzado. Cuando se coloca en placas de mineral de uranio, emite uranio a un ritmo lento de forma indefinida. -block.titaniumdrill.name = Taladro de titanio -block.titaniumdrill.fulldescription = Un taladro avanzado. Cuando se coloca en baldosas de mineral de titanio, emite titanio a un ritmo lento indefinidamente. -block.omnidrill.name = omnidrill -block.omnidrill.fulldescription = El último ejercicio Va a extraer cualquier mineral que se coloca a un ritmo rápido. -block.coalgenerator.name = Generador de carbón -block.coalgenerator.fulldescription = El generador esencial. Genera energía del carbón Emite energía como láser en sus 4 lados. -block.thermalgenerator.name = Generador térmico -block.thermalgenerator.fulldescription = Genera energía de la lava Emite energía como láser en sus 4 lados. -block.combustiongenerator.name = Generador de combustión -block.combustiongenerator.fulldescription = Genera energía del petróleo. Emite energía como láser en sus 4 lados. -block.rtgenerator.name = Generador de RTG -block.rtgenerator.fulldescription = Genera pequeñas cantidades de energía a partir de la desintegración radiactiva del uranio. Emite energía como láser en sus 4 lados. -block.nuclearreactor.name = Reactor nuclear -block.nuclearreactor.fulldescription = Una versión avanzada del generador RTG y el último generador de energía. Genera energía del uranio. Requiere enfriamiento constante de agua. Altamente volátil; explotará violentamente si se suministran cantidades insuficientes de refrigerante. -block.turret.name = Torre -block.turret.fulldescription = Una torrecilla básica y barata. Utiliza piedra para munición. Tiene un poco más de alcance que la torre doble. -block.doubleturret.name = Doble torreta -block.doubleturret.fulldescription = Una versión un poco más poderosa de la torreta. Utiliza piedra para munición. Hace mucho más daño, pero tiene un rango menor. Dispara dos balas. -block.machineturret.name = Torreta de gatling -block.machineturret.fulldescription = Una torreta versátil estándar. Utiliza hierro para munición. Tiene una velocidad de disparo rápida con un daño decente. -block.shotgunturret.name = Torreta divisoria -block.shotgunturret.fulldescription = Una torreta estándar. Utiliza hierro para munición. Dispara una extensión de 7 balas. Alcance más bajo, pero mayor producción de daños que la torreta de gatling. -block.flameturret.name = Torreta de fuego -block.flameturret.fulldescription = Torreta de corto alcance avanzada. Utiliza carbón para munición. Tiene un rango muy bajo, pero un daño muy alto. Bueno para cuartos cercanos. Recomendado para ser utilizado detrás de las paredes. -block.sniperturret.name = Torreta railgun -block.sniperturret.fulldescription = Torreta de largo alcance avanzada. Utiliza acero para munición. Daño muy alto, pero bajo índice de fuego. Es caro de usar, pero puede colocarse lejos de las líneas enemigas debido a su alcance. -block.mortarturret.name = Torreta antiaérea -block.mortarturret.fulldescription = Torreta avanzada de baja salpicadura de daños por salpicadura. Utiliza carbón para munición. Dispara un aluvión de balas que explotan en metralla. Útil para grandes multitudes de enemigos. -block.laserturret.name = Torreta láser -block.laserturret.fulldescription = Torreta de un solo objetivo avanzado. Utiliza el energia. Buena torre de medio alcance. Objetivo único. Nunca falla -block.waveturret.name = Torreta tesla -block.waveturret.fulldescription = Torreta multi-objetivo avanzada. Utiliza el poder Rango medio. Nunca falla. De Medio a bajo daño, pero puede golpear a varios enemigos simultáneamente con rayos en cadena. -block.plasmaturret.name = Torreta de plasma -block.plasmaturret.fulldescription = Versión altamente avanzada de la torreta de fuego. Utiliza carbón como munición. Daño muy alto, rango bajo a medio. -block.chainturret.name = Torreta de cadena -block.chainturret.fulldescription = La torreta de fuego rápido suprema. Usa uranio como munición. Dispara babosas grandes a una alta cadencia. Rango medio. Se extiende por múltiples bloques. Extremadamente duradero. -block.titancannon.name = Cañón titán -block.titancannon.fulldescription = La torreta de largo alcance suprema. Usa uranio como munición. Dispara grandes proyectiles con daño de área a una cadencia media. De largo alcance. Se extiende por múltiples bloques. Extremadamente duradero. -block.playerspawn.name = Punto de aparición del jugador -block.enemyspawn.name = Generador de enemigos +text.about=Creado por [ROYAL]Anuken [] - [SKY] anukendev@gmail.com [] Originalmente una entrada en el [naranja] GDL [] Metal Monstrosity Jam. Créditos: - SFX hecho con [AMARILLO] bfxr [] - Música hecha por [VERDE] RoccoW [] / encontrado en [lime] FreeMusicArchive.org [] Agradecimientos especiales a: - [coral] MitchellFJN []: extensa prueba de juego y comentarios - [cielo] Luxray5474 []: trabajo wiki, contribuciones de código - [lime] Epowerj []: sistema de compilación de código, icono - Todos los probadores beta en itch.io y Google Play\n +text.credits=Créditos +text.discord=¡Únete al Discord de Mindustry! +text.link.discord.description=La sala oficial del discord de Mindustry +text.link.github.description=Código fuente del juego +text.link.dev-builds.description=Estados en desarrollo inestables +text.link.trello.description=Tablero trello oficial para las características planificadas +text.link.itch.io.description=itch.io és la página con descargas para PC y la versión web +text.link.google-play.description=Listado en la tienda de Google Play +text.link.wiki.description=Wiki oficial de Mindustry +text.linkfail=¡Error al abrir el enlace!\nLa URL ha sido copiada a su portapapeles +text.editor.web=¡La versión web no es compatible con el editor!\nDescargue el juego para usarlo. +text.multiplayer.web=¡Esta versión del juego no admite multijugador!\nPara jugar al modo multijugador desde su navegador, use el enlace "versión de varios jugadores" en la página itch.io. +text.gameover=El núcleo fue destruido. +text.highscore=[YELLOW]¡Nueva mejor puntuación! +text.lasted=Duró hasta la ronda +text.level.highscore=Puntuación màs alta: [accent] +text.level.delete.title=Confirmar Eliminación +text.level.select=Selección de nivel +text.level.mode=Modo de juego: +text.savegame=Guardar Partida +text.loadgame=Cargar Partida +text.joingame=Unirse a una Partida +text.newgame=Nueva Partida +text.quit=Salir +text.about.button=Acerca de +text.name=Nombre +text.public=Público +text.players={0} Jugadores en línea +text.server.player.host={0} ANFITRIÓN +text.players.single={0} jugador en línea +text.server.mismatch=Error de paquete: posible desajuste de la versión cliente / servidor.\n¡Asegúrate de que tú y el anfitrión tengáis la última versión de Mindustry! +text.server.closing=[accent] Cerrando servidor ... +text.server.kicked.kick=¡Has sido expulsado del servidor! +text.server.kicked.invalidPassword=¡Contraseña inválida! +text.server.kicked.clientOutdated=Cliente desactualizado ¡Actualiza tu juego! +text.server.kicked.serverOutdated=Servidor desactualizado ¡Pidele actualizar al anfitrión! +text.server.kicked.banned=Tu entrada está prohibida en este servidor. +text.server.kicked.recentKick=Has sido echado recientemente.\nEspera antes de conectarte de nuevo. +text.server.connected=se ha unido. +text.server.disconnected=se ha desconectado +text.nohost=¡No se puede alojar el servidor en un mapa personalizado! +text.host.info=El botón [acento] host [] aloja un servidor en los puertos [escarlata] 6567 [] y [escarlata] 6568. [] Cualquiera en el mismo [LIGHT_GRAY] wifi o red local [] debería poder ver su servidor en su servidor lista. Si desea que las personas puedan conectarse desde cualquier lugar mediante IP, se requiere [acento] reenvío de puerto []. [LIGHT_GRAY] Nota: Si alguien tiene problemas para conectarse a su juego LAN, asegúrese de haber permitido a Mindustry el acceso a su red local en la configuración de su firewall. +text.join.info=Aquí puede ingresar un servidor [acento] IP [] para conectarse, o descubrir servidores de [acento] red local [] para conectarse. Tanto el modo multijugador LAN como WAN son compatibles. [LIGHT_GRAY] Nota: no hay una lista de servidores global automática; si desea conectarse con alguien por IP, deberá solicitar al host su IP. +text.hostserver=Hostear servidor +text.host=Hostear +text.hosting=[acento] Abriendo servidor ... +text.hosts.refresh=Refrescar +text.hosts.discovering=Descubriendo juegos en LAN +text.server.refreshing=Servidor refrescante +text.hosts.none=[lightgray] ¡No se encontraron juegos LAN! +text.host.invalid=[escarlata] No se puede conectar al host. +text.server.friendlyfire=Fuego amigo +text.trace=Rastro del jugador +text.trace.playername=Nombre del jugador: [acento] {0} +text.trace.ip=IP: [acento] {0} +text.trace.id=ID único: [acento] {0} +text.trace.android=Cliente de Android: [acento] {0} +text.trace.modclient=Cliente personalizado: [acento] {0} +text.trace.totalblocksbroken=Total de bloques rotos: [acento] {0} +text.trace.structureblocksbroken=Bloques de estructura rotos: [acento] {0} +text.trace.lastblockbroken=Último bloque roto: [acento] {0} +text.trace.totalblocksplaced=Total de bloques colocados: [acento] {0} +text.trace.lastblockplaced=Último bloque colocado: [acento] {0} +text.invalidid=ID de cliente no válido Presente un informe del error. +text.server.bans=Baneos +text.server.bans.none=¡No se encontraron jugadores baneados! +text.server.admins=Admins +text.server.admins.none=¡No se encontraron administradores! +text.server.add=Agregar servidor +text.server.delete=¿Seguro que quieres eliminar este servidor? +text.server.hostname=Anfitrión: {0} +text.server.edit=Editar servidor +text.server.outdated=[crimson] ¡Servidor obsoleto! [] +text.server.outdated.client=[carmesí] Cliente desactualizado! [] +text.server.version=[lightgray] Versión: {0} +text.server.custombuild=[amarillo] Creación personalizada +text.confirmban=¿Estás seguro de que quieres prohibir este jugador? +text.confirmunban=¿Estás seguro de que quieres desbloquear a este jugador? +text.confirmadmin=¿Seguro que quieres que este jugador sea un administrador? +text.confirmunadmin=¿Seguro que quieres eliminar el estado de administrador de este reproductor? +text.joingame.byip=Unirse por IP ... +text.joingame.title=Unirse a una partida +text.joingame.ip=IP: +text.disconnect=Desconectado. +text.disconnect.data=¡Fallo al cargar datos mundiales! +text.connecting=[accent] Conectando ... +text.connecting.data=[accent] Cargando información del mapa... +text.connectfail=[crimson] Fallo al conectar al servidor: [orange] +text.server.port=Puerto: +text.server.addressinuse=¡Dirección ya en uso! +text.server.invalidport=¡Número de puerto inválido! +text.server.error=[crimson] Error en la creación del servidor: [orange] +text.tutorial.back=< Anterior +text.tutorial.next=Siguiente > +text.save.new=Nuevo Guardado +text.save.overwrite=¿Seguro que quieres sobrescribir este juego guardado? +text.overwrite=Sobreescribir +text.save.none=¡No hay juegos guardados! +text.saveload=[accent] Guardando... +text.savefail=¡Error al guardar el juego! +text.save.delete.confirm=¿Estás seguro de que deseas eliminar este guardado? +text.save.delete=Borrar +text.save.export=Exportar guardado +text.save.import.invalid=[orange] ¡Este guardado es inválido! +text.save.import.fail=[crimson] Fallo al importar guardado: [orange] {0} +text.save.export.fail=[crimson] Fallo al exportar guardado: [orange] {0} +text.save.import=Importar Guardado +text.save.newslot=Nombre del guardado: +text.save.rename=Renombrar +text.save.rename.text=Nuevo nombre +text.selectslot=Seleccionar una guardado +text.slot=[accent] Casilla {0} +text.save.corrupted=[orange] ¡Arhivo de guardado corrupto o inválido! +text.empty= +text.on=Encendido +text.off=Apagado +text.save.autosave=Guardado automático: {0} +text.save.map=Mapa: {0} +text.save.wave=Horda: {0} +text.save.difficulty=Dificultad: {0} +text.save.date=Guardado por última vez: {0} +text.confirm=Confirmar +text.delete=Eliiminar +text.ok=OK +text.open=Abrir +text.cancel=Cancelar +text.openlink=Abrir enlace +text.copylink=Copiar link +text.back=Atrás +text.quit.confirm=¿Seguro que quieres salir? +text.changelog.title=Changelog +text.changelog.loading=Obteniendo changelog ... +text.changelog.error.android=[naranja] Tenga en cuenta que el registro de cambios no funciona en Android 4.4 y versiones posteriores. Esto se debe a un error interno de Android. +text.changelog.error=[escarlata] ¡Error al obtener el registro de cambios! Comprueba tu conexión a Internet. +text.changelog.current=[amarillo] [[Versión actual] +text.changelog.latest=[naranja] [[Última versión] +text.loading=[accent] Cargando... +text.wave=[orange] Horda {0} +text.wave.waiting=Horda en {0} +text.waiting=Esperando... +text.enemies={0} Enemigos +text.enemies.single={0} Enemigo +text.loadimage=Cargar imagen +text.saveimage=Guardar imagen +text.editor.badsize=[orange]¡Dimensiones de imagen inválidas![]\nDimensiones de mapa válidas: {0} +text.editor.errorimageload=Error al cargar el archivo de imagen: [orange] {0} +text.editor.errorimagesave=Error al guardar el archivo de imagen: [orange] {0} +text.editor.generate=Generar +text.editor.resize=Cambiar tamaño +text.editor.loadmap=Cargar mapa +text.editor.savemap=Guardar mapa +text.editor.loadimage=Cargar imagen +text.editor.saveimage=Guardar imagen +text.editor.unsaved=[scarlet] ¡Tienes cambios sin guardar! [] ¿Estás seguro de que quieres salir? +text.editor.brushsize=Tamaño del pincel: {0} +text.editor.noplayerspawn=¡Este mapa no tiene punto de aparición del jugador! +text.editor.manyplayerspawns=¡Los mapas no pueden tener más de un punto de spawn de jugador! +text.editor.manyenemyspawns={0 }¡No puede tener más de puntos de aparición enemiga! +text.editor.resizemap=Cambiar el tamaño del mapa +text.editor.resizebig=[escarlata] ¡Advertencia! [] Los mapas de más de 256 unidades pueden ser inestables. +text.editor.mapname=Nombre del mapa +text.editor.overwrite=[acento] ¡Advertencia!\nEsto sobrescribe un mapa existente. +text.editor.selectmap=Seleccione un mapa para cargar: +text.width=Ancho: +text.height=Altura: +text.randomize=Aleatorizar +text.apply=Aplicar +text.update=Refrescar +text.menu=Menú +text.play=Jugar +text.load=Cargar +text.save=Salvar +text.language.restart=Por favor, reinicie su juego para que la configuración de idioma surta efecto. +text.settings.language=Idioma +text.settings=Ajustes +text.tutorial=Tutorial +text.editor=Editor +text.mapeditor=Editor de Mapas +text.donate=Donar +text.settings.reset=Restablecer los valores predeterminados +text.settings.controls=Controles +text.settings.game=Juego +text.settings.sound=Sonido +text.settings.graphics=Gráficos +text.upgrades=Mejoras +text.purchased=[LIME] Creado! +text.weapons=Armas +text.paused=Pausado +text.info.title=[acento] Información +text.error.title=[carmesí] Se ha producido un error +text.error.crashmessage=[SCARLET] Se ha producido un error inesperado, que habría causado un bloqueo. [] Informe las circunstancias exactas bajo las cuales se produjo este error al desarrollador: [ORANGE] anukendev@gmail.com [] +text.error.crashtitle=Ha ocurrido un error +text.blocks.blockinfo=Información de bloque +text.blocks.powercapacity=Capacidad de energía +text.blocks.powershot=Energía/disparo +text.blocks.size=Tamaño +text.blocks.liquidcapacity=Capacidad de liquido +text.blocks.maxitemssecond=Objetos máximos/segundo +text.blocks.powerrange=Rango de energía +text.blocks.itemcapacity=Capacidad de items +text.blocks.inputliquid=Entrada de líquidos +text.blocks.inputitem=Entrada de ítems +text.blocks.explosive=¡Altamente explosivo! +text.blocks.health=Vida +text.blocks.inaccuracy=Inexactitud +text.blocks.shots=Disparos +text.blocks.inputcapacity=Capacidad de entrada +text.blocks.outputcapacity=Capacidad de salida +setting.difficulty.easy=Fácil +setting.difficulty.normal=Mormal +setting.difficulty.hard=Difícil +setting.difficulty.insane=Insano +setting.difficulty.purge=Purga +setting.difficulty.name=Dificultad: +setting.screenshake.name=Shake de pantalla +setting.smoothcam.name=Cámara lisa +setting.indicators.name=Indicador del enemigo +setting.effects.name=Mostrar efectos +setting.sensitivity.name=Sensibilidad del controlador +setting.saveinterval.name=Intervalo de autoguardado +setting.seconds=Segundos +setting.fullscreen.name=Pantalla completa +setting.multithread.name=Multithreading +setting.fps.name=Mostrar fps +setting.vsync.name=VSync +setting.lasers.name=Mostrar láseres de poder +setting.previewopacity.name=Colocando Vista Previa Opacidad +setting.healthbars.name=Mostrar barras de vida de enemigos y jugadores +setting.pixelate.name=Pixelear pantalla +setting.musicvol.name=Volumen de la música +setting.mutemusic.name=Apagar música +setting.sfxvol.name=Volumen de los efectos de sonido +setting.mutesound.name=Apagar sonidos +map.maze.name=Laberinto +map.fortress.name=Fortaleza +map.sinkhole.name=Sumidero +map.caves.name=Cuevas +map.volcano.name=Volcán +map.caldera.name=Caldera +map.scorch.name=Desierto volcánico +map.desert.name=Desierto +map.island.name=Isla +map.grassland.name=Pastizal +map.tundra.name=Tundra +map.spiral.name=Espiral +map.tutorial.name=Tutorial +text.keybind.title=Vuelva a conectar las llaves +keybind.move_x.name=mover_x +keybind.move_y.name=mover_y +keybind.select.name=Elija +keybind.break.name=Romper +keybind.shoot.name=¡Dispara! +keybind.zoom_hold.name=Enfoque_Mantener +keybind.zoom.name=Enfoquè +keybind.block_info.name=Bloque_informacion +keybind.menu.name=Menú +keybind.pause.name=Pausa +keybind.dash.name=Deslizar +keybind.chat.name=Chat +keybind.player_list.name=Jugadores_lista +keybind.console.name=Console +keybind.rotate_alt.name=Rotacion_alt +keybind.rotate.name=Girar +mode.text.help.title=Descripción de modos +mode.waves.name=Hordas +mode.waves.description=El modo normal. Recursos limitados y las hordas vendrán automáticamente +mode.sandbox.name=Sandbox +mode.sandbox.description=Recursos infinitos y sin temporizador para las olas. +mode.freebuild.name=Construcción libre +mode.freebuild.description=Recursos limitados y sin tiempo definido para las hordas +item.stone.name=Piedra +item.coal.name=Carbón +item.titanium.name=Titanio +item.sand.name=Arena +liquid.water.name=Agua +liquid.lava.name=Lava +liquid.oil.name=Aceite +block.door.name=Puerta +block.door-large.name=Puerta grande +block.conduit.name=Conducto +block.pulseconduit.name=Conducto de pulso +block.liquidrouter.name=Enrutador líquido +block.conveyor.name=Transportador +block.router.name=Enrutador +block.junction.name=Union +block.liquidjunction.name=Unión líquida +block.sorter.name=Clasificador +block.smelter.name=horno de fundición +text.web.unsupported=The web version does not support this feature! Download the game to use it. +text.host.web=The web version does not support hosting games! Download the game to use this feature. +text.map.delete=Are you sure you want to delete the map "[orange]{0}[]"? +text.construction.title=Block Construction Guide +text.construction=You've just selected [accent]block construction mode[].\n\nTo begin placing, simply tap a valid location near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Shift the selection[] by holding and dragging any block in the selection.\n- [accent]Place blocks in a line[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel construction or selection[] by pressing the X at the bottom left. +text.deconstruction.title=Block Deconstruction Guide +text.deconstruction=You've just selected [accent]block deconstruction mode[].\n\nTo begin breaking, simply tap a block near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin de-constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Remove blocks in an area[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel deconstruction or selection[] by pressing the X at the bottom left. +text.showagain=Don't show again next session +text.unlocks=Unlocks +text.addplayers=Add/Remove Players +text.maps=Maps +text.maps.none=[LIGHT_GRAY]No maps found! +text.unlocked=New Block Unlocked! +text.unlocked.plural=New Blocks Unlocked! +text.server.rollback=Rollback +text.server.rollback.numberfield=Rollback Amount: +text.blocks.editlogs=Edit Logs +text.block.editlogsnotfound=[red]There are no edit logs for this location. +text.server.kicked.fastShoot=You are shooting too quickly. +text.server.kicked.nameInUse=There is someone with that name\nalready on this server. +text.server.kicked.nameEmpty=Your name must contain at least one character or number. +text.server.kicked.idInUse=You are already on this server! Connecting with two accounts is not permitted. +text.server.kicked.customClient=This server does not support custom builds. Download an official version. +text.changelog.error.ios=[orange]The changelog is currently not supported in iOS. +text.saving=[accent]Saving... +text.unknown=Unknown +text.custom=Custom +text.builtin=Built-In +text.map.delete.confirm=Are you sure you want to delete this map? This action cannot be undone! +text.map.random=[accent]Random Map +text.map.nospawn=This map does not have any cores for the player to spawn in! Add a [ROYAL]blue[] core to this map in the editor. +text.editor.slope=\\ +text.editor.openin=Open In Editor +text.editor.oregen=Ore Generation +text.editor.oregen.info=Ore Generation: +text.editor.mapinfo=Map Info +text.editor.author=Author: +text.editor.description=Description: +text.editor.name=Name: +text.editor.teams=Teams +text.editor.elevation=Elevation +text.editor.saved=Saved! +text.editor.save.noname=Your map does not have a name! Set one in the 'map info' menu. +text.editor.save.overwrite=Your map overwrites a built-in map! Pick a different name in the 'map info' menu. +text.editor.import.exists=[scarlet]Unable to import:[] a built-in map named '{0}' already exists! +text.editor.import=Import... +text.editor.importmap=Import Map +text.editor.importmap.description=Import an already existing map +text.editor.importfile=Import File +text.editor.importfile.description=Import an external map file +text.editor.importimage=Import Terrain Image +text.editor.importimage.description=Import an external map image file +text.editor.export=Export... +text.editor.exportfile=Export File +text.editor.exportfile.description=Export a map file +text.editor.exportimage=Export Terrain Image +text.editor.exportimage.description=Export a map image file +text.editor.overwrite.confirm=[scarlet]Warning![] A map with this name already exists. Are you sure you want to overwrite it? +text.fps=FPS: {0} +text.tps=TPS: {0} +text.ping=Ping: {0}ms +text.settings.rebind=Rebind +text.yes=Yes +text.no=No +text.blocks.targetsair=Targets Air +text.blocks.itemspeed=Units Moved +text.blocks.shootrange=Range +text.blocks.poweruse=Power Use +text.blocks.inputitemcapacity=Input Item Capacity +text.blocks.outputitemcapacity=Input Item Capacity +text.blocks.maxpowergeneration=Max Power Generation +text.blocks.powertransferspeed=Power Transfer +text.blocks.craftspeed=Production Speed +text.blocks.inputliquidaux=Aux Liquid +text.blocks.inputitems=Input Items +text.blocks.outputitem=Output Item +text.blocks.drilltier=Drillables +text.blocks.drillspeed=Base Drill Speed +text.blocks.liquidoutput=Liquid Output +text.blocks.liquiduse=Liquid Use +text.blocks.coolant=Coolant +text.blocks.coolantuse=Coolant Use +text.blocks.inputliquidfuel=Fuel Liquid +text.blocks.liquidfueluse=Liquid Fuel Use +text.blocks.reload=Reload +text.blocks.inputfuel=Fuel +text.blocks.fuelburntime=Fuel Burn Time +text.unit.blocks=blocks +text.unit.powersecond=power units/second +text.unit.liquidsecond=liquid units/second +text.unit.itemssecond=items/second +text.unit.pixelssecond=pixels/second +text.unit.liquidunits=liquid units +text.unit.powerunits=power units +text.unit.degrees=degrees +text.unit.seconds=seconds +text.unit.none= +text.unit.items=items +text.category.general=General +text.category.power=Power +text.category.liquids=Liquids +text.category.items=Items +text.category.crafting=Crafting +text.category.shooting=Shooting +setting.minimap.name=Show Minimap +content.item.name=Items +content.liquid.name=Liquids +content.unit-type.name=Units +content.recipe.name=Blocks +item.stone.description=A common raw material. Used for separating and refining into other materials, or melting into lava. +item.tungsten.name=Tungsten +item.tungsten.description=A common, but very useful structure material. Used in drills and heat-resistant blocks such as generators and smelteries. +item.lead.name=Lead +item.lead.description=A basic starter material. Used extensively in electronics and liquid transportation blocks. +item.coal.description=A common and readily available fuel. +item.carbide.name=Carbide +item.carbide.description=A tough alloy made with tungsten and carbon. Used in advanced transportation blocks and high-tier drills. +item.titanium.description=A rare super-light metal used extensively in liquid transportation, drills and aircraft. +item.thorium.name=Thorium +item.thorium.description=A dense, radioactive metal used as structural support and nuclear fuel. +item.silicon.name=Silicon +item.silcion.description=An extremely useful semiconductor, with applications in solar panels and many complex electronics. +item.plastanium.name=Plastanium +item.plastanium.description=A light, ductile material used in advanced aircraft and fragmentation ammunition. +item.phase-matter.name=Phase Matter +item.surge-alloy.name=Surge Alloy +item.biomatter.name=Biomatter +item.biomatter.description=A clump of organic mush; used for conversion into oil or as a basic fuel. +item.sand.description=A common material that is used extensively in smelting, both in alloying and as a flux. +item.blast-compound.name=Blast Compound +item.blast-compound.description=A volatile compound used in bombs and explosives. While it can burned as fuel, this is not advised. +item.pyratite.name=Pyratite +item.pyratite.description=An extremely flammable substance used in incendiary weapons. +liquid.cryofluid.name=Cryofluid +text.item.explosiveness=[LIGHT_GRAY]Explosiveness: {0} +text.item.flammability=[LIGHT_GRAY]Flammability: {0} +text.item.radioactivity=[LIGHT_GRAY]Radioactivity: {0} +text.item.fluxiness=[LIGHT_GRAY]Flux Power: {0} +text.item.hardness=[LIGHT_GRAY]Hardness: {0} +text.liquid.heatcapacity=[LIGHT_GRAY]Heat Capacity: {0} +text.liquid.viscosity=[LIGHT_GRAY]Viscosity: {0} +text.liquid.temperature=[LIGHT_GRAY]Temperature: {0} +block.tungsten-wall.name=Tungsten Wall +block.tungsten-wall-large.name=Large Tungsten Wall +block.carbide-wall.name=Carbide Wall +block.carbide-wall-large.name=Large Carbide Wall +block.thorium-wall.name=Thorium Wall +block.thorium-wall-large.name=Large Thorium Wall +block.duo.name=Duo +block.scorch.name=Scorch +block.hail.name=Hail +block.lancer.name=Lancer +block.titanium-conveyor.name=Titanium Conveyor +block.splitter.name=Splitter +block.splitter.description=Outputs items into two opposite directions immediately after they are recieved. +block.router.description=Splits items into all 4 directions. Can store items as a buffer. +block.distributor.name=Distributor +block.distributor.description=A splitter that can split items into 8 directions. +block.sorter.description=Sorts items. If an item matches the selection, it is allowed to pass. Otherwise, the item is outputted to the left and right. +block.overflow-gate.name=Overflow Gate +block.overflow-gate.description=A combination splitter and router that only outputs to the left and right if the front path is blocked. +block.bridgeconveyor.name=Bridge Conveyor +block.bridgeconveyor.description=A conveyor that can go over other blocks, for up to two total blocks. +block.arc-smelter.name=Arc Smelter +block.silicon-smelter.name=Silicon Smelter +block.phase-weaver.name=Phase Weaver +block.pulverizer.name=Pulverizer +block.cryofluidmixer.name=Cryofluid Mixer +block.melter.name=Melter +block.incinerator.name=Incinerator +block.biomattercompressor.name=Biomatter Compressor +block.separator.name=Separator +block.centrifuge.name=Centrifuge +block.power-node.name=Power Node +block.power-node-large.name=Large Power Node +block.battery.name=Battery +block.battery-large.name=Large Battery +block.combustion-generator.name=Combustion Generator +block.turbine-generator.name=Turbine Generator +block.tungsten-drill.name=Tungsten Drill +block.carbide-drill.name=Carbide Drill +block.laser-drill.name=Laser Drill +block.water-extractor.name=Water Extractor +block.cultivator.name=Cultivator +block.dart-ship-factory.name=Dart Ship Factory +block.delta-mech-factory.name=Delta Mech Factory +block.dronefactory.name=Drone Factory +block.repairpoint.name=Repair Point +block.resupplypoint.name=Resupply Point +block.liquidtank.name=Liquid Tank +block.bridgeconduit.name=Bridge Conduit +block.mechanical-pump.name=Mechanical Pump +block.itemsource.name=Item Source +block.itemvoid.name=Item Void +block.liquidsource.name=Liquid Source +block.powervoid.name=Power Void +block.powerinfinite.name=Power Infinite +block.unloader.name=Unloader +block.sortedunloader.name=Sorted Unloader +block.vault.name=Vault +block.wave.name=Wave +block.swarmer.name=Swarmer +block.salvo.name=Salvo +block.ripple.name=Ripple +block.phase-conveyor.name=Phase Conveyor +block.bridge-conveyor.name=Bridge Conveyor +block.plastanium-compressor.name=Plastanium Compressor +block.pyratite-mixer.name=Pyratite Mixer +block.blast-mixer.name=Blast Mixer +block.solidifer.name=Solidifer +block.solar-panel.name=Solar Panel +block.solar-panel-large.name=Large Solar Panel +block.oil-extractor.name=Oil Extractor +block.javelin-ship-factory.name=Javelin Ship factory +block.drone-factory.name=Drone Factory +block.fabricator-factory.name=Fabricator Factory +block.repair-point.name=Repair Point +block.resupply-point.name=Resupply Point +block.pulse-conduit.name=Pulse Conduit +block.phase-conduit.name=Phase Conduit +block.liquid-router.name=Liquid Router +block.liquid-tank.name=Liquid Tank +block.liquid-junction.name=Liquid Junction +block.bridge-conduit.name=Bridge Conduit +block.rotary-pump.name=Rotary Pump +block.nuclear-reactor.name=Nuclear Reactor diff --git a/core/assets/bundles/bundle_fr.properties b/core/assets/bundles/bundle_fr.properties index 6c8a007520..e61eee09e0 100644 --- a/core/assets/bundles/bundle_fr.properties +++ b/core/assets/bundles/bundle_fr.properties @@ -1,528 +1,515 @@ -text.about = Créé par [ROYAL]Anuken.[]\nA l'origine une entrée dans le [orange]GDL[] MM Jam.\n\nCrédits: \n- SFX réalisé avec [yellow]bfxr[] \n- Musique faite par [lime]RoccoW[] / trouvé sur [lime]FreeMusicArchive.org[] \n\nRemerciements particuliers à:\n- [coral]MitchellFJN[]: nombreux tests et retours d'expérience \n- [sky]Luxray5474[]: travail wiki, contributions de code \n- [lime]Epowerj[]: système de compilation de code, icône \n- Tous les beta testeurs sont sur itch.io et Google Play\n -text.discord = Rejoignez le discord de Mindustry -text.changes = [SCARLET]Attention![] \nCertains mécanismes importants du jeu ont ete modifies.\n-Les [accent]telporteurs[] utilisent maintenant de l'énergie.\n-Les [accent]fonderies[] et les[accent]crucibles[] ont maintenant une capacité maximale d'articles.\n-Les [accent]crucibles[] exigent maintenant du charbon comme combustible. -text.gameover = Le noyau a été détruit. -text.highscore = [YELLOW]Nouveau meilleur score! -text.lasted = Vous avez duré jusqu'à la vague -text.level.highscore = Meilleur score: [accent]{0} -text.level.delete.title = Confirmer -text.level.delete = Êtes-vous sûr de vouloir supprimer la carte \"[orange] \"? -text.level.select = Sélection de niveau -text.level.mode = Mode de jeu : -text.savegame = Sauvegarder la partie -text.loadgame = Charger la partie -text.joingame = Rejoindre la partie -text.quit = Quitter -text.about.button = À propos -text.name = Nom : -text.public = Publique -text.players = joueurs en ligne -text.server.player.host = Héberger -text.players.single = joueur en ligne -text.server.mismatch = Erreur de paquet: possible incompatibilité de version client/serveur. Assurez-vous que vous et l'hôte avez la dernière version de Mindustry! -text.server.closing = [accent]Fermeture du serveur ... -text.server.kicked.kick = Vous avez été expulsé du serveur! -text.server.kicked.invalidPassword = Mot de passe non valide ! -text.server.kicked.clientOutdated = Client dépassé! Mettez à jour votre jeu! -text.server.kicked.serverOutdated = Serveur dépassé! Demandez à l'hôte de le mettre à jour! -text.server.kicked.banned = Vous êtes banni sur ce serveur. -text.server.connected = a rejoint le serveur -text.server.disconnected = {0} s'est déconnecté. -text.nohost = Impossible d'héberger le serveur sur une carte personnalisée! -text.hostserver = Héberger un serveur -text.host = Héberger -text.hosting = [accent]Ouverture du serveur ... -text.hosts.refresh = Actualiser -text.hosts.discovering = Recherche de parties LAN -text.server.refreshing = Actualisation du serveur -text.hosts.none = [lightgray]Aucun jeu LAN trouvé! -text.host.invalid = [scarlet]Impossible de se connecter à l'hôte. -text.server.friendlyfire = Tir allié -text.trace = suivre le joueur -text.trace.playername = Nom du joueur: [accent] {0} -text.trace.ip = IP: [accent] {0} -text.trace.id = ID unique: [accent] {0} -text.trace.android = Client Android: [accent] {0} -text.trace.modclient = Client personnalisé: [accent] {0} -text.trace.totalblocksbroken = Total des blocs détruits: [accent] {0} -text.trace.structureblocksbroken = Blocs de structure détruits: [accent] {0} -text.trace.lastblockbroken = Dernier bloc détruit: [accent] {0} -text.trace.totalblocksplaced = Nombre total de blocs placés: [accent] {0} -text.trace.lastblockplaced = Dernier bloc placé: [accent] {0} -text.invalidid = ID client invalide! Soumettre un rapport de bug -text.server.bans = Interdictions -text.server.bans.none = Aucun joueur banni trouvé! -text.server.admins = Administrateurs -text.server.admins.none = Aucun administrateur trouvé! -text.server.add = Ajouter un serveur -text.server.delete = Êtes-vous sûr de vouloir supprimer ce serveur? -text.server.hostname = Héberger -text.server.edit = éditer le serveur -text.server.outdated = [crimson]Serveur obsolète![] -text.server.outdated.client = [Crimson]Client obsolète![] -text.server.version = [lightgray]Version: {0} -text.server.custombuild = [jaune]Construction personnalisée -text.confirmban = Êtes-vous sûr de vouloir bannir ce joueur? -text.confirmunban = Êtes-vous sûr de vouloir annuler le ban de ce joueur? -text.confirmadmin = Êtes-vous sûr de vouloir faire de ce joueur un administrateur? -text.confirmunadmin = Êtes-vous sûr de vouloir supprimer le statut d'administrateur de ce joueur? -text.joingame.byip = Rejoindre par IP ... -text.joingame.title = Rejoindre une partie -text.joingame.ip = IP : -text.disconnect = Déconnecté -text.connecting = [accent]Connexion ... -text.connecting.data = [accent] Chargement des données de la partie ... -text.connectfail = [crimson] Échec de la connexion au serveur : [orange] -text.server.port = Port : -text.server.addressinuse = Adresse déjà utilisée! -text.server.invalidport = Numéro de port incorrect. -text.server.error = [crimson]Erreur lors de l'hébergement du serveur: [orange] {0} -text.tutorial.back = < Précédent\n -text.tutorial.next = Suivant> -text.save.new = Nouvelle sauvegarde -text.save.overwrite = Êtes-vous sûr de vouloir remplacer cette sauvegarde? -text.overwrite = Écraser -text.save.none = Aucune sauvegarde trouvée! -text.saveload = [accent]Sauvegarde ... -text.savefail = Échec de la sauvegarde du jeu ! -text.save.delete.confirm = Êtes-vous sûr de vouloir supprimer cette sauvegarde? -text.save.delete = Supprimer -text.save.export = Exporter la sauvegarde -text.save.import.invalid = [orange]Cette sauvegarde est invalide! -text.save.import.fail = [crimson]Echec de l'importation de la sauvegarde: [orange] {0} -text.save.export.fail = [crimson]Échec de l'exportation de la sauvegarde: [orange] {0} -text.save.import = Importer la sauvegarde -text.save.newslot = Enregistrer le nom: -text.save.rename = Renommer -text.save.rename.text = Nouveau nom: -text.selectslot = Sélectionnez une sauvegarde. -text.slot = [accent]Emplacement {0} -text.save.corrupted = [orange]Le fichier enregistrer est corrompu ou invalide! -text.empty = -text.on = Allumer -text.off = Eteint -text.save.autosave = Sauvegarde automatique -text.save.map = Carte -text.save.wave = Vague : -text.save.difficulty = DIFFICULTÉ -text.save.date = Dernière sauvegarde: {0} -text.confirm = Confirmer -text.delete = Supprimer -text.ok = OK -text.open = Ouvrir -text.cancel = Annuler -text.openlink = Lien public -text.back = Retour -text.quit.confirm = Êtes-vous sûr de vouloir quitter? -text.changelog.title = Note de mise à jour -text.changelog.error = [Scarlet] Erreur lors de l'obtention du changement de serveur! Vérifiez votre connexion internet. -text.changelog.current = [jaune] [[Version actuelle] -text.changelog.latest = [orange][[Dernière version] -text.loading = [accent]Chargement ... -text.wave = [orange]Vague {0} -text.wave.waiting = Vague dans {0} -text.waiting = Attente... -text.enemies = ennemis -text.enemies.single = Ennemi -text.loadimage = Charger l'image -text.saveimage = Enregistrer l'image -text.oregen = Génération de minerais -text.editor.badsize = [orange]Dimensions de l'image non valides![] Dimensions de la carte valides: {0} -text.editor.errorimageload = Erreur lors du chargement du fichier image:[orange] {0} -text.editor.errorimagesave = Erreur lors de la sauvegarde du fichier image:[orange] {0} -text.editor.generate = Générer -text.editor.resize = Redimensionner -text.editor.loadmap = Charger la carte -text.editor.savemap = Enregistrer la carte -text.editor.loadimage = Charger l'image -text.editor.saveimage = Enregistrer l'image -text.editor.unsaved = [scarlet] Vous avez des changements non sauvegardés![] Êtes-vous sûr de vouloir quitter? -text.editor.brushsize = Taille de la brosse: -text.editor.noplayerspawn = Cette carte n'a pas de point d'apparition de joueur! -text.editor.manyplayerspawns = Les cartes ne peuvent pas avoir plus d'un point d'apparition de joueur! -text.editor.manyenemyspawns = Il ne peut pas avoir plus de {0} points d'apparition ennemis! -text.editor.resizemap = Redimensionner la carte -text.editor.resizebig = [scarlet]Attention![] Les cartes de plus de 256 unités peuvent laggés et être instables. -text.editor.mapname = Nom de la carte: -text.editor.overwrite = [accent]Attention! Cela écrasera la carte existante. -text.editor.failoverwrite = [Crimson]Impossible d'écraser la carte par défaut! -text.editor.selectmap = Sélectionnez une carte à charger: -text.width = Largeur: -text.height = Hauteur: -text.randomize = Rendre aléatoire -text.apply = Appliquer -text.update = Modifier -text.menu = Menu -text.play = Jouer -text.load = Charger -text.save = Sauvegarder -text.language.restart = Veuillez redémarrer votre jeu pour que les paramètres de langue soient appliqués. -text.settings.language = Langue -text.settings = Réglages -text.tutorial = Tutoriel -text.editor = Éditeur -text.mapeditor = Éditeur de carte -text.donate = Faire un don -text.settings.reset = Valeur par défaut. -text.settings.controls = Contrôles -text.settings.game = Jeu -text.settings.sound = Son -text.settings.graphics = Graphique -text.upgrades = Améliorations -text.purchased = [VERT]Créé! -text.weapons = Armes -text.paused = Pause -text.respawn = Réapparition dans -text.info.title = [accent]Info -text.error.title = [crimson]Une erreur est survenue -text.error.crashmessage = [SCARLET]Une erreur inattendue est survenue, et aurait provoqué un crash.[] Veuillez indiquer les circonstances exactes dans lesquelles cette erreur est survenue au développeur:[ORANGE] anukendev@gmail.com[] -text.error.crashtitle = Une erreur est survenue -text.mode.break = Mode de rupture: {0} -text.mode.place = Placez le mode: {0} -placemode.hold.name = Ligne -placemode.areadelete.name = surface -placemode.touchdelete.name = toucher -placemode.holddelete.name = tenir -placemode.none.name = aucun -placemode.touch.name = toucher -placemode.cursor.name = curseur -text.blocks.extrainfo = [accent] info supplémentaire du bloc: -text.blocks.blockinfo = Bloquer les infos -text.blocks.powercapacity = capacité d'énergie -text.blocks.powershot = Energie/Tir -text.blocks.powersecond = Energie/Seconde -text.blocks.powerdraindamage = Utilisation d'énergie/Dommage -text.blocks.shieldradius = rayon du bouclier -text.blocks.itemspeedsecond = Vitesse d'Article/Seconde -text.blocks.range = Portée -text.blocks.size = Taille -text.blocks.powerliquid = énergie/Liquide -text.blocks.maxliquidsecond = Max Liquide/Seconde -text.blocks.liquidcapacity = Capacité liquide -text.blocks.liquidsecond = Liquide/seconde -text.blocks.damageshot = Dégâts/tir -text.blocks.ammocapacity = Capacité de munitions -text.blocks.ammo = Munition -text.blocks.ammoitem = Munitions/Article -text.blocks.maxitemssecond = Max articles/seconde -text.blocks.powerrange = Gamme de puissance -text.blocks.lasertilerange = portée du laser -text.blocks.capacity = Capacité -text.blocks.itemcapacity = Capacité article -text.blocks.maxpowergenerationsecond = Génération max d'énergie/seconde -text.blocks.powergenerationsecond = Génération d'énergie/seconde -text.blocks.generationsecondsitem = Génération Secondes / item -text.blocks.input = entrée -text.blocks.inputliquid = Entrée de liquide -text.blocks.inputitem = entré d'article -text.blocks.output = Sortie -text.blocks.secondsitem = Secondes/article -text.blocks.maxpowertransfersecond = Transfert max d'énergie/seconde -text.blocks.explosive = Hautement explosif ! -text.blocks.repairssecond = Réparation/seconde -text.blocks.health = Santé -text.blocks.inaccuracy = Inexactitude -text.blocks.shots = tirs -text.blocks.shotssecond = Tirs/seconde -text.blocks.fuel = Carburant -text.blocks.fuelduration = Durée du carburant -text.blocks.maxoutputsecond = Sortie max/seconde -text.blocks.inputcapacity = Capacité d'entrée -text.blocks.outputcapacity = Capacité de sortie -text.blocks.poweritem = Energie/Article -text.placemode = Mode Placement -text.breakmode = Mode destruction -text.health = santé -setting.difficulty.easy = facile -setting.difficulty.normal = normal -setting.difficulty.hard = difficile -setting.difficulty.insane = Extreme -setting.difficulty.purge = Purge -setting.difficulty.name = Difficulté: -setting.screenshake.name = Tremblement d'écran -setting.smoothcam.name = Caméra lisse -setting.indicators.name = Indicateurs ennemis -setting.effects.name = Effets d'affichage -setting.sensitivity.name = Sensibilité de la manette -setting.saveinterval.name = Intervalle des sauvegardes auto -setting.seconds = {0} secondes -setting.fullscreen.name = Plein écran -setting.multithread.name = Multithreading [scarlet] (instable!) -setting.fps.name = Afficher FPS -setting.vsync.name = VSync -setting.lasers.name = Afficher les rayons des lasers -setting.healthbars.name = Afficher les barres de santé des entités -setting.pixelate.name = Pixéliser l'écran -setting.musicvol.name = volume musique -setting.mutemusic.name = Musique muette -setting.sfxvol.name = Volume SFX -setting.mutesound.name = Son muet -map.maze.name = Labyrinthe -map.fortress.name = forteresse -map.sinkhole.name = gouffre -map.caves.name = cavernes -map.volcano.name = volcan -map.caldera.name = chaudron -map.scorch.name = brûlure -map.desert.name = désert -map.island.name = Île -map.grassland.name = prairie -map.tundra.name = toundra -map.spiral.name = spirale -map.tutorial.name = tutoriel -tutorial.intro.text = [yellow]Bienvenue dans le tutoriel[]. Pour commencer, appuyez sur \"suivant\". -tutorial.moveDesktop.text = Pour vous déplacer, utilisez les touches [orange][[WASD][]. Maintenez [orange]shift[] pour accélérer. Maintenez la touche [orange]CTRL[] enfoncée tout en utilisant la [orange]molette[] pour effectuer un zoom avant ou arrière. -tutorial.shoot.text = Utilisez votre souris pour viser, maintenez le [orange]bouton gauche de la souris[] pour tirer. Essayez de pratiquer sur la [yellow]cible[]. -tutorial.moveAndroid.text = Pour déplacer votre vue, faites glisser un doigt sur l'écran. Pincez et faites glisser pour effectuer un zoom avant ou arrière. -tutorial.placeSelect.text = Essayez de sélectionner un [yellow]transporteur[] dans le menu des blocs en bas à droite. -tutorial.placeConveyorDesktop.text = Utilisez la [orange][[molette][] pour faire pivoter le transporteur [orange]vers l'avant[], puis placez-le dans l'emplacement [yellow]marqué[] en utilisant le [orange][[bouton gauche de la souris][]. -tutorial.placeConveyorAndroid.text = Utilisez [orange][[bouton de rotation][] pour faire pivoter le transporteur [orange]vers l'avant[], faites-le glisser avec votre doigt, puis placez-le dans l'emplacement [yellow]marqué[] avec [orange][[coche][]. -tutorial.placeConveyorAndroidInfo.text = Vous pouvez également appuyer sur l'icône du réticule en bas à gauche pour passer en [orange][[mode tactile][] et placer des blocs en appuyant sur l'écran. En mode tactile, les blocs peuvent être pivotés avec la flèche en bas à gauche. Appuyez sur [yellow]suivant[] pour essayer. -tutorial.placeDrill.text = Maintenant, sélectionnez et placez un [yellow]extracteur de pierre[] à l'endroit marqué. -tutorial.blockInfo.text = Si vous voulez en savoir plus sur un bloc, vous pouvez appuyer sur le [orange]point d'interrogation[] en haut à droite pour lire sa description. -tutorial.deselectDesktop.text = Vous pouvez désélectionner un bloc en utilisant le [orange][[bouton droit de la souris][]. -tutorial.deselectAndroid.text = Vous pouvez désélectionner un bloc en appuyant sur le bouton [orange]X[]. -tutorial.drillPlaced.text = L'extracteur produira maintenant de la [yellow]pierre[], qui sera placée sur le transporteur, puis sera emmenée dans le [yellow]noyau[]. -tutorial.drillInfo.text = Différents minerais ont besoin de différents extracteurs. La pierre nécessite des extracteurs de pierre, le fer des extracteurs de fer,...etc... -tutorial.drillPlaced2.text = Le déplacement d'article dans le noyau le place dans votre[yellow]inventaire[], au milieu à droite. Le placement de blocs utilise les éléments de votre inventaire. -tutorial.moreDrills.text = Vous pouvez relier plusieurs extracteurs et transporteurs ensemble, comme ça. -tutorial.deleteBlock.text = Vous pouvez supprimer des blocs en cliquant sur le clique [orange]droit de la souris[] sur le bloc que vous voulez supprimer. Essayez de supprimer ce transporteur. -tutorial.deleteBlockAndroid.text = Vous pouvez supprimer des blocs en [orange]sélectionnant le réticule[] dans [orange] le menu du mode destruction[] en bas à gauche et en appuyant sur un bloc. Essayez de supprimer ce transporteur. -tutorial.placeTurret.text = Maintenant, sélectionnez et placez une [yellow]tourelle[] à l'[yellow]emplacement marqué[]. -tutorial.placedTurretAmmo.text = Cette tourelle accepte maintenant les [yellow]munitions[] du transporteur. Vous pouvez voir combien de munitions elle a en la survolant et en vérifiant sa [green]barre verte[]. -tutorial.turretExplanation.text = Les tourelles tirent automatiquement sur l'ennemi le plus proche, pourvu qu'elles aient suffisamment de munitions. -tutorial.waves.text = Toutes les [yellow]60 secondes[], une [coral]vague d'ennemis[] apparaîtra dans des endroits spécifiques et tentera de détruire votre noyau. -tutorial.coreDestruction.text = Votre objectif est de [yellow]défendre le noyau[]. Si le noyau est détruit, vous [coral]perdez le jeu[]. -tutorial.pausingDesktop.text = Si vous avez besoin de faire une pause, appuyez sur le bouton [orange]pause[] en haut à gauche pour mettre le jeu en pause. Vous pouvez toujours casser et placer des blocs en pause, mais vous ne pouvez pas bouger ou tirer. -tutorial.pausingAndroid.text = Si vous avez besoin de faire une pause, appuyez sur le bouton [orange]pause[] en haut à gauche pour mettre le jeu en pause. Vous pouvez toujours casser et placer des blocs en pause. -tutorial.purchaseWeapons.text = Vous pouvez acheter de nouvelles [yellow]armes[] pour votre robot en ouvrant le menu de mise à niveau en bas à gauche. -tutorial.switchWeapons.text = Changez d'arme en cliquant sur l'icône en bas à droite, ou en utilisant les chiffres [orange][[1-9][]. -tutorial.spawnWave.text = Une vague arrive. Détruisez-les. -tutorial.pumpDesc.text = Dans les vagues plus lointaines, vous devrez peut-être utiliser des [yellow]pompes[] pour distribuer du liquide pour les générateurs ou les extracteurs. -tutorial.pumpPlace.text = Les pompes fonctionnent de la même manière que les extracteurs, sauf qu'elles récoltent du liquides et non du minerai. Essayez de placer une pompe sur [yellow]pétrole[] désigné. -tutorial.conduitUse.text = Maintenant, placez un [orange]conduit[] qui s'éloigne de la pompe. -tutorial.conduitUse2.text = Et un autre ... -tutorial.conduitUse3.text = Et un autre ... -tutorial.generator.text = Maintenant, placez un bloc [orange]de générateur à combustion[] à l'extrémité du conduit. -tutorial.generatorExplain.text = Ce générateur va maintenant créer de l' [yellow]énergie[] à partir de pétrole. -tutorial.lasers.text = L'énergie est distribuée à l'aide de [yellow]lasers d'énergie[]. Tournez et placez-en un ici. -tutorial.laserExplain.text = Le générateur va maintenant déplacer la puissance dans le laser. Un faisceau [yellow]opaque[] signifie qu'il transmet actuellement de la puissance, et un faisceau [yellow]transparent[] signifie que ce n'est pas le cas. -tutorial.laserMore.text = Vous pouvez vérifier la puissance d'un bloc en survolant celui-ci et en vérifiant la [yellow]barre jaune[] en haut. -tutorial.healingTurret.text = Ce laser peut être utilisé pour alimenter une [lime]tourelle de réparation[]. Placez-en une ici. -tutorial.healingTurretExplain.text = Tant qu'elle a de la puissance, cette tourelle [lime]réparera les blocs voisins[].En jouant, assurez-vous d'en avoir une dans votre base le plus rapidement possible! -tutorial.smeltery.text = De nombreux blocs nécessitent de l' [orange]acier[] pour fabriquer, ce qui nécessite une [orange]fonderie[]. Placez-en une ici. -tutorial.smelterySetup.text = Cette fonderie produira maintenant de l' [orange]acier[] à partir du fer, utilisant le charbon comme combustible. -tutorial.tunnelExplain.text = Notez également que les objets traversent un [orange]tunnel[] et émergent de l'autre côté, traversant le bloc de pierre. Gardez à l'esprit que les tunnels ne peuvent traverser que 2 blocs. -tutorial.end.text = Et cela conclut le tutoriel! Bonne chance! -text.keybind.title = Relier le clés -keybind.move_x.name = mouvement x -keybind.move_y.name = mouvement y -keybind.select.name = sélectionner -keybind.break.name = Pause -keybind.shoot.name = tirer -keybind.zoom_hold.name = tenir le zoom -keybind.zoom.name = zoom -keybind.block_info.name = bloc_info -keybind.menu.name = menu -keybind.pause.name = Pause -keybind.dash.name = attaque frontal -keybind.chat.name = chat -keybind.player_list.name = Liste des joueurs -keybind.console.name = console -keybind.rotate_alt.name = tourner_alt -keybind.rotate.name = Tourner -keybind.weapon_1.name = Arme 1 -keybind.weapon_2.name = Arme 2 -keybind.weapon_3.name = Arme 3 -keybind.weapon_4.name = Arme 4 -keybind.weapon_5.name = Arme 5 -keybind.weapon_6.name = Arme 6 -mode.waves.name = Vagues -mode.sandbox.name = bac à sable -mode.freebuild.name = construction libre -upgrade.standard.name = La norme -upgrade.standard.description = Le robot standard. -upgrade.blaster.name = blaster -upgrade.blaster.description = Tire faiblement et lentetement. -upgrade.triblaster.name = Tri-blaster -upgrade.triblaster.description = Tire 3 balles se propageant en V. -upgrade.clustergun.name = clustergun -upgrade.clustergun.description = Tire une portée de grenades explosives. -upgrade.beam.name = beam cannon -upgrade.beam.description = Tire un rayon laser de perçage à longue portée. -upgrade.vulcan.name = Vulcain -upgrade.vulcan.description = Tire un barrage de balles rapides. -upgrade.shockgun.name = shockgun -upgrade.shockgun.description = Tire une charge de balles qui explosent en éclats. -item.stone.name = pierre -item.iron.name = fer -item.coal.name = charbon -item.steel.name = Acier -item.titanium.name = Titane -item.dirium.name = dirium -item.uranium.name = uranium -item.sand.name = sable -liquid.water.name = eau -liquid.plasma.name = plasma -liquid.lava.name = lave -liquid.oil.name = pétrole -block.weaponfactory.name = usine d'armes -block.weaponfactory.fulldescription = Utilisé pour créer des armes pour le joueur robot. Cliquez pour utiliser. Extrait automatiquement les ressources du noyau. -block.air.name = air -block.blockpart.name = partie de block -block.deepwater.name = eaux profondes -block.water.name = eau -block.lava.name = lave -block.oil.name = pétrole -block.stone.name = pierre -block.blackstone.name = pierre noire -block.iron.name = fer -block.coal.name = charbon -block.titanium.name = titane -block.uranium.name = uranium -block.dirt.name = terre -block.sand.name = sable -block.ice.name = glace -block.snow.name = neige -block.grass.name = herbe -block.sandblock.name = bloc de sable -block.snowblock.name = bloc de neige -block.stoneblock.name = bloc de pierre -block.blackstoneblock.name = bloc de roche noire -block.grassblock.name = bloc d'herbe -block.mossblock.name = bloc de mousse -block.shrub.name = arbuste -block.rock.name = roche -block.icerock.name = roche de glace -block.blackrock.name = roche noire -block.dirtblock.name = bloc de terre -block.stonewall.name = mur de pierres -block.stonewall.fulldescription = Un bloc défensif bon marché. Utile pour protéger le noyau et les tourelles durant les premières vagues. -block.ironwall.name = mur de fer -block.ironwall.fulldescription = Un bloc défensif de base. Fournit une protection contre les ennemis. -block.steelwall.name = mur en acier -block.steelwall.fulldescription = Un bloc défensif standard. une protection adéquate contre les ennemis. -block.titaniumwall.name = mur de titane -block.titaniumwall.fulldescription = Un bloc défensif très résistant. Fournit une protection contre les ennemis. -block.duriumwall.name = mur de dirium -block.duriumwall.fulldescription = Un bloc défensif extrêmement résistant. Fournit une protection contre les ennemis. -block.compositewall.name = mur composite -block.steelwall-large.name = grand mur d'acier -block.steelwall-large.fulldescription = Un bloc défensif standard, prend plusieurs blocs de largeurs. -block.titaniumwall-large.name = grand mur de titane -block.titaniumwall-large.fulldescription = Un bloc défensif très résistant, prend plusieurs blocs de largeurs. -block.duriumwall-large.name = grand mur de dirium -block.duriumwall-large.fulldescription = Un bloc défensif extrêmement résistant, prend plusieurs blocs de largeurs. -block.titaniumshieldwall.name = mur blindé -block.titaniumshieldwall.fulldescription = Un bloc défensif solide, avec un bouclier intégré. Nécessite de l'énergie. Utilise l'énergie pour absorber les balles ennemies. Il est recommandé d'utiliser un distributeur d'énergie pour fournir de l'énergie à ce bloc. -block.repairturret.name = tourelle de réparation -block.repairturret.fulldescription = Répare les blocs avoisinants endommagés dans un zone circulaire à un rythme lent. Utilise de l'énergie. -block.megarepairturret.name = tourelle de réparation II -block.megarepairturret.fulldescription = Répare les blocs avoisinants endommagés dans un zone circulaire à un rythme régulier. Utilise de l'énergie. -block.shieldgenerator.name = générateur de bouclier -block.shieldgenerator.fulldescription = Un bloc défensif avancé. Protège tous les blocs avoisinants dans une zone circulaire. Utilise l'énergie à un rythme lent, mais draine beaucoup d'énergie au contact d'un tir ennemi. -block.door.name = porte -block.door.fulldescription = Un bloc qui peut être ouvert et fermé en cliquant dessus. -block.door-large.name = grande porte -block.door-large.fulldescription = Un bloc qui peut être ouvert et fermé en cliquant dessus. -block.conduit.name = conduit -block.conduit.fulldescription = Bloc de transport de liquide basique. Fonctionne comme un transporteur, mais avec des liquides. A placé à coté de pompes. Peut être utilisé comme un pont sur les liquides pour les ennemis et les joueurs. -block.pulseconduit.name = conduit à impulsion -block.pulseconduit.fulldescription = Bloc de transport de liquide avancé. Transporte les liquides plus rapidement et stocke plus que les conduits standards. -block.liquidrouter.name = routeur de liquide -block.liquidrouter.fulldescription = Fonctionne de manière similaire à un routeur. Accepte l'entrée de liquide d'un côté et l'envoie vers 3 autres côtés. Utile pour répartir le liquide d'un conduit vers plusieurs autres conduits. -block.conveyor.name = transporteur -block.conveyor.fulldescription = Bloc de transport standard. Déplace les objets vers l'avant et les dépose automatiquement dans les tourelles ou des blocs d'artisanats. Rotatif. Peut être utilisé comme une plateforme sur les liquides, pour les ennemis et les joueurs. -block.steelconveyor.name = transporteur en acier -block.steelconveyor.fulldescription = transporteur d'articles avancé. Déplace les objets plus rapidement que les transporteurs standards. -block.poweredconveyor.name = transporteur à impulsions -block.poweredconveyor.fulldescription = Le transporteur d'articles ultime. Déplace les articles plus rapidement que les convoyeurs en acier. -block.router.name = Routeur -block.router.fulldescription = Accepte les éléments d'une direction et les distribue dans 3 autres directions. Peut également stocker une certaine quantité d'articles. Utile pour diviser cette quantité afin d'approvisionner plusieurs tourelles ou des blocs d'artisanat. -block.junction.name = jonction -block.junction.fulldescription = Agit comme un pont pour deux transporteurs qui se croisent et qui possèdent différents articles. -block.conveyortunnel.name = tunnel de transport -block.conveyortunnel.fulldescription = Transporte des articles sous les blocs. Pour l'utiliser, placez les entre des blocs, au maximum deux. Assurez-vous que les deux tunnels sont orientés dans des directions opposées. -block.liquidjunction.name = jonction à liquide -block.liquidjunction.fulldescription = Agit comme un pont pour deux conduits. Utile dans la situations ou deux conduits se croisent et transportent différents liquides. -block.liquiditemjunction.name = jonction de liquide-article -block.liquiditemjunction.fulldescription = Agit comme un pont pour croiser les conduits et les convoyeurs. -block.powerbooster.name = distributeur d'énergie -block.powerbooster.fulldescription = Distribue la puissance à tous les blocs avoisinants dans une zone circulaire. -block.powerlaser.name = laser d'énergie -block.powerlaser.fulldescription = Crée un laser qui transmet la puissance au bloc en face de lui. Ne génère pas d'énergie par lui-même. Idéal avec des générateurs ou d'autres lasers. -block.powerlaserrouter.name = routeur laser -block.powerlaserrouter.fulldescription = Laser qui distribue la puissance à trois directions à la fois. Utile pour séparer l'énergie afin d'alimenter plusieurs blocs -block.powerlasercorner.name = laser en angle droit -block.powerlasercorner.fulldescription = Laser qui distribue la puissance à deux directions en angle droit. Utile pour séparer l'énergie afin d'alimenter plusieurs blocs. -block.teleporter.name = téléporteur -block.teleporter.fulldescription = Bloc de transport avancé. Les téléporteurs saisissent des articles aux autres téléporteurs de la même couleur. Ne fait rien si aucun téléporteur de la même couleur n'existe. Si plusieurs téléporteurs existent de la même couleur, un téléporteur aléatoire est sélectionné. Utilise de l'énergie. Cliquez pour changer de couleur. -block.sorter.name = trieur -block.sorter.fulldescription = Trie l'article par type de matériau. Le matériau à accepter est indiqué par la couleur du bloc. Tous les éléments qui correspondent au matériau de tri sont sortis vers l'avant, tout le reste est sorti à gauche et à droite. -block.core.name = noyau -block.pump.name = pompe -block.pump.fulldescription = Pompe les liquides provenant d'un bloc source - généralement de l'eau, de la lave ou de l'huile. Transmet le liquide dans les conduits voisins. -block.fluxpump.name = pompe à flux -block.fluxpump.fulldescription = Une version avancée de la pompe. Stocke plus et pompe le liquide plus rapidement que la pompe ordinaire. -block.smelter.name = fonderie -block.smelter.fulldescription = Le bloc d'artisanat essentiel. Produit de l'acier lorsqu'il est approvisionné en fer et charbon. Il est conseillé d'introduire le fer et le charbon par différents transporteurs pour éviter les situations de débordement. -block.crucible.name = Crucible -block.crucible.fulldescription = Un bloc d'artisanat avancé. Produit du diridium lorsqu'il est approvisionné en fer, en titanium et en charbon . Il est conseillé d'introduire ces minerais par différents transporteurs. -block.coalpurifier.name = raffinerie de charbon -block.coalpurifier.fulldescription = Un bloc d'artisanat de base. Produit du charbon lorsqu'il est fourni avec de grandes quantités d'eau et de pierre. -block.titaniumpurifier.name = raffinerie de titane -block.titaniumpurifier.fulldescription = Un bloc d'artisanat avancé. Produit du titane lorsqu'il est fourni avec de grandes quantités d'eau et de fer. -block.oilrefinery.name = raffinerie de pétrole -block.oilrefinery.fulldescription = Un bloc d'artisanat standard. Affine de grandes quantités de pétrole grâce au charbon. Utile pour alimenter les tourelles à charbon lorsque les filons de charbon sont rares. -block.stoneformer.name = raffinerie de pierre -block.stoneformer.fulldescription = Un bloc d'artisanat standard. Il purifie la lave en pierre. Utile pour produire des quantités massives de pierre. -block.lavasmelter.name = fonderie d'acier -block.lavasmelter.fulldescription = Un bloc d'artisanat de base. Utilise la lave pour convertir le fer en acier. Une alternative aux fonderies. Utile dans les situations où le charbon est rare. -block.stonedrill.name = extracteur de pierre -block.stonedrill.fulldescription = L'extracteur essentiel. Lorsqu'il est placé sur de la pierre, il l'extrait à un rythme rapide. -block.irondrill.name = extracteur de fer -block.irondrill.fulldescription = Un extracteur de base. Lorsqu'ils il est placé sur un minerai de charbon, il l'extrait à un rythme régulier. -block.coaldrill.name = extracteur de charbon -block.coaldrill.fulldescription = Un extracteur de base. Lorsqu'il est placé sur un minerai de charbon, il l'extrait à un rythme régulier. -block.uraniumdrill.name = extracteur d'uranium -block.uraniumdrill.fulldescription = Un extracteur avancé .Lorsqu'il est placé sur un minerai d'uranium, il l'extrait lentement. -block.titaniumdrill.name = extracteur de titane -block.titaniumdrill.fulldescription = Un extracteur avancé. Lorsqu'il est placé sur un minerai de titane, il l'extrait lentement. -block.omnidrill.name = omni-extracteur -block.omnidrill.fulldescription = L'extracteur ultime .Minera n'importe quel minerai sur lequel il est placé à un rythme rapide. -block.coalgenerator.name = générateur à charbon -block.coalgenerator.fulldescription = Le générateur essentiel. Génère de l'énergie à partir du charbon. eparpille l'énergie de ses 4 cotés. -block.thermalgenerator.name = générateur thermique -block.thermalgenerator.fulldescription = Génère de l'énergie à partir de la lave. éparpille l'énergie de ses 4 cotés. -block.combustiongenerator.name = générateur à combustion -block.combustiongenerator.fulldescription = Génère de l'énergie à partir du pétrole. éparpille l'énergie de ses 4 cotés. -block.rtgenerator.name = Générateur RTG -block.rtgenerator.fulldescription = Génère de petites quantités d'énergie à partir d'uranium. éparpille l'énergie de ses 4 cotés -block.nuclearreactor.name = réacteur nucléaire -block.nuclearreactor.fulldescription = Une version avancée du générateur RTG ,et le générateur d'énergie ultime. Génère de l'énergie à partir de l'uranium. Nécessite un refroidissement à eau constant. Hautement inflammable; explosera violemment si des quantités insuffisantes de liquide de refroidissement sont fournies. -block.turret.name = tourelle -block.turret.fulldescription = Une tourelle basique et bon marché. Utilise la pierre pour munition. A légèrement plus de portée que la double-tourelle. -block.doubleturret.name = tourelle double -block.doubleturret.fulldescription = Une version légèrement plus puissante de la tourelle. Utilise la pierre comme munition. Fait beaucoup plus de dégâts, mais a une portée inférieure. -block.machineturret.name = tourelle gattling -block.machineturret.fulldescription = Une tourelle complète standard. Utilise le fer pour munition. A une vitesse de tir rapide avec des dégâts décents. -block.shotgunturret.name = tourelle fusil à pompe. -block.shotgunturret.fulldescription = Une tourelle standard. Utilise le fer pour munition. Tire une portée de 7 balles. Portée basse, mais plus de dégâts que la tourelle gattling. -block.flameturret.name = tourelle incendiaire -block.flameturret.fulldescription = Tourelle avancée à courte portée. Utilise du charbon pour munition. A une portée très faible, mais des dégâts très élevés. Bon pour les places étroites. Recommandé pour tirer à travers les murs. -block.sniperturret.name = Tourelle laser -block.sniperturret.fulldescription = Tourelle avancée à longue portée. Utilise l'acier pour munition. Dommages très élevés, mais faible vitesse de tir. Chère, mais peut être placé loin des lignes ennemies en raison de sa portée. -block.mortarturret.name = Tourelle Antiaérienne -block.mortarturret.fulldescription = Tourelle avancée à fragmentation de faible précision. Utilise du charbon pour munition. Tire un barrage de balles qui explose en éclats. Utile pour les grandes foules d'ennemis. -block.laserturret.name = Tourelle Laser -block.laserturret.fulldescription = Tourelle à cible unique avancée. Utilise de l'énergie. Bonne tourelle polyvalente de moyenne portée. Une seule cible seulement. Ne manque jamais. -block.waveturret.name = Tourelle Tesla -block.waveturret.fulldescription = Tourelle multi-cible avancée. Utilise de l'énergie. Portée moyenne. Ne manque jamais sa cible. Peu de dégâts, mais peut frapper plusieurs ennemis simultanément avec sa répétition d'éclair. -block.plasmaturret.name = Tourelle à plasma -block.plasmaturret.fulldescription = Version très avancée de la tourelle incendiaire. Utilise le charbon comme munition. Dommages très élevés, de faible à moyenne portée. -block.chainturret.name = Tourelle à répétition. -block.chainturret.fulldescription = La tourelle ultime à tir rapide. Utilise l'uranium comme munition. Tire de grosses salves à une vitesse de feu élevée. Portée moyenne. Traverse plusieurs carreaux. Extrêmement résistante. -block.titancannon.name = Cannon Titan -block.titancannon.fulldescription = La tourelle à longue portée ultime. Utilise l'uranium comme munition. Tire de gros obus à dégâts de zone à une cadence de tir moyenne. Longue portée. occupe plusieurs blocks. Extrêmement dur. -block.playerspawn.name = point d'apparition joueur -block.enemyspawn.name = Point d'apparition ennemie +text.about=Créé par [ROYAL]Anuken.[]\nA l'origine une entrée dans le [orange]GDL[] MM Jam.\n\nCrédits: \n- SFX réalisé avec [yellow]bfxr[] \n- Musique faite par [lime]RoccoW[] / trouvé sur [lime]FreeMusicArchive.org[] \n\nRemerciements particuliers à:\n- [coral]MitchellFJN[]: nombreux tests et retours d'expérience \n- [sky]Luxray5474[]: travail wiki, contributions de code \n- [lime]Epowerj[]: système de compilation de code, icône \n- Tous les beta testeurs sont sur itch.io et Google Play\n +text.discord=Rejoignez le discord de Mindustry +text.gameover=Le noyau a été détruit. +text.highscore=[YELLOW]Nouveau meilleur score! +text.lasted=Vous avez duré jusqu'à la vague +text.level.highscore=Meilleur score: [accent]{0} +text.level.delete.title=Confirmer +text.level.select=Sélection de niveau +text.level.mode=Mode de jeu : +text.savegame=Sauvegarder la partie +text.loadgame=Charger la partie +text.joingame=Rejoindre la partie +text.quit=Quitter +text.about.button=À propos +text.name=Nom : +text.public=Publique +text.players=joueurs en ligne +text.server.player.host=Héberger +text.players.single=joueur en ligne +text.server.mismatch=Erreur de paquet: possible incompatibilité de version client/serveur. Assurez-vous que vous et l'hôte avez la dernière version de Mindustry! +text.server.closing=[accent]Fermeture du serveur ... +text.server.kicked.kick=Vous avez été expulsé du serveur! +text.server.kicked.invalidPassword=Mot de passe non valide ! +text.server.kicked.clientOutdated=Client dépassé! Mettez à jour votre jeu! +text.server.kicked.serverOutdated=Serveur dépassé! Demandez à l'hôte de le mettre à jour! +text.server.kicked.banned=Vous êtes banni sur ce serveur. +text.server.connected=a rejoint le serveur +text.server.disconnected={0} s'est déconnecté. +text.nohost=Impossible d'héberger le serveur sur une carte personnalisée! +text.hostserver=Héberger un serveur +text.host=Héberger +text.hosting=[accent]Ouverture du serveur ... +text.hosts.refresh=Actualiser +text.hosts.discovering=Recherche de parties LAN +text.server.refreshing=Actualisation du serveur +text.hosts.none=[lightgray]Aucun jeu LAN trouvé! +text.host.invalid=[scarlet]Impossible de se connecter à l'hôte. +text.server.friendlyfire=Tir allié +text.trace=suivre le joueur +text.trace.playername=Nom du joueur: [accent] {0} +text.trace.ip=IP: [accent] {0} +text.trace.id=ID unique: [accent] {0} +text.trace.android=Client Android: [accent] {0} +text.trace.modclient=Client personnalisé: [accent] {0} +text.trace.totalblocksbroken=Total des blocs détruits: [accent] {0} +text.trace.structureblocksbroken=Blocs de structure détruits: [accent] {0} +text.trace.lastblockbroken=Dernier bloc détruit: [accent] {0} +text.trace.totalblocksplaced=Nombre total de blocs placés: [accent] {0} +text.trace.lastblockplaced=Dernier bloc placé: [accent] {0} +text.invalidid=ID client invalide! Soumettre un rapport de bug +text.server.bans=Interdictions +text.server.bans.none=Aucun joueur banni trouvé! +text.server.admins=Administrateurs +text.server.admins.none=Aucun administrateur trouvé! +text.server.add=Ajouter un serveur +text.server.delete=Êtes-vous sûr de vouloir supprimer ce serveur? +text.server.hostname=Héberger +text.server.edit=éditer le serveur +text.server.outdated=[crimson]Serveur obsolète![] +text.server.outdated.client=[Crimson]Client obsolète![] +text.server.version=[lightgray]Version: {0} +text.server.custombuild=[jaune]Construction personnalisée +text.confirmban=Êtes-vous sûr de vouloir bannir ce joueur? +text.confirmunban=Êtes-vous sûr de vouloir annuler le ban de ce joueur? +text.confirmadmin=Êtes-vous sûr de vouloir faire de ce joueur un administrateur? +text.confirmunadmin=Êtes-vous sûr de vouloir supprimer le statut d'administrateur de ce joueur? +text.joingame.byip=Rejoindre par IP ... +text.joingame.title=Rejoindre une partie +text.joingame.ip=IP : +text.disconnect=Déconnecté +text.connecting=[accent]Connexion ... +text.connecting.data=[accent] Chargement des données de la partie ... +text.connectfail=[crimson] Échec de la connexion au serveur : [orange] +text.server.port=Port : +text.server.addressinuse=Adresse déjà utilisée! +text.server.invalidport=Numéro de port incorrect. +text.server.error=[crimson]Erreur lors de l'hébergement du serveur: [orange] {0} +text.tutorial.back=< Précédent\n +text.tutorial.next=Suivant> +text.save.new=Nouvelle sauvegarde +text.save.overwrite=Êtes-vous sûr de vouloir remplacer cette sauvegarde? +text.overwrite=Écraser +text.save.none=Aucune sauvegarde trouvée! +text.saveload=[accent]Sauvegarde ... +text.savefail=Échec de la sauvegarde du jeu ! +text.save.delete.confirm=Êtes-vous sûr de vouloir supprimer cette sauvegarde? +text.save.delete=Supprimer +text.save.export=Exporter la sauvegarde +text.save.import.invalid=[orange]Cette sauvegarde est invalide! +text.save.import.fail=[crimson]Echec de l'importation de la sauvegarde: [orange] {0} +text.save.export.fail=[crimson]Échec de l'exportation de la sauvegarde: [orange] {0} +text.save.import=Importer la sauvegarde +text.save.newslot=Enregistrer le nom: +text.save.rename=Renommer +text.save.rename.text=Nouveau nom: +text.selectslot=Sélectionnez une sauvegarde. +text.slot=[accent]Emplacement {0} +text.save.corrupted=[orange]Le fichier enregistrer est corrompu ou invalide! +text.empty= +text.on=Allumer +text.off=Eteint +text.save.autosave=Sauvegarde automatique +text.save.map=Carte +text.save.wave=Vague : +text.save.difficulty=DIFFICULTÉ +text.save.date=Dernière sauvegarde: {0} +text.confirm=Confirmer +text.delete=Supprimer +text.ok=OK +text.open=Ouvrir +text.cancel=Annuler +text.openlink=Lien public +text.back=Retour +text.quit.confirm=Êtes-vous sûr de vouloir quitter? +text.changelog.title=Note de mise à jour +text.changelog.error=[Scarlet] Erreur lors de l'obtention du changement de serveur! Vérifiez votre connexion internet. +text.changelog.current=[jaune] [[Version actuelle] +text.changelog.latest=[orange][[Dernière version] +text.loading=[accent]Chargement ... +text.wave=[orange]Vague {0} +text.wave.waiting=Vague dans {0} +text.waiting=Attente... +text.enemies=ennemis +text.enemies.single=Ennemi +text.loadimage=Charger l'image +text.saveimage=Enregistrer l'image +text.editor.badsize=[orange]Dimensions de l'image non valides![] Dimensions de la carte valides: {0} +text.editor.errorimageload=Erreur lors du chargement du fichier image:[orange] {0} +text.editor.errorimagesave=Erreur lors de la sauvegarde du fichier image:[orange] {0} +text.editor.generate=Générer +text.editor.resize=Redimensionner +text.editor.loadmap=Charger la carte +text.editor.savemap=Enregistrer la carte +text.editor.loadimage=Charger l'image +text.editor.saveimage=Enregistrer l'image +text.editor.unsaved=[scarlet] Vous avez des changements non sauvegardés![] Êtes-vous sûr de vouloir quitter? +text.editor.brushsize=Taille de la brosse: +text.editor.noplayerspawn=Cette carte n'a pas de point d'apparition de joueur! +text.editor.manyplayerspawns=Les cartes ne peuvent pas avoir plus d'un point d'apparition de joueur! +text.editor.manyenemyspawns=Il ne peut pas avoir plus de {0} points d'apparition ennemis! +text.editor.resizemap=Redimensionner la carte +text.editor.resizebig=[scarlet]Attention![] Les cartes de plus de 256 unités peuvent laggés et être instables. +text.editor.mapname=Nom de la carte: +text.editor.overwrite=[accent]Attention! Cela écrasera la carte existante. +text.editor.selectmap=Sélectionnez une carte à charger: +text.width=Largeur: +text.height=Hauteur: +text.randomize=Rendre aléatoire +text.apply=Appliquer +text.update=Modifier +text.menu=Menu +text.play=Jouer +text.load=Charger +text.save=Sauvegarder +text.language.restart=Veuillez redémarrer votre jeu pour que les paramètres de langue soient appliqués. +text.settings.language=Langue +text.settings=Réglages +text.tutorial=Tutoriel +text.editor=Éditeur +text.mapeditor=Éditeur de carte +text.donate=Faire un don +text.settings.reset=Valeur par défaut. +text.settings.controls=Contrôles +text.settings.game=Jeu +text.settings.sound=Son +text.settings.graphics=Graphique +text.upgrades=Améliorations +text.purchased=[VERT]Créé! +text.weapons=Armes +text.paused=Pause +text.info.title=[accent]Info +text.error.title=[crimson]Une erreur est survenue +text.error.crashmessage=[SCARLET]Une erreur inattendue est survenue, et aurait provoqué un crash.[] Veuillez indiquer les circonstances exactes dans lesquelles cette erreur est survenue au développeur:[ORANGE] anukendev@gmail.com[] +text.error.crashtitle=Une erreur est survenue +text.blocks.blockinfo=Bloquer les infos +text.blocks.powercapacity=capacité d'énergie +text.blocks.powershot=Energie/Tir +text.blocks.size=Taille +text.blocks.liquidcapacity=Capacité liquide +text.blocks.maxitemssecond=Max articles/seconde +text.blocks.powerrange=Gamme de puissance +text.blocks.itemcapacity=Capacité article +text.blocks.inputliquid=Entrée de liquide +text.blocks.inputitem=entré d'article +text.blocks.explosive=Hautement explosif ! +text.blocks.health=Santé +text.blocks.inaccuracy=Inexactitude +text.blocks.shots=tirs +text.blocks.inputcapacity=Capacité d'entrée +text.blocks.outputcapacity=Capacité de sortie +setting.difficulty.easy=facile +setting.difficulty.normal=normal +setting.difficulty.hard=difficile +setting.difficulty.insane=Extreme +setting.difficulty.purge=Purge +setting.difficulty.name=Difficulté: +setting.screenshake.name=Tremblement d'écran +setting.smoothcam.name=Caméra lisse +setting.indicators.name=Indicateurs ennemis +setting.effects.name=Effets d'affichage +setting.sensitivity.name=Sensibilité de la manette +setting.saveinterval.name=Intervalle des sauvegardes auto +setting.seconds={0} secondes +setting.fullscreen.name=Plein écran +setting.multithread.name=Multithreading [scarlet] (instable!) +setting.fps.name=Afficher FPS +setting.vsync.name=VSync +setting.lasers.name=Afficher les rayons des lasers +setting.healthbars.name=Afficher les barres de santé des entités +setting.pixelate.name=Pixéliser l'écran +setting.musicvol.name=volume musique +setting.mutemusic.name=Musique muette +setting.sfxvol.name=Volume SFX +setting.mutesound.name=Son muet +map.maze.name=Labyrinthe +map.fortress.name=forteresse +map.sinkhole.name=gouffre +map.caves.name=cavernes +map.volcano.name=volcan +map.caldera.name=chaudron +map.scorch.name=brûlure +map.desert.name=désert +map.island.name=Île +map.grassland.name=prairie +map.tundra.name=toundra +map.spiral.name=spirale +map.tutorial.name=tutoriel +text.keybind.title=Relier le clés +keybind.move_x.name=mouvement x +keybind.move_y.name=mouvement y +keybind.select.name=sélectionner +keybind.break.name=Pause +keybind.shoot.name=tirer +keybind.zoom_hold.name=tenir le zoom +keybind.zoom.name=zoom +keybind.block_info.name=bloc_info +keybind.menu.name=menu +keybind.pause.name=Pause +keybind.dash.name=attaque frontal +keybind.chat.name=chat +keybind.player_list.name=Liste des joueurs +keybind.console.name=console +keybind.rotate_alt.name=tourner_alt +keybind.rotate.name=Tourner +mode.waves.name=Vagues +mode.sandbox.name=bac à sable +mode.freebuild.name=construction libre +item.stone.name=pierre +item.coal.name=charbon +item.titanium.name=Titane +item.sand.name=sable +liquid.water.name=eau +liquid.lava.name=lave +liquid.oil.name=pétrole +block.door.name=porte +block.door-large.name=grande porte +block.conduit.name=conduit +block.pulseconduit.name=conduit à impulsion +block.liquidrouter.name=routeur de liquide +block.conveyor.name=transporteur +block.router.name=Routeur +block.junction.name=jonction +block.liquidjunction.name=jonction à liquide +block.sorter.name=trieur +block.smelter.name=fonderie +text.credits=Credits +text.link.discord.description=the official Mindustry discord chatroom +text.link.github.description=Game source code +text.link.dev-builds.description=Unstable development builds +text.link.trello.description=Official trello board for planned features +text.link.itch.io.description=itch.io page with PC downloads and web version +text.link.google-play.description=Google Play store listing +text.link.wiki.description=official Mindustry wiki +text.linkfail=Failed to open link!\nThe URL has been copied to your cliboard. +text.editor.web=The web version does not support the editor!\nDownload the game to use it. +text.web.unsupported=The web version does not support this feature! Download the game to use it. +text.multiplayer.web=This version of the game does not support multiplayer!\nTo play multiplayer from your browser, use the "multiplayer web version" link at the itch.io page. +text.host.web=The web version does not support hosting games! Download the game to use this feature. +text.map.delete=Are you sure you want to delete the map "[orange]{0}[]"? +text.construction.title=Block Construction Guide +text.construction=You've just selected [accent]block construction mode[].\n\nTo begin placing, simply tap a valid location near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Shift the selection[] by holding and dragging any block in the selection.\n- [accent]Place blocks in a line[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel construction or selection[] by pressing the X at the bottom left. +text.deconstruction.title=Block Deconstruction Guide +text.deconstruction=You've just selected [accent]block deconstruction mode[].\n\nTo begin breaking, simply tap a block near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin de-constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Remove blocks in an area[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel deconstruction or selection[] by pressing the X at the bottom left. +text.showagain=Don't show again next session +text.unlocks=Unlocks +text.addplayers=Add/Remove Players +text.newgame=New Game +text.maps=Maps +text.maps.none=[LIGHT_GRAY]No maps found! +text.unlocked=New Block Unlocked! +text.unlocked.plural=New Blocks Unlocked! +text.server.rollback=Rollback +text.server.rollback.numberfield=Rollback Amount: +text.blocks.editlogs=Edit Logs +text.block.editlogsnotfound=[red]There are no edit logs for this location. +text.server.kicked.fastShoot=You are shooting too quickly. +text.server.kicked.recentKick=You have been kicked recently.\nWait before connecting again. +text.server.kicked.nameInUse=There is someone with that name\nalready on this server. +text.server.kicked.nameEmpty=Your name must contain at least one character or number. +text.server.kicked.idInUse=You are already on this server! Connecting with two accounts is not permitted. +text.server.kicked.customClient=This server does not support custom builds. Download an official version. +text.host.info=The [accent]host[] button hosts a server on ports [scarlet]6567[] and [scarlet]6568.[]\nAnybody on the same [LIGHT_GRAY]wifi or local network[] should be able to see your server in their server list.\n\nIf you want people to be able to connect from anywhere by IP, [accent]port forwarding[] is required.\n\n[LIGHT_GRAY]Note: If someone is experiencing trouble connecting to your LAN game, make sure you have allowed Mindustry access to your local network in your firewall settings. +text.join.info=Here, you can enter a [accent]server IP[] to connect to, or discover [accent]local network[] servers to connect to.\nBoth LAN and WAN multiplayer is supported.\n\n[LIGHT_GRAY]Note: There is no automatic global server list; if you want to connect to someone by IP, you would need to ask the host for their IP. +text.disconnect.data=Failed to load world data! +text.copylink=Copy Link +text.changelog.loading=Getting changelog... +text.changelog.error.android=[orange]Note that the changelog sometimes does not work on Android 4.4 and below!\nThis is due to an internal Android bug. +text.changelog.error.ios=[orange]The changelog is currently not supported in iOS. +text.saving=[accent]Saving... +text.unknown=Unknown +text.custom=Custom +text.builtin=Built-In +text.map.delete.confirm=Are you sure you want to delete this map? This action cannot be undone! +text.map.random=[accent]Random Map +text.map.nospawn=This map does not have any cores for the player to spawn in! Add a [ROYAL]blue[] core to this map in the editor. +text.editor.slope=\\ +text.editor.openin=Open In Editor +text.editor.oregen=Ore Generation +text.editor.oregen.info=Ore Generation: +text.editor.mapinfo=Map Info +text.editor.author=Author: +text.editor.description=Description: +text.editor.name=Name: +text.editor.teams=Teams +text.editor.elevation=Elevation +text.editor.saved=Saved! +text.editor.save.noname=Your map does not have a name! Set one in the 'map info' menu. +text.editor.save.overwrite=Your map overwrites a built-in map! Pick a different name in the 'map info' menu. +text.editor.import.exists=[scarlet]Unable to import:[] a built-in map named '{0}' already exists! +text.editor.import=Import... +text.editor.importmap=Import Map +text.editor.importmap.description=Import an already existing map +text.editor.importfile=Import File +text.editor.importfile.description=Import an external map file +text.editor.importimage=Import Terrain Image +text.editor.importimage.description=Import an external map image file +text.editor.export=Export... +text.editor.exportfile=Export File +text.editor.exportfile.description=Export a map file +text.editor.exportimage=Export Terrain Image +text.editor.exportimage.description=Export a map image file +text.editor.overwrite.confirm=[scarlet]Warning![] A map with this name already exists. Are you sure you want to overwrite it? +text.fps=FPS: {0} +text.tps=TPS: {0} +text.ping=Ping: {0}ms +text.settings.rebind=Rebind +text.yes=Yes +text.no=No +text.blocks.targetsair=Targets Air +text.blocks.itemspeed=Units Moved +text.blocks.shootrange=Range +text.blocks.poweruse=Power Use +text.blocks.inputitemcapacity=Input Item Capacity +text.blocks.outputitemcapacity=Input Item Capacity +text.blocks.maxpowergeneration=Max Power Generation +text.blocks.powertransferspeed=Power Transfer +text.blocks.craftspeed=Production Speed +text.blocks.inputliquidaux=Aux Liquid +text.blocks.inputitems=Input Items +text.blocks.outputitem=Output Item +text.blocks.drilltier=Drillables +text.blocks.drillspeed=Base Drill Speed +text.blocks.liquidoutput=Liquid Output +text.blocks.liquiduse=Liquid Use +text.blocks.coolant=Coolant +text.blocks.coolantuse=Coolant Use +text.blocks.inputliquidfuel=Fuel Liquid +text.blocks.liquidfueluse=Liquid Fuel Use +text.blocks.reload=Reload +text.blocks.inputfuel=Fuel +text.blocks.fuelburntime=Fuel Burn Time +text.unit.blocks=blocks +text.unit.powersecond=power units/second +text.unit.liquidsecond=liquid units/second +text.unit.itemssecond=items/second +text.unit.pixelssecond=pixels/second +text.unit.liquidunits=liquid units +text.unit.powerunits=power units +text.unit.degrees=degrees +text.unit.seconds=seconds +text.unit.none= +text.unit.items=items +text.category.general=General +text.category.power=Power +text.category.liquids=Liquids +text.category.items=Items +text.category.crafting=Crafting +text.category.shooting=Shooting +setting.previewopacity.name=Placing Preview Opacity +setting.minimap.name=Show Minimap +mode.text.help.title=Description of modes +mode.waves.description=the normal mode. limited resources and automatic incoming waves. +mode.sandbox.description=infinite resources and no timer for waves. +mode.freebuild.description=limited resources and no timer for waves. +content.item.name=Items +content.liquid.name=Liquids +content.unit-type.name=Units +content.recipe.name=Blocks +item.stone.description=A common raw material. Used for separating and refining into other materials, or melting into lava. +item.tungsten.name=Tungsten +item.tungsten.description=A common, but very useful structure material. Used in drills and heat-resistant blocks such as generators and smelteries. +item.lead.name=Lead +item.lead.description=A basic starter material. Used extensively in electronics and liquid transportation blocks. +item.coal.description=A common and readily available fuel. +item.carbide.name=Carbide +item.carbide.description=A tough alloy made with tungsten and carbon. Used in advanced transportation blocks and high-tier drills. +item.titanium.description=A rare super-light metal used extensively in liquid transportation, drills and aircraft. +item.thorium.name=Thorium +item.thorium.description=A dense, radioactive metal used as structural support and nuclear fuel. +item.silicon.name=Silicon +item.silcion.description=An extremely useful semiconductor, with applications in solar panels and many complex electronics. +item.plastanium.name=Plastanium +item.plastanium.description=A light, ductile material used in advanced aircraft and fragmentation ammunition. +item.phase-matter.name=Phase Matter +item.surge-alloy.name=Surge Alloy +item.biomatter.name=Biomatter +item.biomatter.description=A clump of organic mush; used for conversion into oil or as a basic fuel. +item.sand.description=A common material that is used extensively in smelting, both in alloying and as a flux. +item.blast-compound.name=Blast Compound +item.blast-compound.description=A volatile compound used in bombs and explosives. While it can burned as fuel, this is not advised. +item.pyratite.name=Pyratite +item.pyratite.description=An extremely flammable substance used in incendiary weapons. +liquid.cryofluid.name=Cryofluid +text.item.explosiveness=[LIGHT_GRAY]Explosiveness: {0} +text.item.flammability=[LIGHT_GRAY]Flammability: {0} +text.item.radioactivity=[LIGHT_GRAY]Radioactivity: {0} +text.item.fluxiness=[LIGHT_GRAY]Flux Power: {0} +text.item.hardness=[LIGHT_GRAY]Hardness: {0} +text.liquid.heatcapacity=[LIGHT_GRAY]Heat Capacity: {0} +text.liquid.viscosity=[LIGHT_GRAY]Viscosity: {0} +text.liquid.temperature=[LIGHT_GRAY]Temperature: {0} +block.tungsten-wall.name=Tungsten Wall +block.tungsten-wall-large.name=Large Tungsten Wall +block.carbide-wall.name=Carbide Wall +block.carbide-wall-large.name=Large Carbide Wall +block.thorium-wall.name=Thorium Wall +block.thorium-wall-large.name=Large Thorium Wall +block.duo.name=Duo +block.scorch.name=Scorch +block.hail.name=Hail +block.lancer.name=Lancer +block.titanium-conveyor.name=Titanium Conveyor +block.splitter.name=Splitter +block.splitter.description=Outputs items into two opposite directions immediately after they are recieved. +block.router.description=Splits items into all 4 directions. Can store items as a buffer. +block.distributor.name=Distributor +block.distributor.description=A splitter that can split items into 8 directions. +block.sorter.description=Sorts items. If an item matches the selection, it is allowed to pass. Otherwise, the item is outputted to the left and right. +block.overflow-gate.name=Overflow Gate +block.overflow-gate.description=A combination splitter and router that only outputs to the left and right if the front path is blocked. +block.bridgeconveyor.name=Bridge Conveyor +block.bridgeconveyor.description=A conveyor that can go over other blocks, for up to two total blocks. +block.arc-smelter.name=Arc Smelter +block.silicon-smelter.name=Silicon Smelter +block.phase-weaver.name=Phase Weaver +block.pulverizer.name=Pulverizer +block.cryofluidmixer.name=Cryofluid Mixer +block.melter.name=Melter +block.incinerator.name=Incinerator +block.biomattercompressor.name=Biomatter Compressor +block.separator.name=Separator +block.centrifuge.name=Centrifuge +block.power-node.name=Power Node +block.power-node-large.name=Large Power Node +block.battery.name=Battery +block.battery-large.name=Large Battery +block.combustion-generator.name=Combustion Generator +block.turbine-generator.name=Turbine Generator +block.tungsten-drill.name=Tungsten Drill +block.carbide-drill.name=Carbide Drill +block.laser-drill.name=Laser Drill +block.water-extractor.name=Water Extractor +block.cultivator.name=Cultivator +block.dart-ship-factory.name=Dart Ship Factory +block.delta-mech-factory.name=Delta Mech Factory +block.dronefactory.name=Drone Factory +block.repairpoint.name=Repair Point +block.resupplypoint.name=Resupply Point +block.liquidtank.name=Liquid Tank +block.bridgeconduit.name=Bridge Conduit +block.mechanical-pump.name=Mechanical Pump +block.itemsource.name=Item Source +block.itemvoid.name=Item Void +block.liquidsource.name=Liquid Source +block.powervoid.name=Power Void +block.powerinfinite.name=Power Infinite +block.unloader.name=Unloader +block.sortedunloader.name=Sorted Unloader +block.vault.name=Vault +block.wave.name=Wave +block.swarmer.name=Swarmer +block.salvo.name=Salvo +block.ripple.name=Ripple +block.phase-conveyor.name=Phase Conveyor +block.bridge-conveyor.name=Bridge Conveyor +block.plastanium-compressor.name=Plastanium Compressor +block.pyratite-mixer.name=Pyratite Mixer +block.blast-mixer.name=Blast Mixer +block.solidifer.name=Solidifer +block.solar-panel.name=Solar Panel +block.solar-panel-large.name=Large Solar Panel +block.oil-extractor.name=Oil Extractor +block.javelin-ship-factory.name=Javelin Ship factory +block.drone-factory.name=Drone Factory +block.fabricator-factory.name=Fabricator Factory +block.repair-point.name=Repair Point +block.resupply-point.name=Resupply Point +block.pulse-conduit.name=Pulse Conduit +block.phase-conduit.name=Phase Conduit +block.liquid-router.name=Liquid Router +block.liquid-tank.name=Liquid Tank +block.liquid-junction.name=Liquid Junction +block.bridge-conduit.name=Bridge Conduit +block.rotary-pump.name=Rotary Pump +block.nuclear-reactor.name=Nuclear Reactor diff --git a/core/assets/bundles/bundle_in_ID.properties b/core/assets/bundles/bundle_in_ID.properties index 569b89321b..963c0f0eab 100644 --- a/core/assets/bundles/bundle_in_ID.properties +++ b/core/assets/bundles/bundle_in_ID.properties @@ -1,491 +1,515 @@ -text.about = Dibuat oleh [ROYAL]Anuken.[]\nAwalnya masuk di [orange]GDL[] MM Jam.\n\nKredit:\n- SFX dibuat dengan [YELLOW]bfxr[]\n- Musik dibuat oleh [GREEN]RoccoW[] / ditemukan di [lime]FreeMusicArchive.org[]\n\nTerima kasih khusus kepada:\n- [coral]MitchellFJN[]: playtesting dan umpan balik yang luas\n- [sky]Luxray5474[]: pekerjaan wiki, kontribusi kode\n- Semua penguji beta di itch.io dan Google Play\n -text.discord = Bergabunglah dengan Discord Mindustry! -text.gameover = Intinya hancur. -text.highscore = [YELLOW]Rekor baru! -text.lasted = Anda bertahan sampai gelombang -text.level.highscore = Skor Tinggi: [accent]{0} -text.level.delete.title = Konfirmasi Hapus -text.level.delete = Yakin ingin menghapus peta \"[orange]{0}\"? -text.level.select = Pilih Level -text.level.mode = Modus permainan: -text.savegame = Simpan Permainan -text.loadgame = Lanjutkan -text.joingame = Bermain Bersama -text.quit = Keluar -text.about.button = Tentang -text.name = Nama: -text.public = Publik -text.players = {0} pemain online -text.server.player.host = {0} (host) -text.players.single = {0} pemain online -text.server.mismatch = Kesalahan paket: kemungkinan versi client / server tidak sesuai.\nPastikan Anda dan host memiliki versi terbaru Mindustry! -text.server.closing = [accent]Menutup server... -text.server.kicked.kick = Anda telah dikeluarkan dari server! -text.server.kicked.invalidPassword = Kata sandi salah! -text.server.kicked.clientOutdated = Client versi lama! Update game Anda! -text.server.kicked.serverOutdated = Server versi lama! Tanyakan host untuk mengupdate! -text.server.connected = {0} telah bergabung. -text.server.disconnected = {0} telah terputus. -text.nohost = Tidak dapat meng-host server pada peta khusus! -text.hostserver = Host Server -text.host = Host -text.hosting = [accent]Membuka server... -text.hosts.refresh = Segarkan -text.hosts.discovering = Mencari game LAN -text.server.refreshing = Menyegarkan server -text.hosts.none = [lightgray]Tidak ada game LAN yang ditemukan! -text.host.invalid = [scarlet]Tidak dapat terhubung ke host. -text.server.friendlyfire = Tembak Sesama -text.server.add = Tambahkan Server -text.server.delete = Yakin ingin menghapus server ini? -text.server.hostname = Host: {0} -text.server.edit = Sunting Server -text.joingame.byip = Bergabung dengan IP... -text.joingame.title = Bermain Bersama -text.joingame.ip = IP: -text.disconnect = Sambungan terputus. -text.connecting = [accent]Menghubungkan... -text.connecting.data = [accent]Memuat data level... -text.connectfail = [crimson]Gagal terhubung ke server: [orange]{0} -text.server.port = Port: -text.server.addressinuse = Alamat sudah di pakai! -text.server.invalidport = Nomor port salah! -text.server.error = [crimson]Kesalahan server hosting: [orange]{0} -text.tutorial.back = < Sebelumnya -text.tutorial.next = Berikutnya > -text.save.new = Simpan Baru -text.save.overwrite = Yakin ingin mengganti slot simpan ini? -text.overwrite = Ganti -text.save.none = Tidak ada simpanan ditemukan! -text.saveload = [accent]Menyimpan... -text.savefail = Gagal menyimpan game! -text.save.delete.confirm = Yakin ingin menghapus save ini? -text.save.delete = Hapus -text.save.export = Ekspor Simpanan -text.save.import.invalid = [orange]Simpanan ini tidak valid! -text.save.import.fail = [crimson]Gagal mengimpor: [orange]{0} -text.save.export.fail = [crimson]Gagal mengekspor save: [orange]{0} -text.save.import = Impor Simpanan -text.save.newslot = Nama simpanan: -text.save.rename = Ganti nama -text.save.rename.text = Nama baru: -text.selectslot = Pilih simpanan. -text.slot = [accent]Slot{0} -text.save.corrupted = [orange]Simpanan rusak atau tidak valid! -text.empty = -text.on = Hidup -text.off = Mati -text.save.autosave = Simpan otomatis: {0} -text.save.map = Peta: {0} -text.save.wave = Gelombang {0} -text.save.date = Terakhir Disimpan: {0} -text.confirm = Konfirmasi -text.delete = Hapus -text.ok = OK -text.open = Buka -text.cancel = Batal -text.openlink = Buka tautan -text.back = Kembali -text.quit.confirm = Anda yakin ingin berhenti? -text.loading = [accent]Memuat... -text.wave = [orange]Gelombang {0} -text.wave.waiting = Gelombang dimulai {0} -text.waiting = Menunggu... -text.enemies = {0} musuh -text.enemies.single = {0} Musuh -text.loadimage = Buka Gambar -text.saveimage = Simpan Gambar -text.oregen = Generator Bijih -text.editor.badsize = [orange]Dimensi gambar tidak valid![]\nDimensi peta yang valid: {0} -text.editor.errorimageload = Kesalahan saat memuat file gambar:\n[orange]{0} -text.editor.errorimagesave = Kesalahan saat menyimpan file gambar:\n[orange]{0} -text.editor.generate = Hasilkan -text.editor.resize = Ubah ukuran -text.editor.loadmap = Buka Peta -text.editor.savemap = Simpan Peta -text.editor.loadimage = Buka Gambar -text.editor.saveimage = Simpan Gambar -text.editor.unsaved = [scarlet]Anda memiliki perubahan yang belum disimpan![]\nYakin ingin keluar? -text.editor.brushsize = Ukuran sikat: {0} -text.editor.noplayerspawn = Peta ini tidak memiliki spawnpoint pemain! -text.editor.manyplayerspawns = Peta tidak bisa memiliki lebih dari satu\nspawnpoint pemain! -text.editor.manyenemyspawns = Tidak dapat memiliki lebih dari\n{0} spawnpoint musuh! -text.editor.resizemap = Ubah ukuran peta -text.editor.resizebig = [scarlet]Peringatan!\n[]Peta yang lebih besar dari 256 unit mungkin nge-lag dan tidak stabil. -text.editor.mapname = Nama Peta: -text.editor.overwrite = [accent]Peringatan!\nIni akan mengganti peta yang ada. -text.editor.failoverwrite = [crimson]Tidak dapat mengganti peta default! -text.editor.selectmap = Pilih peta yang akan dimuat: -text.width = Lebar: -text.height = Tinggi: -text.randomize = Acak -text.apply = Terapkan -text.update = Perbarui -text.menu = Menu -text.play = Main -text.load = Buka -text.save = Simpan -text.language.restart = Silakan mulai ulang permainan Anda agar pengaturan bahasa mulai berlaku. -text.settings.language = Bahasa -text.settings = Pengaturan -text.tutorial = Tutorial -text.editor = Pengedit -text.mapeditor = Pengedit Peta -text.donate = Sumbangkan -text.settings.reset = Atur ulang ke Default -text.settings.controls = Kontrol -text.settings.game = Permainan -text.settings.sound = Suara -text.settings.graphics = Grafis -text.upgrades = Perbaruan -text.purchased = [LIME]Dibuat! -text.weapons = Senjata -text.paused = Jeda -text.respawn = Respawning dalam -text.info.title = [accent]Info -text.error.title = [crimson]Telah terjadi kesalahan -text.error.crashmessage = [SCARLET]Kesalahan tak terduga telah terjadi, yang menyebabkan kerusakan.\n[]Tolong laporkan keadaan yang tepat dimana kesalahan ini terjadi pada pengembang:\n[ORANGE] anukendev@gmail.com[] -text.error.crashtitle = Telah terjadi kesalahan -text.mode.break = Mode penghancur: {0} -text.mode.place = Mode penaruh: {0} -placemode.hold.name = garis -placemode.areadelete.name = area -placemode.touchdelete.name = sentuh -placemode.holddelete.name = tahan -placemode.none.name = tidak ada -placemode.touch.name = sentuh -placemode.cursor.name = kursor -text.blocks.extrainfo = [accent]info tambahan blok: -text.blocks.blockinfo = Info Blok -text.blocks.powercapacity = Kapasitas Tenaga -text.blocks.powershot = Tenaga/tembakan -text.blocks.powersecond = Tenaga/detik -text.blocks.powerdraindamage = Tenaga Dipakai/damage -text.blocks.shieldradius = Radius Perisai -text.blocks.itemspeedsecond = Kecepatan Barang/detik -text.blocks.range = Jangkauan -text.blocks.size = Ukuran -text.blocks.powerliquid = Tenaga/Cairan -text.blocks.maxliquidsecond = Batas cairan/detik -text.blocks.liquidcapacity = Kapasitas cairan -text.blocks.liquidsecond = Cairan/detik -text.blocks.damageshot = Damage/tembakan -text.blocks.ammocapacity = Kapasitas Amunisi -text.blocks.ammo = Amunisi -text.blocks.ammoitem = Amunisi/barang -text.blocks.maxitemssecond = Batas barang/detik -text.blocks.powerrange = Jangkauan tenaga -text.blocks.lasertilerange = Kotak jangkauan laser -text.blocks.capacity = Kapasitas -text.blocks.itemcapacity = Kapasitas Barang -text.blocks.maxpowergenerationsecond = Batas Penghasil Tenaga/detik -text.blocks.powergenerationsecond = Penghasil Tenaga/detik -text.blocks.generationsecondsitem = Waktu Penghasil (detik)/barang -text.blocks.input = Masukan -text.blocks.inputliquid = Cairan yang Masuk -text.blocks.inputitem = Barang yang Masuk -text.blocks.output = Keluar -text.blocks.secondsitem = Detik/barang -text.blocks.maxpowertransfersecond = Batas transfer tenaga/detik -text.blocks.explosive = Mudah meledak! -text.blocks.repairssecond = Perbaikan/detik -text.blocks.health = Darah -text.blocks.inaccuracy = Ketidaktelitian -text.blocks.shots = Tembakan -text.blocks.shotssecond = Tembakan/detik -text.blocks.fuel = Bahan Bakar -text.blocks.fuelduration = Durasi Bahan Bakar -text.blocks.maxoutputsecond = Batas keluar/detik -text.blocks.inputcapacity = Kapasitas masuk -text.blocks.outputcapacity = Kapasitas keluar -text.blocks.poweritem = Tenaga/barang -text.placemode = Mode Penempatan -text.breakmode = Mode Penghancur -text.health = darah -setting.difficulty.easy = mudah -setting.difficulty.normal = normal -setting.difficulty.hard = sulit -setting.difficulty.insane = sangat susah -setting.difficulty.purge = paling susah -setting.difficulty.name = Kesulitan: -setting.screenshake.name = Layar Bergoyang -setting.smoothcam.name = Kamera Halus -setting.indicators.name = Indikator Musuh -setting.effects.name = Efek Tampilan -setting.sensitivity.name = Sensitivitas Pengendali -setting.saveinterval.name = Waktu Simpan Otomatis -setting.seconds = {0} Detik -setting.fullscreen.name = Layar Penuh -setting.fps.name = Tunjukkan FPS -setting.vsync.name = VSync -setting.lasers.name = Tampilkan Laser Tenaga -setting.healthbars.name = Tampilkan Bar Darah Entitas -setting.pixelate.name = Layar Pixel -setting.musicvol.name = Volume Musik -setting.mutemusic.name = Bisukan Musik -setting.sfxvol.name = Volume Suara -setting.mutesound.name = Bisukan Suara -map.maze.name = labirin -map.fortress.name = benteng -map.sinkhole.name = lubang pembuangan -map.caves.name = gua -map.volcano.name = gunung berapi -map.caldera.name = kaldera -map.scorch.name = penghangusan -map.desert.name = gurun -map.island.name = pulau -map.grassland.name = padang rumput -map.tundra.name = tundra -map.spiral.name = spiral -map.tutorial.name = tutorial -tutorial.intro.text = [yellow]Selamat datang di tutorial.[] Untuk memulai, tekan 'berikutnya'. -tutorial.moveDesktop.text = Untuk bergerak, gunakan tombol [orange][[WASD][]. Tahan tombol [orange]shift[] untuk mempercepat. Tahan [orange]CTRL[] saat menggunakan [orange]scrollwheel[] untuk memperbesar atau memperkecil tampilan. -tutorial.shoot.text = Gunakan mouse anda untuk mengarahkan, tahan [orange]tombol kiri mouse[] untuk menembak. Cobalah menembaki [yellow]target[]. -tutorial.moveAndroid.text = Untuk menggeser tampilan, seret satu jari ke layar. Jepit dan seret untuk memperbesar atau memperkecil tampilan. -tutorial.placeSelect.text = Coba pilih [yellow]konveyor[] dari menu blok di kanan bawah. -tutorial.placeConveyorDesktop.text = Gunakan [orange][[scrollwheel][] untuk memutar konveyor menghadap [orange]ke depan[], lalu letakkan di [yellow]lokasi yang ditandai[] menggunakan [orange][[tombol kiri mouse]][]. -tutorial.placeConveyorAndroid.text = Gunakan [orange][[tombol putar]][] untuk memutar konveyor menghadap [orange]ke depan[], seret ke posisi dengan satu jari, lalu letakkan di [yellow]lokasi yang ditandai[] dengan menggunakan [orange][[tanda centang][]. -tutorial.placeConveyorAndroidInfo.text = Sebagai alternatif, Anda dapat menekan ikon crosshair di kiri bawah untuk beralih ke [orange][[mode sentuh]][], dan letakkan blok dengan mengetuk layar. Dalam mode sentuh, blok bisa diputar dengan panah di kiri bawah. Tekan [yellow]berikutnya[] untuk mencobanya. -tutorial.placeDrill.text = Sekarang, pilih dan tempatkan [yellow]pertambangan battu[] di lokasi yang ditandai. -tutorial.blockInfo.text = Jika Anda ingin mempelajari lebih lanjut tentang blok, Anda dapat menekan [orange]tanda tanya[] di bagian kanan atas untuk membaca deskripsinya. -tutorial.deselectDesktop.text = Anda bisa membatalkan pemilihan blok menggunakan [orange][[tombol mouse kanan][]. -tutorial.deselectAndroid.text = Anda dapat membatalkan pemilihan blok dengan menekan tombol [orange]X (silang)[]. -tutorial.drillPlaced.text = Pertambangannya sekarang akan menghasilkan [yellow]batu[] yang dikeluarkan ke konveyor, lalu memindahkannya ke [yellow]intinya[]. -tutorial.drillInfo.text = Bijih yang berbeda membutuhkan pertambangan yang berbeda. Batu membutuhkan pertambangan batu, besi membutuhkan pertambangan besi, dll. -tutorial.drillPlaced2.text = Memindahkan barang ke dalam inti menempatkannya di [yellow]inventaris barang[] Anda, di kiri atas. Menempatkan blok menggunakan barang dari inventaris Anda. -tutorial.moreDrills.text = Anda bisa menghubungkan banyak pertambangan dan konveyor bersama-sama, seperti biasa. -tutorial.deleteBlock.text = Anda dapat menghapus blok dengan mengeklik [orange]tombol mouse kanan[] di blok yang ingin Anda hapus. Coba hapus konveyor ini. -tutorial.deleteBlockAndroid.text = Anda dapat menghapus blok dengan [orange]memilih crosshair[] di [orange]menu mode penghancur[] di kiri bawah dan mengetuk bloknya. Coba hapus konveyor ini. -tutorial.placeTurret.text = Sekarang, pilih dan tempatkan [yellow]turret[] di [yellow]lokasi yang ditandai[]. -tutorial.placedTurretAmmo.text = Turret ini sekarang akan menerima [yellow]amunisi[] dari konveyor. Anda dapat melihat berapa banyak amunisi yang dimiliki dengan menggeser kursor di bloknya dan memeriksa di [green]bilah hijau[]. -tutorial.turretExplanation.text = Turret secara otomatis akan menembak musuh terdekat dalam jangkauan, selama mereka memiliki cukup amunisi. -tutorial.waves.text = Setiap [yellow]60[] detik, gelombang [coral]musuh[] akan muncul di lokasi tertentu dan berusaha menghancurkan intinya. -tutorial.coreDestruction.text = Tujuan Anda adalah untuk [yellow]mempertahankan intinya[]. Jika intinya hancur, Anda [coral]kalah dalam permainan[]. -tutorial.pausingDesktop.text = Jika Anda perlu istirahat sebentar, tekan [orange]tombol jeda[] di bagian kiri atas atau [orange]tombol spasi[] untuk menghentikan sementara permainan. Anda masih bisa memilih dan menempatkan blok sambil berhenti, tapi tidak bisa bergerak atau menembak. -tutorial.pausingAndroid.text = Jika Anda perlu istirahat sebentar, tekan [orange]tombol jeda[] di kiri atas untuk menjeda permainan. Anda masih bisa menghapus dan menempatkan blok sambil berhenti sebentar. -tutorial.purchaseWeapons.text = Anda bisa membeli [yellow]senjata baru[] untuk robot Anda dengan membuka menu upgrade di kiri bawah. -tutorial.switchWeapons.text = Untuk mengganti senjata, klik ikonnya di kiri bawah, atau gunakan angka [orange][[1-9][]. -tutorial.spawnWave.text = Gelombang sekarang datang. Hancurkan mereka. -tutorial.pumpDesc.text = Pada gelombang selanjutnya, Anda mungkin perlu menggunakan [yellow]pompa[] untuk mendistribusikan cairan untuk generator atau ekstraktor. -tutorial.pumpPlace.text = Pompa bekerja seperti dengan pertambangan, namun mereka menghasilkan cairan dan bukan barang. Cobalah menempatkan pompa pada [yellow]minyak yang ditunjuk[]. -tutorial.conduitUse.text = Sekarang tempatkan [orange]saluran[] yang mengarah jauh dari pompa. -tutorial.conduitUse2.text = Dan beberapa lagi... -tutorial.conduitUse3.text = Dan beberapa lagi... -tutorial.generator.text = Sekarang, tempatkan [orange]blok generator pembakaran[] di ujung saluran. -tutorial.generatorExplain.text = Generator ini sekarang akan menciptakan [yellow]tenaga[] dari minyak. -tutorial.lasers.text = Tenaga didistribusikan menggunakan [yellow]laser tenaga[]. Putar dan tempatkan di sini. -tutorial.laserExplain.text = Generator sekarang akan memindahkan tenaga ke blok laser. Sinar [yellow]terang[] menandakan bahwa saat ini mentransmisikan tenaga, dan sinar [yellow]transparan[] berarti tidak. -tutorial.laserMore.text = Anda dapat memeriksa berapa banyak tenaga yang dimiliki blok dengan memindahkan kursor/mengetuk di atasnya dan memeriksa [yellow]bar kuning[] di bagian atas. -tutorial.healingTurret.text = Laser ini bisa digunakan untuk menyalakan [lime]turret perbaikan[]. Tempatkan satu di sini. -tutorial.healingTurretExplain.text = Selama memiliki tenaga, turret ini akan [lime]memperbaiki blok terdekat[]. Saat bermain, pastikan Anda memasukkannya ke markas Anda secepat mungkin! -tutorial.smeltery.text = Banyak blok yang membutuhkan [orange]baja[] agar dapat dibangun, yang membutuhkan [orange]peleburan[] untuk dibuat. Tempatkan satu di sini. -tutorial.smelterySetup.text = Peleburan ini sekarang akan menghasilkan [orange]baja[] dari besi yang masuk, dengan batubara sebagai bahan bakarnya. -tutorial.tunnelExplain.text = Perhatikan juga bahwa barang-barang itu masuk melalui [orange]blok terowongan[] dan muncul di sisi lain, melewati blok batu. Perlu diingat bahwa terowongan hanya bisa melalui sampai 2 blok. -tutorial.end.text = Dan itu menyimpulkan tutorialnya! Semoga berhasil! -keybind.move_x.name = gerak_x -keybind.move_y.name = gerak_y -keybind.select.name = pilih -keybind.break.name = hapus -keybind.shoot.name = tembak -keybind.zoom_hold.name = perbesar_tahan -keybind.zoom.name = perbesar -keybind.menu.name = menu -keybind.pause.name = jeda -keybind.dash.name = berlari -keybind.rotate_alt.name = putar_alt -keybind.rotate.name = putar -keybind.weapon_1.name = senjata_1 -keybind.weapon_2.name = senjata_2 -keybind.weapon_3.name = senjata_3 -keybind.weapon_4.name = senjata_4 -keybind.weapon_5.name = senjata_5 -keybind.weapon_6.name = senjata_6 -mode.waves.name = gelombang -mode.sandbox.name = sandbox -mode.freebuild.name = freebuild -upgrade.standard.name = standar -upgrade.standard.description = Robot standar. -upgrade.blaster.name = blaster -upgrade.blaster.description = Menembakan sebuah peluru yang lemah dan lambat. -upgrade.triblaster.name = triblaster -upgrade.triblaster.description = Menembakan 3 peluru secara menyebar. -upgrade.clustergun.name = clustergun -upgrade.clustergun.description = Menembakan sebuah granat eksplosif yang tidak akurat. -upgrade.beam.name = meriam sinar -upgrade.beam.description = Menembakan sinar laser jarak jauh. -upgrade.vulcan.name = vulcan -upgrade.vulcan.description = Menembakkan rombongan peluru dengan cepat. -upgrade.shockgun.name = shockgun -upgrade.shockgun.description = Menembakkan ledakan yang menghancurkan dari pecahan peluru yang terisi. -item.stone.name = batu -item.iron.name = besi -item.coal.name = batu bara -item.steel.name = baja -item.titanium.name = titanium -item.dirium.name = dirium -item.thorium.name = thorium -item.sand.name = pasir -liquid.water.name = air -liquid.plasma.name = plasma -liquid.lava.name = lahar -liquid.oil.name = minyak -block.weaponfactory.name = pabrik senjata -block.weaponfactory.fulldescription = Dipakai untuk membuat senjata bagi robot pemain. Klik untuk memakai. Otomatis mengambil sumber daya dari inti. -block.air.name = udara -block.blockpart.name = bagian blok -block.deepwater.name = air dangkal -block.water.name = air -block.lava.name = lahar -block.oil.name = minyak -block.stone.name = batu -block.blackstone.name = batu hitam -block.iron.name = besi -block.coal.name = batu bara -block.titanium.name = titanium -block.thorium.name = thorium -block.dirt.name = tanah -block.sand.name = pasir -block.ice.name = es -block.snow.name = salju -block.grass.name = rumput -block.sandblock.name = blok pasir -block.snowblock.name = blok salju -block.stoneblock.name = blok batu -block.blackstoneblock.name = blok batu hitam -block.grassblock.name = blok rumput -block.mossblock.name = blok lumut -block.shrub.name = belukar -block.rock.name = batu -block.icerock.name = batu es -block.blackrock.name = batu hitam -block.dirtblock.name = blok tanah -block.stonewall.name = dinding batu -block.stonewall.fulldescription = Sebuah blok defensif yang murah. Berguna untuk melindungi inti dan turret di beberapa gelombang pertama. -block.ironwall.name = dinding besi -block.ironwall.fulldescription = Blok defensif dasar. Menyediakan perlindungan dari musuh. -block.steelwall.name = dinding baja -block.steelwall.fulldescription = Sebuah blok defensif standar. Perlindungan yang memadai dari musuh. -block.titaniumwall.name = dinding titanium -block.titaniumwall.fulldescription = Blok pertahanan yang kuat. Menyediakan perlindungan dari musuh. -block.duriumwall.name = dinding dirium -block.duriumwall.fulldescription = Blok pertahanan yang sangat kuat. Menyediakan perlindungan dari musuh. -block.compositewall.name = dinding komposit -block.steelwall-large.name = dinding baja besar -block.steelwall-large.fulldescription = Sebuah blok defensif standar. Membentang beberapa ubin. -block.titaniumwall-large.name = dinding titanium besar -block.titaniumwall-large.fulldescription = Blok pertahanan yang kuat. Membentang beberapa ubin. -block.duriumwall-large.name = dinding dirium yang besar -block.duriumwall-large.fulldescription = Blok pertahanan yang sangat kuat. Membentang beberapa ubin. -block.titaniumshieldwall.name = dinding perisai -block.titaniumshieldwall.fulldescription = Sebuah blok defensif yang kuat, dengan tambahan perisai. Membutuhkan tenaga. Menggunakan energi untuk menyerap peluru musuh. Dianjurkan untuk menggunakan pemercepat tenaga untuk memberi energi pada blok ini. -block.repairturret.name = turret perbaikan -block.repairturret.fulldescription = Memperbaiki blok terdekat yang rusak dengan lambat. Menggunakan sedikit tenaga. -block.megarepairturret.name = perbaikan turret II -block.megarepairturret.fulldescription = Memperbaiki blok yang rusak dengan normal. Menggunakan tenaga. -block.shieldgenerator.name = pembangkit perisai -block.shieldgenerator.fulldescription = Blok defensif yang maju. Mellindungi semua blok dalam radius dari serangan. Menggunakan tenaga dengan lambat saat menganggur, namun menyalurkan energi dengan cepat pada kontak peluru. -block.door.name = pintu -block.door.fulldescription = Blok yang bisa dibuka dan ditutup dengan mengetuknya. -block.door-large.name = pintu besar -block.door-large.fulldescription = Blok yang bisa dibuka dan ditutup dengan mengetuknya. -block.conduit.name = saluran -block.conduit.fulldescription = Blok pengangkut cairan dasar. Bekerja seperti konveyor, tapi dengan cairan. Terbaik digunakan dengan pompa atau saluran lainnya. Bisa digunakan sebagai jembatan di atas cairan untuk musuh dan pemain. -block.pulseconduit.name = saluran cepat -block.pulseconduit.fulldescription = Blok pengangkut cairan tingkat lanjut. Mengangkut cairan lebih cepat dan menyimpan lebih banyak dari pada saluran standar. -block.liquidrouter.name = router cairan -block.liquidrouter.fulldescription = Bekerja seperti router. Menerima masukan cairan dari satu sisi dan mengeluarkannya ke sisi yang lain. Berguna untuk pemisahan cairan dari satu saluran ke beberapa saluran lainnya. -block.conveyor.name = konveyor -block.conveyor.fulldescription = Blok dasar pengangkut barang. Memindahkan barang ke depan dan secara otomatis menyimpannya ke turret, ekstraktor, dan pertambangan. Bisa diputar. Bisa digunakan sebagai jembatan di atas cairan untuk musuh dan pemain. -block.steelconveyor.name = konveyor baja -block.steelconveyor.fulldescription = Blok transportasi barang lanjutan. Memindahkan barang lebih cepat dari konveyor standar. -block.poweredconveyor.name = konveyor cepat -block.poweredconveyor.fulldescription = Blok terbaik untuk pengangkutan barang. Memindahkan barang lebih cepat dari konveyor baja. -block.router.name = router -block.router.fulldescription = Menerima item dari satu arah dan mengeluarkannya ke 3 arah. Bisa juga menyimpan sejumlah barang. Berguna untuk membelah bahan dari satu pertambangan ke beberapa turret. -block.junction.name = persimpangan jalan -block.junction.fulldescription = Bertindak sebagai jembatan untuk dua sabuk persimpangan. Berguna dalam situasi dengan dua konveyor berbeda yang membawa bahan berbeda ke lokasi yang berbeda. -block.conveyortunnel.name = terowongan konveyor -block.conveyortunnel.fulldescription = Memindahkan barang di bawah blok. Untuk menggunakan, tempatkan satu terowongan yang menuju ke terowongan di bawah blok, dan satu di sisi lain. Pastikan kedua terowongan menghadap ke arah yang berlawanan, yaitu menuju blok yang mereka masukkan atau keluarkan. -block.liquidjunction.name = persimpangan cairan -block.liquidjunction.fulldescription = Bertindak sebagai jembatan untuk dua saluran persimpangan. Berguna dalam situasi dengan dua saluran berbeda yang membawa cairan berbeda ke lokasi yang berbeda. -block.liquiditemjunction.name = persimpangan barang-cairan -block.liquiditemjunction.fulldescription = Bertindak sebagai jembatan untuk menyilang saluran dan konveyor. -block.powerbooster.name = pemercepat tenaga -block.powerbooster.fulldescription = Mendistribusikan tenaga ke semua blok dalam radiusnya. -block.powerlaser.name = laser tenaga -block.powerlaser.fulldescription = Membuat laser yang mentransmisikan daya ke blok di depannya. Tidak menghasilkan tenaga itu sendiri. Terbaik digunakan dengan generator atau laser lainnya. -block.powerlaserrouter.name = router laser -block.powerlaserrouter.fulldescription = Laser yang mendistribusikan tenaga ke tiga arah sekaligus. Berguna dalam situasi di mana diperlukan tenaga ke beberapa blok dari satu generator. -block.powerlasercorner.name = sudut laser -block.powerlasercorner.fulldescription = Laser yang mendistribusikan tenaga ke dua arah sekaligus. Berguna dalam situasi di mana diperlukan tenaga ke beberapa blok dari satu generator, dan arah router kurang tepat. -block.teleporter.name = teleporter -block.teleporter.fulldescription = Blok transportasi barang lanjutan. Teleporter memasukkan barang ke teleporter lain dengan warna yang sama. Tidak ada apa-apa jika tidak ada teleporter dengan warna yang sama. Jika beberapa teleporter memiliki warna yang sama, teleporter dipilih secara acak. Menggunakan tenaga. Ketuk/klik untuk mengubah warna. -block.sorter.name = penyortir -block.sorter.fulldescription = Menyortir barang menurut jenis bahannya. Bahan yang diterima ditandai dengan warna di blok. Semua item yang sesuai dengan jenis bahan dilepaskan ke depan, segala sesuatu yang lain dikeluarkan ke kiri dan kanan. -block.core.name = inti -block.pump.name = pompa -block.pump.fulldescription = Memompa cairan dari sumber blok- biasanya air, lahar atau minyak. Mengeluarkan cairan ke saluran terdekat. -block.fluxpump.name = pompa flux -block.fluxpump.fulldescription = Sebuah versi lanjutan dari pompa. Menyimpan lebih banyak cairan dan memompa cairan lebih cepat. -block.smelter.name = peleburan -block.smelter.fulldescription = Blok kerajinan esensial. Saat dimasukkan 1 besi dan 1 batu bara sebagai bahan bakar, akan mengeluarkan satu baja. Disarankan untuk memasukkan besi dan batu bara ke sabuk yang berbeda untuk mencegah penyumbatan. -block.crucible.name = peleburan dirium -block.crucible.fulldescription = Sebuah blok kerajinan yang maju. Saat dimasukkan 1 titanium, 1 baja dan 1 batu bara sebagai bahan bakar, mengeluarkan satu dirium. Disarankan untuk memasukkan batubara, baja dan titanium pada sabuk yang berbeda untuk mencegah penyumbatan. -block.coalpurifier.name = ekstraktor batubara -block.coalpurifier.fulldescription = Blok ekstraktor dasar. mengeluarkan batu bara saat dipasok dengan air dan batu dalam skala yang besar. -block.titaniumpurifier.name = ekstraktor titanium -block.titaniumpurifier.fulldescription = Blok ekstraktor standar. mengeluarkan titanium bila dipasok dengan air dan besi dalam skala yang besar. -block.oilrefinery.name = penyulingan minyak -block.oilrefinery.fulldescription = Menyuling sejumlah minyak menjadi batubara. Berguna untuk memasok turret berbasis batubara saat penambangan batubara langka. -block.stoneformer.name = pembentuk batu -block.stoneformer.fulldescription = Mengubah lahar ke dalam batu. Berguna untuk menghasilkan batu dalam jumlah besar untuk pemurni batu bara. -block.lavasmelter.name = peleburan lava -block.lavasmelter.fulldescription = Menggunakan lahar untuk mengubah besi menjadi baja. Sebuah alternatif untuk peleburan batubara. Berguna dalam situasi di mana pertambangan batubara langka. -block.stonedrill.name = pertambangan batu -block.stonedrill.fulldescription = Pertambangan penting. Saat diletakkan di atas ubin batu, akan menghasilkan batu pada kecepatan yang lambat tanpa batas waktu. -block.irondrill.name = pertambangan besi -block.irondrill.fulldescription = Pertambangan dasar. Saat diletakkan di atas ubin bijih besi, akan mengeluarkan besi pada kecepatan yang lambat tanpa batas waktu. -block.coaldrill.name = pertambangan batubara -block.coaldrill.fulldescription = Pertambangan dasar. Saat ditempatkan di ubin bijih batubara, akan mengeluarkan batu bara pada kecepatan yang lambat tanpa batas waktu. -block.thoriumdrill.name = pertambangan thorium -block.thoriumdrill.fulldescription = Sebuah pertambangan yang canggih. Saat ditempatkan di ubin bijih thorium, akan mengeluarkan thorium pada kecepatan lambat tanpa batas waktu. -block.titaniumdrill.name = pertambangan titanium -block.titaniumdrill.fulldescription = Sebuah pertambangan yang canggih. Saat ditempatkan pada ubin bijih titanium, akan mengeluarkan titanium pada kecepatan lambat tanpa batas waktu. -block.omnidrill.name = pertambangan super -block.omnidrill.fulldescription = Pertambangan yang terbaik. Akan saya tambang bijih apapun itu ditempatkan pada kecepatan tinggi. -block.coalgenerator.name = pembangkit tenaga batubara -block.coalgenerator.fulldescription = Generator penting. Menghasilkan tenaga dari batu bara. Keluarkan tenaga sebagai laser ke 4 sisinya. -block.thermalgenerator.name = pembangkit tenaga panas -block.thermalgenerator.fulldescription = Menghasilkan tenaga dari lahar. Mengeluarkan tenaga sebagai laser ke 4 sisi. -block.combustiongenerator.name = pembangkit tenaga minyak -block.combustiongenerator.fulldescription = Menghasilkan tenaga dari minyak. Mengeluarkan tenaga sebagai laser ke 4 sisi. -block.rtgenerator.name = pembangkit tenaga radioaktif -block.rtgenerator.fulldescription = Menghasilkan sedikit tenaga dari peluruhan radioaktif thorium. Mengeluarkan tenaga sebagai laser ke 4 sisi. -block.nuclearreactor.name = reaktor nuklir -block.nuclearreactor.fulldescription = Versi lanjutan Pembangkit Tenaga Radioaktif, dan generator tenaga tertinggi. Menghasilkan tenaga dari thorium. Membutuhkan pendinginan air konstan. Sangat mudah menguap; akan meledak dengan hebat jika tidak cukup jumlah pendingin yang diberikan. -block.turret.name = turret -block.turret.fulldescription = Sebuah menara dasar yang murah. Menggunakan batu untuk amunisi. Memiliki jangkauan yang sedikit lebih banyak daripada turret ganda. -block.doubleturret.name = turret ganda -block.doubleturret.fulldescription = Versi turret standar yang sedikit lebih bertenaga. Menggunakan batu untuk amunisi. Memberikan damage secara signifikan lebih banyak, namun memiliki jangkauan yang lebih rendah. Menembak dua peluru. -block.machineturret.name = turret cepat -block.machineturret.fulldescription = Sebuah menara standar. Menggunakan besi untuk amunisi. Memiliki tembakan yang cepat dengan damage yang layak. -block.shotgunturret.name = turret split -block.shotgunturret.fulldescription = Sebuah turret standar. Menggunakan besi untuk amunisi. Menembakkan 7 peluru. Jaraknya pendek, namun damage-nya lebih tinggi daripada turret cepat. -block.flameturret.name = turret api -block.flameturret.fulldescription = Turret jarak dekat lanjutan. Menggunakan batubara untuk amunisi. Memiliki jangkauan yang pendek, namun sangat tinggi damage-nya. Bagus untuk jarak dekat. Dianjurkan untuk digunakan dibalik dinding. -block.sniperturret.name = turret railgun -block.sniperturret.fulldescription = Turret jarak jauh lanjutan. Menggunakan baja untuk amunisi. Kerusakan yang sangat tinggi, namun menembak dengan lambat. Mahal untuk digunakan, tapi bisa ditempatkan jauh dari garis musuh karena jangkauannya. -block.mortarturret.name = turret flak -block.mortarturret.fulldescription = Turret dengan akurasi pendek dan damage eksplosif. Menggunakan batubara untuk amunisi. Menembakkan peluru yang meledak lalu menjadi pecahan peluru. Berguna untuk kerumunan musuh yang besar. -block.laserturret.name = turret laser -block.laserturret.fulldescription = Turret satu target. Menggunakan tenaga. Memiliki jarak sedang yang bagus. Target tunggal saja. Tidak pernah meleset. -block.waveturret.name = turret tesla -block.waveturret.fulldescription = Turret target banyak. Menggunakan tenaga. Jaraknya sedang. Tidak pernah meleset. Rata-rata damage-nya kecil, namun bisa menembak beberapa musuh bersamaan dengan petir berantai. -block.plasmaturret.name = turret plasma -block.plasmaturret.fulldescription = Versi yang sangat maju dari turret api. Menggunakan batubara sebagai amunisi. Damage yang sangat tinggi, jaraknya pendek sampai sedang. -block.chainturret.name = turret berantai -block.chainturret.fulldescription = Menara api yang menembak dengan cepat. Menggunakan thorium sebagai amunisi. Menembak peluru besar dengan kecepatan tinggi. Jaraknya sedang. Membentang beberapa ubin. Sangat tangguh. -block.titancannon.name = meriam titan -block.titancannon.fulldescription = Turret jarak jauh terakhir. Menggunakan thorium sebagai amunisi. Menembakkan peluru yang meledak dengan cipratan besar dengan kecepatan sedang. Jarak jauh. Membentang beberapa ubin. Sangat tangguh. -block.playerspawn.name = spawn pemain -block.enemyspawn.name = spawn musuh \ No newline at end of file +text.about=Dibuat oleh [ROYAL]Anuken.[]\nAwalnya masuk di [orange]GDL[] MM Jam.\n\nKredit:\n- SFX dibuat dengan [YELLOW]bfxr[]\n- Musik dibuat oleh [GREEN]RoccoW[] / ditemukan di [lime]FreeMusicArchive.org[]\n\nTerima kasih khusus kepada:\n- [coral]MitchellFJN[]: playtesting dan umpan balik yang luas\n- [sky]Luxray5474[]: pekerjaan wiki, kontribusi kode\n- Semua penguji beta di itch.io dan Google Play\n +text.discord=Bergabunglah dengan Discord Mindustry! +text.gameover=Intinya hancur. +text.highscore=[YELLOW]Rekor baru! +text.lasted=Anda bertahan sampai gelombang +text.level.highscore=Skor Tinggi: [accent]{0} +text.level.delete.title=Konfirmasi Hapus +text.level.select=Pilih Level +text.level.mode=Modus permainan: +text.savegame=Simpan Permainan +text.loadgame=Lanjutkan +text.joingame=Bermain Bersama +text.quit=Keluar +text.about.button=Tentang +text.name=Nama: +text.public=Publik +text.players={0} pemain online +text.server.player.host={0} (host) +text.players.single={0} pemain online +text.server.mismatch=Kesalahan paket: kemungkinan versi client / server tidak sesuai.\nPastikan Anda dan host memiliki versi terbaru Mindustry! +text.server.closing=[accent]Menutup server... +text.server.kicked.kick=Anda telah dikeluarkan dari server! +text.server.kicked.invalidPassword=Kata sandi salah! +text.server.kicked.clientOutdated=Client versi lama! Update game Anda! +text.server.kicked.serverOutdated=Server versi lama! Tanyakan host untuk mengupdate! +text.server.connected={0} telah bergabung. +text.server.disconnected={0} telah terputus. +text.nohost=Tidak dapat meng-host server pada peta khusus! +text.hostserver=Host Server +text.host=Host +text.hosting=[accent]Membuka server... +text.hosts.refresh=Segarkan +text.hosts.discovering=Mencari game LAN +text.server.refreshing=Menyegarkan server +text.hosts.none=[lightgray]Tidak ada game LAN yang ditemukan! +text.host.invalid=[scarlet]Tidak dapat terhubung ke host. +text.server.friendlyfire=Tembak Sesama +text.server.add=Tambahkan Server +text.server.delete=Yakin ingin menghapus server ini? +text.server.hostname=Host: {0} +text.server.edit=Sunting Server +text.joingame.byip=Bergabung dengan IP... +text.joingame.title=Bermain Bersama +text.joingame.ip=IP: +text.disconnect=Sambungan terputus. +text.connecting=[accent]Menghubungkan... +text.connecting.data=[accent]Memuat data level... +text.connectfail=[crimson]Gagal terhubung ke server: [orange]{0} +text.server.port=Port: +text.server.addressinuse=Alamat sudah di pakai! +text.server.invalidport=Nomor port salah! +text.server.error=[crimson]Kesalahan server hosting: [orange]{0} +text.tutorial.back=< Sebelumnya +text.tutorial.next=Berikutnya > +text.save.new=Simpan Baru +text.save.overwrite=Yakin ingin mengganti slot simpan ini? +text.overwrite=Ganti +text.save.none=Tidak ada simpanan ditemukan! +text.saveload=[accent]Menyimpan... +text.savefail=Gagal menyimpan game! +text.save.delete.confirm=Yakin ingin menghapus save ini? +text.save.delete=Hapus +text.save.export=Ekspor Simpanan +text.save.import.invalid=[orange]Simpanan ini tidak valid! +text.save.import.fail=[crimson]Gagal mengimpor: [orange]{0} +text.save.export.fail=[crimson]Gagal mengekspor save: [orange]{0} +text.save.import=Impor Simpanan +text.save.newslot=Nama simpanan: +text.save.rename=Ganti nama +text.save.rename.text=Nama baru: +text.selectslot=Pilih simpanan. +text.slot=[accent]Slot{0} +text.save.corrupted=[orange]Simpanan rusak atau tidak valid! +text.empty= +text.on=Hidup +text.off=Mati +text.save.autosave=Simpan otomatis: {0} +text.save.map=Peta: {0} +text.save.wave=Gelombang {0} +text.save.date=Terakhir Disimpan: {0} +text.confirm=Konfirmasi +text.delete=Hapus +text.ok=OK +text.open=Buka +text.cancel=Batal +text.openlink=Buka tautan +text.back=Kembali +text.quit.confirm=Anda yakin ingin berhenti? +text.loading=[accent]Memuat... +text.wave=[orange]Gelombang {0} +text.wave.waiting=Gelombang dimulai {0} +text.waiting=Menunggu... +text.enemies={0} musuh +text.enemies.single={0} Musuh +text.loadimage=Buka Gambar +text.saveimage=Simpan Gambar +text.editor.badsize=[orange]Dimensi gambar tidak valid![]\nDimensi peta yang valid: {0} +text.editor.errorimageload=Kesalahan saat memuat file gambar:\n[orange]{0} +text.editor.errorimagesave=Kesalahan saat menyimpan file gambar:\n[orange]{0} +text.editor.generate=Hasilkan +text.editor.resize=Ubah ukuran +text.editor.loadmap=Buka Peta +text.editor.savemap=Simpan Peta +text.editor.loadimage=Buka Gambar +text.editor.saveimage=Simpan Gambar +text.editor.unsaved=[scarlet]Anda memiliki perubahan yang belum disimpan![]\nYakin ingin keluar? +text.editor.brushsize=Ukuran sikat: {0} +text.editor.noplayerspawn=Peta ini tidak memiliki spawnpoint pemain! +text.editor.manyplayerspawns=Peta tidak bisa memiliki lebih dari satu\nspawnpoint pemain! +text.editor.manyenemyspawns=Tidak dapat memiliki lebih dari\n{0} spawnpoint musuh! +text.editor.resizemap=Ubah ukuran peta +text.editor.resizebig=[scarlet]Peringatan!\n[]Peta yang lebih besar dari 256 unit mungkin nge-lag dan tidak stabil. +text.editor.mapname=Nama Peta: +text.editor.overwrite=[accent]Peringatan!\nIni akan mengganti peta yang ada. +text.editor.selectmap=Pilih peta yang akan dimuat: +text.width=Lebar: +text.height=Tinggi: +text.randomize=Acak +text.apply=Terapkan +text.update=Perbarui +text.menu=Menu +text.play=Main +text.load=Buka +text.save=Simpan +text.language.restart=Silakan mulai ulang permainan Anda agar pengaturan bahasa mulai berlaku. +text.settings.language=Bahasa +text.settings=Pengaturan +text.tutorial=Tutorial +text.editor=Pengedit +text.mapeditor=Pengedit Peta +text.donate=Sumbangkan +text.settings.reset=Atur ulang ke Default +text.settings.controls=Kontrol +text.settings.game=Permainan +text.settings.sound=Suara +text.settings.graphics=Grafis +text.upgrades=Perbaruan +text.purchased=[LIME]Dibuat! +text.weapons=Senjata +text.paused=Jeda +text.info.title=[accent]Info +text.error.title=[crimson]Telah terjadi kesalahan +text.error.crashmessage=[SCARLET]Kesalahan tak terduga telah terjadi, yang menyebabkan kerusakan.\n[]Tolong laporkan keadaan yang tepat dimana kesalahan ini terjadi pada pengembang:\n[ORANGE] anukendev@gmail.com[] +text.error.crashtitle=Telah terjadi kesalahan +text.blocks.blockinfo=Info Blok +text.blocks.powercapacity=Kapasitas Tenaga +text.blocks.powershot=Tenaga/tembakan +text.blocks.size=Ukuran +text.blocks.liquidcapacity=Kapasitas cairan +text.blocks.maxitemssecond=Batas barang/detik +text.blocks.powerrange=Jangkauan tenaga +text.blocks.itemcapacity=Kapasitas Barang +text.blocks.inputliquid=Cairan yang Masuk +text.blocks.inputitem=Barang yang Masuk +text.blocks.explosive=Mudah meledak! +text.blocks.health=Darah +text.blocks.inaccuracy=Ketidaktelitian +text.blocks.shots=Tembakan +text.blocks.inputcapacity=Kapasitas masuk +text.blocks.outputcapacity=Kapasitas keluar +setting.difficulty.easy=mudah +setting.difficulty.normal=normal +setting.difficulty.hard=sulit +setting.difficulty.insane=sangat susah +setting.difficulty.purge=paling susah +setting.difficulty.name=Kesulitan: +setting.screenshake.name=Layar Bergoyang +setting.smoothcam.name=Kamera Halus +setting.indicators.name=Indikator Musuh +setting.effects.name=Efek Tampilan +setting.sensitivity.name=Sensitivitas Pengendali +setting.saveinterval.name=Waktu Simpan Otomatis +setting.seconds={0} Detik +setting.fullscreen.name=Layar Penuh +setting.fps.name=Tunjukkan FPS +setting.vsync.name=VSync +setting.lasers.name=Tampilkan Laser Tenaga +setting.healthbars.name=Tampilkan Bar Darah Entitas +setting.pixelate.name=Layar Pixel +setting.musicvol.name=Volume Musik +setting.mutemusic.name=Bisukan Musik +setting.sfxvol.name=Volume Suara +setting.mutesound.name=Bisukan Suara +map.maze.name=labirin +map.fortress.name=benteng +map.sinkhole.name=lubang pembuangan +map.caves.name=gua +map.volcano.name=gunung berapi +map.caldera.name=kaldera +map.scorch.name=penghangusan +map.desert.name=gurun +map.island.name=pulau +map.grassland.name=padang rumput +map.tundra.name=tundra +map.spiral.name=spiral +map.tutorial.name=tutorial +keybind.move_x.name=gerak_x +keybind.move_y.name=gerak_y +keybind.select.name=pilih +keybind.break.name=hapus +keybind.shoot.name=tembak +keybind.zoom_hold.name=perbesar_tahan +keybind.zoom.name=perbesar +keybind.menu.name=menu +keybind.pause.name=jeda +keybind.dash.name=berlari +keybind.rotate_alt.name=putar_alt +keybind.rotate.name=putar +mode.waves.name=gelombang +mode.sandbox.name=sandbox +mode.freebuild.name=freebuild +item.stone.name=batu +item.coal.name=batu bara +item.titanium.name=titanium +item.thorium.name=thorium +item.sand.name=pasir +liquid.water.name=air +liquid.lava.name=lahar +liquid.oil.name=minyak +block.door.name=pintu +block.door-large.name=pintu besar +block.conduit.name=saluran +block.pulseconduit.name=saluran cepat +block.liquidrouter.name=router cairan +block.conveyor.name=konveyor +block.router.name=router +block.junction.name=persimpangan jalan +block.liquidjunction.name=persimpangan cairan +block.sorter.name=penyortir +block.smelter.name=peleburan +text.credits=Credits +text.link.discord.description=the official Mindustry discord chatroom +text.link.github.description=Game source code +text.link.dev-builds.description=Unstable development builds +text.link.trello.description=Official trello board for planned features +text.link.itch.io.description=itch.io page with PC downloads and web version +text.link.google-play.description=Google Play store listing +text.link.wiki.description=official Mindustry wiki +text.linkfail=Failed to open link!\nThe URL has been copied to your cliboard. +text.editor.web=The web version does not support the editor!\nDownload the game to use it. +text.web.unsupported=The web version does not support this feature! Download the game to use it. +text.multiplayer.web=This version of the game does not support multiplayer!\nTo play multiplayer from your browser, use the "multiplayer web version" link at the itch.io page. +text.host.web=The web version does not support hosting games! Download the game to use this feature. +text.map.delete=Are you sure you want to delete the map "[orange]{0}[]"? +text.construction.title=Block Construction Guide +text.construction=You've just selected [accent]block construction mode[].\n\nTo begin placing, simply tap a valid location near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Shift the selection[] by holding and dragging any block in the selection.\n- [accent]Place blocks in a line[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel construction or selection[] by pressing the X at the bottom left. +text.deconstruction.title=Block Deconstruction Guide +text.deconstruction=You've just selected [accent]block deconstruction mode[].\n\nTo begin breaking, simply tap a block near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin de-constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Remove blocks in an area[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel deconstruction or selection[] by pressing the X at the bottom left. +text.showagain=Don't show again next session +text.unlocks=Unlocks +text.addplayers=Add/Remove Players +text.newgame=New Game +text.maps=Maps +text.maps.none=[LIGHT_GRAY]No maps found! +text.unlocked=New Block Unlocked! +text.unlocked.plural=New Blocks Unlocked! +text.server.rollback=Rollback +text.server.rollback.numberfield=Rollback Amount: +text.blocks.editlogs=Edit Logs +text.block.editlogsnotfound=[red]There are no edit logs for this location. +text.server.kicked.fastShoot=You are shooting too quickly. +text.server.kicked.banned=You are banned on this server. +text.server.kicked.recentKick=You have been kicked recently.\nWait before connecting again. +text.server.kicked.nameInUse=There is someone with that name\nalready on this server. +text.server.kicked.nameEmpty=Your name must contain at least one character or number. +text.server.kicked.idInUse=You are already on this server! Connecting with two accounts is not permitted. +text.server.kicked.customClient=This server does not support custom builds. Download an official version. +text.host.info=The [accent]host[] button hosts a server on ports [scarlet]6567[] and [scarlet]6568.[]\nAnybody on the same [LIGHT_GRAY]wifi or local network[] should be able to see your server in their server list.\n\nIf you want people to be able to connect from anywhere by IP, [accent]port forwarding[] is required.\n\n[LIGHT_GRAY]Note: If someone is experiencing trouble connecting to your LAN game, make sure you have allowed Mindustry access to your local network in your firewall settings. +text.join.info=Here, you can enter a [accent]server IP[] to connect to, or discover [accent]local network[] servers to connect to.\nBoth LAN and WAN multiplayer is supported.\n\n[LIGHT_GRAY]Note: There is no automatic global server list; if you want to connect to someone by IP, you would need to ask the host for their IP. +text.trace=Trace Player +text.trace.playername=Player name: [accent]{0} +text.trace.ip=IP: [accent]{0} +text.trace.id=Unique ID: [accent]{0} +text.trace.android=Android Client: [accent]{0} +text.trace.modclient=Custom Client: [accent]{0} +text.trace.totalblocksbroken=Total blocks broken: [accent]{0} +text.trace.structureblocksbroken=Structure blocks broken: [accent]{0} +text.trace.lastblockbroken=Last block broken: [accent]{0} +text.trace.totalblocksplaced=Total blocks placed: [accent]{0} +text.trace.lastblockplaced=Last block placed: [accent]{0} +text.invalidid=Invalid client ID! Submit a bug report. +text.server.bans=Bans +text.server.bans.none=No banned players found! +text.server.admins=Admins +text.server.admins.none=No admins found! +text.server.outdated=[crimson]Outdated Server![] +text.server.outdated.client=[crimson]Outdated Client![] +text.server.version=[lightgray]Version: {0} +text.server.custombuild=[yellow]Custom Build +text.confirmban=Are you sure you want to ban this player? +text.confirmunban=Are you sure you want to unban this player? +text.confirmadmin=Are you sure you want to make this player an admin? +text.confirmunadmin=Are you sure you want to remove admin status from this player? +text.disconnect.data=Failed to load world data! +text.save.difficulty=Difficulty: {0} +text.copylink=Copy Link +text.changelog.title=Changelog +text.changelog.loading=Getting changelog... +text.changelog.error.android=[orange]Note that the changelog sometimes does not work on Android 4.4 and below!\nThis is due to an internal Android bug. +text.changelog.error.ios=[orange]The changelog is currently not supported in iOS. +text.changelog.error=[scarlet]Error getting changelog!\nCheck your internet connection. +text.changelog.current=[yellow][[Current version] +text.changelog.latest=[orange][[Latest version] +text.saving=[accent]Saving... +text.unknown=Unknown +text.custom=Custom +text.builtin=Built-In +text.map.delete.confirm=Are you sure you want to delete this map? This action cannot be undone! +text.map.random=[accent]Random Map +text.map.nospawn=This map does not have any cores for the player to spawn in! Add a [ROYAL]blue[] core to this map in the editor. +text.editor.slope=\\ +text.editor.openin=Open In Editor +text.editor.oregen=Ore Generation +text.editor.oregen.info=Ore Generation: +text.editor.mapinfo=Map Info +text.editor.author=Author: +text.editor.description=Description: +text.editor.name=Name: +text.editor.teams=Teams +text.editor.elevation=Elevation +text.editor.saved=Saved! +text.editor.save.noname=Your map does not have a name! Set one in the 'map info' menu. +text.editor.save.overwrite=Your map overwrites a built-in map! Pick a different name in the 'map info' menu. +text.editor.import.exists=[scarlet]Unable to import:[] a built-in map named '{0}' already exists! +text.editor.import=Import... +text.editor.importmap=Import Map +text.editor.importmap.description=Import an already existing map +text.editor.importfile=Import File +text.editor.importfile.description=Import an external map file +text.editor.importimage=Import Terrain Image +text.editor.importimage.description=Import an external map image file +text.editor.export=Export... +text.editor.exportfile=Export File +text.editor.exportfile.description=Export a map file +text.editor.exportimage=Export Terrain Image +text.editor.exportimage.description=Export a map image file +text.editor.overwrite.confirm=[scarlet]Warning![] A map with this name already exists. Are you sure you want to overwrite it? +text.fps=FPS: {0} +text.tps=TPS: {0} +text.ping=Ping: {0}ms +text.settings.rebind=Rebind +text.yes=Yes +text.no=No +text.blocks.targetsair=Targets Air +text.blocks.itemspeed=Units Moved +text.blocks.shootrange=Range +text.blocks.poweruse=Power Use +text.blocks.inputitemcapacity=Input Item Capacity +text.blocks.outputitemcapacity=Input Item Capacity +text.blocks.maxpowergeneration=Max Power Generation +text.blocks.powertransferspeed=Power Transfer +text.blocks.craftspeed=Production Speed +text.blocks.inputliquidaux=Aux Liquid +text.blocks.inputitems=Input Items +text.blocks.outputitem=Output Item +text.blocks.drilltier=Drillables +text.blocks.drillspeed=Base Drill Speed +text.blocks.liquidoutput=Liquid Output +text.blocks.liquiduse=Liquid Use +text.blocks.coolant=Coolant +text.blocks.coolantuse=Coolant Use +text.blocks.inputliquidfuel=Fuel Liquid +text.blocks.liquidfueluse=Liquid Fuel Use +text.blocks.reload=Reload +text.blocks.inputfuel=Fuel +text.blocks.fuelburntime=Fuel Burn Time +text.unit.blocks=blocks +text.unit.powersecond=power units/second +text.unit.liquidsecond=liquid units/second +text.unit.itemssecond=items/second +text.unit.pixelssecond=pixels/second +text.unit.liquidunits=liquid units +text.unit.powerunits=power units +text.unit.degrees=degrees +text.unit.seconds=seconds +text.unit.none= +text.unit.items=items +text.category.general=General +text.category.power=Power +text.category.liquids=Liquids +text.category.items=Items +text.category.crafting=Crafting +text.category.shooting=Shooting +setting.multithread.name=Multithreading +setting.previewopacity.name=Placing Preview Opacity +setting.minimap.name=Show Minimap +text.keybind.title=Rebind Keys +keybind.block_info.name=block_info +keybind.chat.name=chat +keybind.player_list.name=player_list +keybind.console.name=console +mode.text.help.title=Description of modes +mode.waves.description=the normal mode. limited resources and automatic incoming waves. +mode.sandbox.description=infinite resources and no timer for waves. +mode.freebuild.description=limited resources and no timer for waves. +content.item.name=Items +content.liquid.name=Liquids +content.unit-type.name=Units +content.recipe.name=Blocks +item.stone.description=A common raw material. Used for separating and refining into other materials, or melting into lava. +item.tungsten.name=Tungsten +item.tungsten.description=A common, but very useful structure material. Used in drills and heat-resistant blocks such as generators and smelteries. +item.lead.name=Lead +item.lead.description=A basic starter material. Used extensively in electronics and liquid transportation blocks. +item.coal.description=A common and readily available fuel. +item.carbide.name=Carbide +item.carbide.description=A tough alloy made with tungsten and carbon. Used in advanced transportation blocks and high-tier drills. +item.titanium.description=A rare super-light metal used extensively in liquid transportation, drills and aircraft. +item.thorium.description=A dense, radioactive metal used as structural support and nuclear fuel. +item.silicon.name=Silicon +item.silcion.description=An extremely useful semiconductor, with applications in solar panels and many complex electronics. +item.plastanium.name=Plastanium +item.plastanium.description=A light, ductile material used in advanced aircraft and fragmentation ammunition. +item.phase-matter.name=Phase Matter +item.surge-alloy.name=Surge Alloy +item.biomatter.name=Biomatter +item.biomatter.description=A clump of organic mush; used for conversion into oil or as a basic fuel. +item.sand.description=A common material that is used extensively in smelting, both in alloying and as a flux. +item.blast-compound.name=Blast Compound +item.blast-compound.description=A volatile compound used in bombs and explosives. While it can burned as fuel, this is not advised. +item.pyratite.name=Pyratite +item.pyratite.description=An extremely flammable substance used in incendiary weapons. +liquid.cryofluid.name=Cryofluid +text.item.explosiveness=[LIGHT_GRAY]Explosiveness: {0} +text.item.flammability=[LIGHT_GRAY]Flammability: {0} +text.item.radioactivity=[LIGHT_GRAY]Radioactivity: {0} +text.item.fluxiness=[LIGHT_GRAY]Flux Power: {0} +text.item.hardness=[LIGHT_GRAY]Hardness: {0} +text.liquid.heatcapacity=[LIGHT_GRAY]Heat Capacity: {0} +text.liquid.viscosity=[LIGHT_GRAY]Viscosity: {0} +text.liquid.temperature=[LIGHT_GRAY]Temperature: {0} +block.tungsten-wall.name=Tungsten Wall +block.tungsten-wall-large.name=Large Tungsten Wall +block.carbide-wall.name=Carbide Wall +block.carbide-wall-large.name=Large Carbide Wall +block.thorium-wall.name=Thorium Wall +block.thorium-wall-large.name=Large Thorium Wall +block.duo.name=Duo +block.scorch.name=Scorch +block.hail.name=Hail +block.lancer.name=Lancer +block.titanium-conveyor.name=Titanium Conveyor +block.splitter.name=Splitter +block.splitter.description=Outputs items into two opposite directions immediately after they are recieved. +block.router.description=Splits items into all 4 directions. Can store items as a buffer. +block.distributor.name=Distributor +block.distributor.description=A splitter that can split items into 8 directions. +block.sorter.description=Sorts items. If an item matches the selection, it is allowed to pass. Otherwise, the item is outputted to the left and right. +block.overflow-gate.name=Overflow Gate +block.overflow-gate.description=A combination splitter and router that only outputs to the left and right if the front path is blocked. +block.bridgeconveyor.name=Bridge Conveyor +block.bridgeconveyor.description=A conveyor that can go over other blocks, for up to two total blocks. +block.arc-smelter.name=Arc Smelter +block.silicon-smelter.name=Silicon Smelter +block.phase-weaver.name=Phase Weaver +block.pulverizer.name=Pulverizer +block.cryofluidmixer.name=Cryofluid Mixer +block.melter.name=Melter +block.incinerator.name=Incinerator +block.biomattercompressor.name=Biomatter Compressor +block.separator.name=Separator +block.centrifuge.name=Centrifuge +block.power-node.name=Power Node +block.power-node-large.name=Large Power Node +block.battery.name=Battery +block.battery-large.name=Large Battery +block.combustion-generator.name=Combustion Generator +block.turbine-generator.name=Turbine Generator +block.tungsten-drill.name=Tungsten Drill +block.carbide-drill.name=Carbide Drill +block.laser-drill.name=Laser Drill +block.water-extractor.name=Water Extractor +block.cultivator.name=Cultivator +block.dart-ship-factory.name=Dart Ship Factory +block.delta-mech-factory.name=Delta Mech Factory +block.dronefactory.name=Drone Factory +block.repairpoint.name=Repair Point +block.resupplypoint.name=Resupply Point +block.liquidtank.name=Liquid Tank +block.bridgeconduit.name=Bridge Conduit +block.mechanical-pump.name=Mechanical Pump +block.itemsource.name=Item Source +block.itemvoid.name=Item Void +block.liquidsource.name=Liquid Source +block.powervoid.name=Power Void +block.powerinfinite.name=Power Infinite +block.unloader.name=Unloader +block.sortedunloader.name=Sorted Unloader +block.vault.name=Vault +block.wave.name=Wave +block.swarmer.name=Swarmer +block.salvo.name=Salvo +block.ripple.name=Ripple +block.phase-conveyor.name=Phase Conveyor +block.bridge-conveyor.name=Bridge Conveyor +block.plastanium-compressor.name=Plastanium Compressor +block.pyratite-mixer.name=Pyratite Mixer +block.blast-mixer.name=Blast Mixer +block.solidifer.name=Solidifer +block.solar-panel.name=Solar Panel +block.solar-panel-large.name=Large Solar Panel +block.oil-extractor.name=Oil Extractor +block.javelin-ship-factory.name=Javelin Ship factory +block.drone-factory.name=Drone Factory +block.fabricator-factory.name=Fabricator Factory +block.repair-point.name=Repair Point +block.resupply-point.name=Resupply Point +block.pulse-conduit.name=Pulse Conduit +block.phase-conduit.name=Phase Conduit +block.liquid-router.name=Liquid Router +block.liquid-tank.name=Liquid Tank +block.liquid-junction.name=Liquid Junction +block.bridge-conduit.name=Bridge Conduit +block.rotary-pump.name=Rotary Pump +block.nuclear-reactor.name=Nuclear Reactor diff --git a/core/assets/bundles/bundle_ita.properties b/core/assets/bundles/bundle_ita.properties index 02b83f8158..9cb1f17c56 100644 --- a/core/assets/bundles/bundle_ita.properties +++ b/core/assets/bundles/bundle_ita.properties @@ -1,551 +1,515 @@ -text.about = Creato da [ROYAL]Anuken[] - [SKY]anukendev@gmail.com[]\nOriginariamente era una voce nel [orange]GDL[] Metal Monstrosity Jam.\n\n Crediti:\n - SFX realizzato con [YELLOW]bfxr [] \n - Musica creata da [GREEN]RoccoW[] / trovata su [lime]FreeMusicArchive.org[]\n\n Un ringraziamento speciale a:\n - [coral]MitchellFJN []: esteso test del gioco e feedback\n - [sky]Luxray5474 []: lavorazione della wiki, contributi col codice\n - [lime]Epowerj []: sistema di costruzione del codice, icone\n - Tutti i beta tester su itch.io e Google Play\n -text.credits = Crediti -text.discord = Unisciti sul server discord di mindustry! -text.changes = [SCARLET]Attenzione!\n[]Alcune importanti meccaniche di gioco sono state modificate.\n\n - [accent]I teletrasporti[] ora usano la corrente.\n - [accent]Le fornaci[] e [accent]i crogioli[] ora hanno una capacità massima di oggetti. \n- [accent]I crogioli[] ora richiedono il carbone come combustibile. -text.link.discord.description = la chatroom ufficiale del server discord di Mindustry -text.link.github.description = Codice sorgente del gioco -text.link.dev-builds.description = Build di sviluppo versioni instabili -text.link.trello.description = Scheda ufficiale trello per funzionalità pianificate -text.link.itch.io.description = pagina di itch.io con download per PC e versione web -text.link.google-play.description = Elenco di Google Play Store -text.link.wiki.description = wiki ufficiale di Mindustry -text.linkfail = Impossibile aprire il link! L'URL è stato copiato nella tua bacheca. -text.editor.web = La versione web non supporta l'editor! Scarica il gioco per usarlo. -text.multiplayer.web = Questa versione del gioco non supporta il multiplayer! Per giocare in multiplayer dal tuo browser, usa il link \"versione web multiplayer\" nella pagina itch.io. -text.gameover = Il nucleo è stato distrutto. -text.highscore = [YELLOW]Nuovo record! -text.lasted = Sei durato fino all'onda -text.level.highscore = Migliore: [accent]{0} -text.level.delete.title = Conferma Eliminazione -text.level.delete = Sei sicuro di voler eliminare la mappa \"[arancione]{0}\"? -text.level.select = Selezione del livello -text.level.mode = Modalità di gioco: -text.savegame = Salva -text.loadgame = Carica -text.joingame = Gioca MP -text.newgame = Nuovo gioco -text.quit = Esci -text.about.button = Informazioni -text.name = Nome: -text.public = Pubblico -text.players = {0} giocatori online -text.server.player.host = {0} (host) -text.players.single = {0} giocatori online -text.server.mismatch = Errore nel pacchetto: possibile discrepanza nella versione client / server. Assicurati che tu e l'host abbiate l'ultima versione di Mindustry! -text.server.closing = [accent]Chiusura server ... -text.server.kicked.kick = Sei stato cacciato dal server! -text.server.kicked.invalidPassword = 10468 = Password non valida. -text.server.kicked.clientOutdated = Versione del client obsoleta! Aggiorna il tuo gioco! -text.server.kicked.serverOutdated = Server obsoleto! Chiedi all'host di aggiornare! -text.server.kicked.banned = Sei stato bannato su questo server. -text.server.kicked.recentKick = Sei stato cacciato di recente. Attendi prima di connetterti di nuovo. -text.server.connected = {0} si è connesso -text.server.disconnected = {0} si è disconnesso -text.nohost = Impossibile hostare il server con una mappa personalizzata! -text.host.info = Il pulsante [accent]hos [] ospita un server sulle porte [scarlet]6567[] e [scarlet]656.[] Chiunque sulla stessa [LIGHT_GRAY]connessione wifi o rete locale[] dovrebbe essere in grado di vedere il proprio server nel proprio elenco server.\n\n Se vuoi che le persone siano in grado di connettersi ovunque tramite IP, è richiesto il [accent]port forwarding[]. \n\n[LIGHT_GRAY]Nota: se qualcuno sta riscontrando problemi durante la connessione al gioco LAN, assicurati di aver consentito a Mindustry di accedere alla rete locale nelle impostazioni del firewall. -text.join.info = Qui è possibile inserire un [accent]IP del server[] a cui connettersi, o scoprire [accento]un server sulla rete locale[] disponibile.\n Sono supportati sia il multiplayer LAN che WAN. \n\n[LIGHT_GRAY]Nota: non esiste un elenco di server globali automatici; se si desidera connettersi a qualcuno tramite IP, è necessario chiedere all'host il proprio IP. -text.hostserver = Server host -text.host = Host -text.hosting = [accento] Apertura del server ... -text.hosts.refresh = Aggiorna -text.hosts.discovering = Scoperta partite LAN -text.server.refreshing = Aggiornamento server -text.hosts.none = [lightgray]Nessuna partita LAN trovata! -text.host.invalid = [scarlet]Impossibile connettersi all'host. -text.server.friendlyfire = Fuoco amico -text.trace = Trace Player -text.trace.playername = Nome del giocatore: [accent]{0} -text.trace.ip = IP: [accent]{0} -text.trace.id = ID univoco: [accent]{0} -text.trace.android = Client Android: [accent] {0} -text.trace.modclient = Cliente personalizzato: [accent]{0} -text.trace.totalblocksbroken = Totale blocchi interrotti: [accent]{0} -text.trace.structureblocksbroken = Blocchi strutturali distrutti: [accent]{0} -text.trace.lastblockbroken = Ultimo blocco distrutto: [accent]{0} -text.trace.totalblocksplaced = Totale blocchi posizionati: [accent]{0} -text.trace.lastblockplaced = Ultimo blocco inserito: [accent]{0} -text.invalidid = ID cliente non valido! Invia una segnalazione di bug. -text.server.bans = Lista Ban -text.server.bans.none = Nessun giocatore bandito trovato! -text.server.admins = Amministratori -text.server.admins.none = Nessun amministratore trovato! -text.server.add = Aggiungi server -text.server.delete = Sei sicuro di voler eliminare questo server? -text.server.hostname = Host: {0} -text.server.edit = Modifica server -text.server.outdated = [crimson]Server obsoleto![] -text.server.outdated.client = [crimson]Client obsoleto![] -text.server.version = [lightgray]Versione: {0} -text.server.custombuild = [yellow] Costruzione personalizzata -text.confirmban = Sei sicuro di voler bandire questo giocatore? -text.confirmunban = Sei sicuro di voler sbloccare questo giocatore? -text.confirmadmin = Sei sicuro di voler rendere questo giocatore un amministratore? -text.confirmunadmin = Sei sicuro di voler rimuovere lo stato di amministratore da questo player? -text.joingame.byip = Unisciti a IP ... -text.joingame.title = Unisciti alla Partita -text.joingame.ip = IP: -text.disconnect = Disconnesso. -text.disconnect.data = Errore nel caricamento i dati del mondo! -text.connecting = [accent]Connessione in corso ... -text.connecting.data = [accent]Caricamento dei dati del mondo ... -text.connectfail = [crimson] Impossibile connettersi al server: [orange] {0} -text.server.port = Porta: -text.server.addressinuse = Indirizzo già in uso! -text.server.invalidport = Numero di porta non valido! -text.server.error = [crimson]Errore nell'hosting del server: [orange] {0} -text.tutorial.back = < Prec -text.tutorial.next = Succ > -text.save.new = Nuovo Salvataggio -text.save.overwrite = Sei sicuro di voler sovrascrivere questo salvataggio? -text.overwrite = Sostituisci -text.save.none = Nessun salvataggio trovato! -text.saveload = [Accent]Salvataggio ... -text.savefail = Salvataggio del gioco non riuscito! -text.save.delete.confirm = Sei sicuro di voler eliminare questo salvataggio? -text.save.delete = Elimina -text.save.export = Esporta Salva -text.save.import.invalid = [orange]Questo salvataggio non è valido! -text.save.import.fail = [crimson]Impossibile importare salvataggio: [orange]{0} -text.save.export.fail = [crimson]Impossibile esportare il salvataggio: [orange]{0} -text.save.import = Importa Salvataggio -text.save.newslot = Salva nome: -text.save.rename = Rinomina -text.save.rename.text = Nuovo nome: -text.selectslot = Seleziona un salvataggio. -text.slot = [accent]Slot {0} -text.save.corrupted = [orang]File di salvataggio danneggiato o non valido! -text.empty = -text.on = Acceso -text.off = Spento -text.save.autosave = Salvataggio automatico: {0} -text.save.map = mappa -text.save.wave = Ondata: -text.save.difficulty = Difficolta: {0} -text.save.date = Ultimo salvataggio: {0} -text.confirm = Conferma -text.delete = Elimina -text.ok = OK -text.open = Apri -text.cancel = Annulla -text.openlink = Apri Link -text.copylink = Copia link -text.back = Indietro -text.quit.confirm = Sei sicuro di voler uscire? -text.changelog.title = Registro modifiche -text.changelog.loading = Ottenere il registro delle modifiche ... -text.changelog.error.android = [orange]Nota che il log delle modifiche non funziona su Android 4.4 e versioni precedenti! Ciò è dovuto a un bug interno di Android. -text.changelog.error = [scarlet]Errore durante il recupero del changelog! Controlla la tua connessione Internet. -text.changelog.current = [yellow][[Current version] -text.changelog.latest = [orange][[Latest version] -text.loading = [accent]Caricamento in corso ... -text.wave = [orange]Onda {0} -text.wave.waiting = Onda in {0} -text.waiting = In attesa... -text.enemies = {0} Nemici -text.enemies.single = {0} Nemico -text.loadimage = Carica immagine -text.saveimage = Salva Immagine -text.oregen = Generazione dei minerali -text.editor.badsize = [orange]Dimensioni dell'immagine non valide![]\n Dimensioni della mappa valide: {0} -text.editor.errorimageload = Errore durante il caricamento del file immagine:\n [orange]{0} -text.editor.errorimagesave = Errore durante il salvataggio del file immagine:\n [orange]{0} -text.editor.generate = Genera -text.editor.resize = Zomma o \nRiduci -text.editor.loadmap = Carica\nmappa -text.editor.savemap = Salva\nla mappa -text.editor.loadimage = Carica\nimmagine -text.editor.saveimage = Salva\nImmagine -text.editor.unsaved = [scarlet]Hai modifiche non salvate![]\nSei sicuro di voler uscire? -text.editor.brushsize = Dimensione del pennello: {0} -text.editor.noplayerspawn = Questa mappa non ha lo spawnpoint del giocatore! -text.editor.manyplayerspawns = Le mappe non possono avere più di un punto di spawn di un giocatore! -text.editor.manyenemyspawns = Non puoi avere più di {0} spawn nemici! -text.editor.resizemap = Ridimensiona la mappa -text.editor.resizebig = [Scarlet]Attenzione!\n[]Le mappe più grandi di 256 unità potrebbero causare del lag oltre ad essere instabili. -text.editor.mapname = Nome Mappa: -text.editor.overwrite = [Accent]Attenzione!\nQuesto sovrascrive una mappa esistente. -text.editor.failoverwrite = [crimson]Impossibile sovrascrivere la mappa di default! -text.editor.selectmap = Seleziona una mappa da caricare: -text.width = Larghezza: -text.height = Altezza: -text.randomize = Randomizza -text.apply = Applicare -text.update = Aggiorna -text.menu = Menu -text.play = Gioca -text.load = Carica -text.save = Salva -text.language.restart = Riavvia il gioco affinché il cambiamento della lingua abbia effetto. -text.settings.language = Lingua -text.settings = Impostazioni -text.tutorial = Lezioni -text.editor = Editor -text.mapeditor = Editor delle mappe -text.donate = Dona -text.settings.reset = Resetta Alle Impostazioni Predefinite -text.settings.controls = Controlli -text.settings.game = Gioco -text.settings.sound = Suono -text.settings.graphics = Grafica -text.upgrades = Miglioramenti -text.purchased = [LIME]Creato! -text.weapons = Armi -text.paused = In pausa -text.respawn = Rinascita in -text.info.title = [Accent]Informazioni -text.error.title = [crimson]Si è verificato un errore -text.error.crashmessage = [SCARLET]Si è verificato un errore imprevisto che ha causato un arresto anomalo.[] Si prega di segnalare le circostanze esatte in cui questo errore si è verificato allo sviluppatore:\n[ORANGE]anukendev@gmail.com[] -text.error.crashtitle = Si è verificato un errore -text.mode.break = Modalità di interruzione: {0} -text.mode.place = Modalità luogo: {0} -placemode.hold.name = linea -placemode.areadelete.name = area -placemode.touchdelete.name = toccare -placemode.holddelete.name = trattieni -placemode.none.name = nessuno -placemode.touch.name = toccare -placemode.cursor.name = cursore -text.blocks.extrainfo = [accent]informazioni extra sui blocchi: -text.blocks.blockinfo = Informazioni sul blocco -text.blocks.powercapacity = Capacità energetica -text.blocks.powershot = Danno/Colpo -text.blocks.powersecond = Energia/Secondo -text.blocks.powerdraindamage = Consumo/Danno -text.blocks.shieldradius = Raggio dello scudo -text.blocks.itemspeedsecond = Velocita Oggetti/Secondo -text.blocks.range = Gamma -text.blocks.size = Grandezza -text.blocks.powerliquid = Energia/Liquido -text.blocks.maxliquidsecond = Max liquido/Secondo -text.blocks.liquidcapacity = Capacità del liquido -text.blocks.liquidsecond = Liquido/Secondo -text.blocks.damageshot = Danni colpo -text.blocks.ammocapacity = Capacità del caricatore -text.blocks.ammo = Munizioni -text.blocks.ammoitem = Munizioni/Oggetto -text.blocks.maxitemssecond = Oggetti massimi/secondo -text.blocks.powerrange = Raggio Energia -text.blocks.lasertilerange = Raggio piastrelle laser -text.blocks.capacity = Capacità -text.blocks.itemcapacity = Capacità oggetto -text.blocks.maxpowergenerationsecond = Massima Energia Generata/secondo -text.blocks.powergenerationsecond = Energia generata/secondo -text.blocks.generationsecondsitem = Generazione secondi/oggetto -text.blocks.input = Ingresso -text.blocks.inputliquid = Ingresso del liquido -text.blocks.inputitem = Ingresso Oggetto -text.blocks.output = Uscita -text.blocks.secondsitem = Secondi/item -text.blocks.maxpowertransfersecond = Massimo trasferimento di potenza/secondo -text.blocks.explosive = Altamente esplosivo! -text.blocks.repairssecond = Ripara/secondo -text.blocks.health = Salute -text.blocks.inaccuracy = inesattezza -text.blocks.shots = Colpi -text.blocks.shotssecond = Colpi/secondo -text.blocks.fuel = Carburante -text.blocks.fuelduration = Durata del carburante -text.blocks.maxoutputsecond = Uscita max/secondo -text.blocks.inputcapacity = Capacità di ingresso -text.blocks.outputcapacity = Capacità di uscita -text.blocks.poweritem = Energia/Oggetto -text.placemode = Place Mode -text.breakmode = Modalità di interruzione -text.health = Salutee -setting.difficulty.easy = facile -setting.difficulty.normal = medio -setting.difficulty.hard = difficile -setting.difficulty.insane = Folle -setting.difficulty.purge = Epurazione -setting.difficulty.name = Difficoltà: -setting.screenshake.name = Screen Shake -setting.smoothcam.name = Smooth Camera -setting.indicators.name = Indicatori nemici -setting.effects.name = Visualizza effetti -setting.sensitivity.name = Sensibilità del controllore. -setting.saveinterval.name = Intervallo di salvataggio automatico -setting.seconds = {0} Secondi -setting.fullscreen.name = Schermo Intero -setting.multithread.name = multithreading -setting.fps.name = Mostra FPS -setting.vsync.name = Sincronizzazione Verticale -setting.lasers.name = Mostra Energia Dei Laser -setting.healthbars.name = Mostra barra della salute delle entità -setting.pixelate.name = Schermo Pixelate -setting.musicvol.name = Volume Musica -setting.mutemusic.name = Musica muta -setting.sfxvol.name = Volume SFX -setting.mutesound.name = Suono muto -map.maze.name = labirinto -map.fortress.name = fortezza -map.sinkhole.name = dolina -map.caves.name = grotte -map.volcano.name = vulcano -map.caldera.name = caldera -map.scorch.name = bruciatura -map.desert.name = Deserto -map.island.name = Isola -map.grassland.name = Prateria -map.tundra.name = Tundra -map.spiral.name = spirale -map.tutorial.name = Tutorial -tutorial.intro.text = [yellow]Benvenuti nel tutorial.[] Per iniziare, premere 'succ'. -tutorial.moveDesktop.text = Per spostarsi, utilizza i tasti [orange][[WASD][] . Tenere premuto [orange]shift []per correre. Tenere premuto [orange]CTRL[] mentre si utilizza la [orange]rotella del mouse[] per ingrandire o ridurre lo zoom. -tutorial.shoot.text = Usa il mouse per mirare, tieni premuto [orange]tasto sinistro del mouse[] per sparare. Fai uun po' di pratica con quest' [yellow]obiettivo[]. -tutorial.moveAndroid.text = Per spostare la vista, trascina un dito sullo schermo. Pizzica e trascina per ingrandire o ridurre. -tutorial.placeSelect.text = Prova a selezionare un [yellow]nastro trasportatore[] dal menu dei blocchi in basso a destra. -tutorial.placeConveyorDesktop.text = Utilizza la [orange]rotellina di scorrimento[] per ruotare il nastro trasportatore in modo che sia rivolto verso [orange]in avanti[], quindi posizionarlo nella [yellow]posizione contrassegnata[] utilizzando il [orange]tasto sinistro del mouse[]. -tutorial.placeConveyorAndroid.text = Utilizzare il pulsante [orange]tasto di rotazione[] per ruotare il trasportatore in modo che sia rivolto [orange]in avanti[], trascinalo in posizione con un dito, quindi posizionalo nella [yellow]posizione contrassegnata[] utilizzando [orange]segno di spunta[] -tutorial.placeConveyorAndroidInfo.text = In alternativa, puoi premere l'icona mirino in basso a sinistra per passare alla [orange] touch mode[] e posiziona i blocchi toccando sullo schermo. In modalità touch, i blocchi possono essere ruotati con la freccia in basso a sinistra. Premi [yellow]avanti[] per provarlo. -tutorial.placeDrill.text = Ora, seleziona e posiziona un [yellow]trapano per pietra[] nella posizione contrassegnata. -tutorial.blockInfo.text = Se vuoi saperne di più su un blocco, puoi toccare il [orange]punto interrogativo[] in alto a destra per leggere la sua descrizione. -tutorial.deselectDesktop.text = Puoi deselezionare un blocco usando [orange]tasto destro del mouse[]. -tutorial.deselectAndroid.text = È possibile deselezionare un blocco premendo il tasto [orange]X[]. -tutorial.drillPlaced.text = Il trapano ora produrrà [yellow]pietra,[] la manderà sul nastro trasportatore, quindi la sposterà nel [yellow]nucleo[]. -tutorial.drillInfo.text = I minerali differenti hanno bisogno di trapani diversi. La pietra richiede il trapano di pietra, il ferro richiede il trapano di ferro, ecc. -tutorial.drillPlaced2.text = Spostando gli oggetti nel nucleo li metti nell' [yellow]inventario[], in alto a sinistra. Piazzare i blocchi usa gli oggetti dal tuo inventario. -tutorial.moreDrills.text = Puoi collegare molti trapani e trasportatori insieme, in questo modo. -tutorial.deleteBlock.text = È possibile eliminare i blocchi facendo clic sul [orange]pulsante destro del mouse[] sul blocco che si desidera eliminare. Prova a eliminare questo trasportatore. -tutorial.deleteBlockAndroid.text = È possibile eliminare i blocchi [orange]selezionandoli col mirino[] nel menu della [orange]modalità pausa[] in basso a sinistra e toccando un blocco. Prova a eliminare questo trasportatore. -tutorial.placeTurret.text = Ora, seleziona e posiziona una [yellow]torretta[] nella [yellow]posizione contrassegnata[]. -tutorial.placedTurretAmmo.text = Questa torretta ora accetta [yellow]munizioni[] dal trasportatore. Puoi vedere quante munizioni ha al passaggio del mouse [green]barra verde[]. -tutorial.turretExplanation.text = Le torrette spareranno automaticamente al nemico più vicino nel raggio d'azione, a patto che abbiano munizioni sufficienti. -tutorial.waves.text = Ogni [yellow]60[] secondi, un'ondata di [coral]nemici[] si genera in posizioni specifiche e tenta di distruggere il nucleo. -tutorial.coreDestruction.text = Il tuo obiettivo è difendere [yellow]il nucleo[]. Se il nucleo viene distrutto, tu [coral]perdi la partita[]. -tutorial.pausingDesktop.text = Se hai bisogno di fare una pausa, premi il [orange]pulsante di pausa[] in alto a sinistra per mettere in pausa il gioco. Puoi ancora selezionare e posizionare i blocchi mentre sei in pausa, ma non puoi muoverti o sparare -tutorial.pausingAndroid.text = Se hai bisogno di fare una pausa, premi il [orange]pulsante di pausa[] in alto a sinistra per mettere in pausa il gioco. Puoi ancora rompere e posizionare i blocchi mentre sei in pausa. -tutorial.purchaseWeapons.text = Puoi acquistare nuove [yellow]armi[] per il tuo mech aprendo il menu di aggiornamenti in basso a sinistra. -tutorial.switchWeapons.text = Cambia le armi facendo clic sulla sua icona in basso a sinistra o usando i numeri [orange][[1-9][]. -tutorial.spawnWave.text = Ecco un'ondata ora. Distruggili. -tutorial.pumpDesc.text = Nelle onde successive, potrebbe essere necessario utilizzare le [yellow]pompe[] per distribuire i liquidi per i generatori o gli estrattori. -tutorial.pumpPlace.text = Le pompe funzionano in modo simile ai trapani, tranne per il fatto che producono liquidi anziché oggetti. Prova a posizionare una pompa sull' [yellow]petrolio evidenziato[]. -tutorial.conduitUse.text = Ora posiziona una [orange]conduttura[] -tutorial.conduitUse2.text = E alcuni altri ... -tutorial.conduitUse3.text = E alcuni altri ... -tutorial.generator.text = Ora, posizionare un [orange]generatore a combustione[] all'estremità del condotto. -tutorial.generatorExplain.text = Questo generatore ora creerà [yellow]corrente[] dall'petrolio. -tutorial.lasers.text = La potenza è distribuita usando i [yellow]laser energetici[]. Ruota e posizionane uno qui. -tutorial.laserExplain.text = Il generatore ora trasferirà l'energia nel blocco laser. Un raggio [yellow]opaco[] indica che sta trasmettendo corrente e un raggio [yellow]trasparente[] significa che non la sta strasmettendo. -tutorial.laserMore.text = Puoi controllare quanta energia ha un blocco passandoci sopra e controllando la barra [yellow]gialla[] in alto. -tutorial.healingTurret.text = Questo laser può essere utilizzato per alimentare una [lime]torretta di riparazione[]. Mettine una qui. -tutorial.healingTurretExplain.text = Finché ha energia, questa torretta [lime]riparerà i blocchi vicini.[] Durante la riproduzione del gioco, assicurati di averne una nella tua base il più rapidamente possibile! -tutorial.smeltery.text = Molti blocchi richiedono [orange]acciaio[] da produrre, che richiede una [orange] fonderia[] per la produzione. Mettine una qui. -tutorial.smelterySetup.text = Questa fonderia produrrà ora [orange]acciaio[] dal ferro in ingresso, usando il carbone come combustibile. -tutorial.tunnelExplain.text = Si noti inoltre che gli oggetti passano attraverso un[orange]tunnel[] e emergono dall'altra parte, passando attraverso il blocco di pietra. Tieni presente che i tunnel possono attraversare fino a 2 blocchi. -tutorial.end.text = E questo conclude il tutorial! In bocca al lupo! -text.keybind.title = Configurazione Tasti -keybind.move_x.name = move_x -keybind.move_y.name = move_y -keybind.select.name = seleziona -keybind.break.name = rompere -keybind.shoot.name = sparare -keybind.zoom_hold.name = zoom_hold -keybind.zoom.name = zoom -keybind.block_info.name = Informazioni blocco -keybind.menu.name = menu -keybind.pause.name = pausa -keybind.dash.name = corsa -keybind.chat.name = Chat -keybind.player_list.name = lista_giocatori -keybind.console.name = console -keybind.rotate_alt.name = rotate_alt -keybind.rotate.name = Ruotare -keybind.weapon_1.name = arma_1 -keybind.weapon_2.name = arma_2 -keybind.weapon_3.name = arma_3 -keybind.weapon_4.name = arma_4 -keybind.weapon_5.name = arma_5 -keybind.weapon_6.name = arma_6 -mode.text.help.title = Descrizione delle modalità -mode.waves.name = onde -mode.waves.description = modalità normale. risorse limitate e onde in entrata automatiche. -mode.sandbox.name = Sandbox -mode.sandbox.description = risorse infinite e nessun timer per le onde. -mode.freebuild.name = freebuild -mode.freebuild.description = risorse limitate e nessun timer per le onde. -upgrade.standard.name = Standard -upgrade.standard.description = Il mech standard. -upgrade.blaster.name = blaster -upgrade.blaster.description = Spara un proiettile lento, debole. -upgrade.triblaster.name = triblaster -upgrade.triblaster.description = Spara 3 proiettili a diffusione. -upgrade.clustergun.name = clustergun -upgrade.clustergun.description = Spara delle imprecise granate esplosive. -upgrade.beam.name = cannone a raggi -upgrade.beam.description = Spara un raggio laser penetrante a lungo raggio. -upgrade.vulcan.name = Vulcano -upgrade.vulcan.description = Spara una raffica di proiettili veloci. -upgrade.shockgun.name = shockgun -upgrade.shockgun.description = Spara a una devastante esplosione di shrapnel carichi. -item.stone.name = pietra -item.iron.name = ferro -item.coal.name = carbone -item.steel.name = acciaio -item.titanium.name = titanio -item.dirium.name = diridio -item.uranium.name = uranio -item.sand.name = sabbia -liquid.water.name = acqua -liquid.plasma.name = Plasma -liquid.lava.name = lava -liquid.oil.name = petrolio -block.weaponfactory.name = fabbrica d'armi -block.weaponfactory.fulldescription = Utilizzata per creare armi per il giocatore mech. Clicca per usare. Prende automaticamente le risorse dal core. -block.air.name = aria -block.blockpart.name = blockpart -block.deepwater.name = acque profonde -block.water.name = acqua -block.lava.name = lava -block.oil.name = petrolio -block.stone.name = pietra -block.blackstone.name = pietra nera -block.iron.name = ferro -block.coal.name = carbone -block.titanium.name = titanio -block.uranium.name = uranio -block.dirt.name = terra -block.sand.name = sabbia -block.ice.name = ghiaccio -block.snow.name = neve -block.grass.name = Erba -block.sandblock.name = blocco di sabbia -block.snowblock.name = blocco di neve -block.stoneblock.name = blocco di pietra -block.blackstoneblock.name = blocco di pietra nera -block.grassblock.name = blocco d'erba -block.mossblock.name = blocco di muschio -block.shrub.name = arbusto -block.rock.name = roccia -block.icerock.name = giaccio -block.blackrock.name = roccia nera -block.dirtblock.name = blocco di terra -block.stonewall.name = muro di pietra -block.stonewall.fulldescription = Un blocco difensivo poco costoso. Utile per proteggere il nucleo e le torrette nelle prime ondate. -block.ironwall.name = muro di ferro -block.ironwall.fulldescription = Un blocco difensivo di base. Fornisce protezione dai nemici. -block.steelwall.name = muro d'acciaio -block.steelwall.fulldescription = Un blocco difensivo standard. protezione adeguata dai nemici. -block.titaniumwall.name = muro di titanio -block.titaniumwall.fulldescription = Un forte blocco difensivo. Fornisce protezione dai nemici. -block.duriumwall.name = muro di diridio -block.duriumwall.fulldescription = Un blocco difensivo molto forte. Fornisce protezione dai nemici. -block.compositewall.name = muro composito -block.steelwall-large.name = grande muro di acciaio -block.steelwall-large.fulldescription = Un blocco difensivo standard. Si estende su più tessere. -block.titaniumwall-large.name = grande muro di titanio -block.titaniumwall-large.fulldescription = Un forte blocco difensivo. Si estende su più tessere. -block.duriumwall-large.name = grande muro di diridio -block.duriumwall-large.fulldescription = Un blocco difensivo molto forte. Si estende su più tessere. -block.titaniumshieldwall.name = muro schermato -block.titaniumshieldwall.fulldescription = Un forte blocco difensivo, con uno scudo incorporato extra. Richiede energia. Utilizza l'energia per assorbire i proiettili nemici. Si consiglia di utilizzare i booster di energia per fornire energia a questo blocco. -block.repairturret.name = torretta di riparazione -block.repairturret.fulldescription = Ripara i blocchi danneggiati vicini nel raggio di azione a un ritmo lento. Utilizza piccole quantità di energia. -block.megarepairturret.name = torretta di riparazione II -block.megarepairturret.fulldescription = Ripara i blocchi vicini danneggiati nel raggio di portata a ritmo moderato. Usa il potere. -block.shieldgenerator.name = generatore di scudi -block.shieldgenerator.fulldescription = Un blocco difensivo avanzato. Fa da scudo per tutti i blocchi in un raggio dalla posizione. Utilizza l'energia a una velocità ridotta quando è inattivo, ma scarica rapidamente energia sul contatto con i proiettili. -block.door.name = porta -block.door.fulldescription = Un blocco che può essere aperto e chiuso toccandolo. -block.door-large.name = grande porta -block.door-large.fulldescription = Un blocco che può essere aperto e chiuso toccandolo. -block.conduit.name = Condotto -block.conduit.fulldescription = Blocco di trasporto liquido di base. Funziona come un trasportatore, ma con liquidi. Ideale per pompe o altri condotti. Può essere usato come un ponte sui liquidi per nemici e giocatori. -block.pulseconduit.name = condotto di impulso -block.pulseconduit.fulldescription = Blocco di trasporto di liquidi avanzato. Trasporta i liquidi più velocemente e immagazzina più dei condotti standard. -block.liquidrouter.name = router liquido -block.liquidrouter.fulldescription = Funziona in modo simile a un router. Accetta input liquidi da un lato e li invia agli altri lati. Utile per separare il liquido da un singolo condotto in più condotti. -block.conveyor.name = trasportatore -block.conveyor.fulldescription = Blocco di trasporto basico. Sposta gli oggetti in avanti e li deposita automaticamente in torrette o crafters. Ruotabile. Può essere usato come un ponte sui liquidi per nemici e giocatori. -block.steelconveyor.name = trasportatore d'acciaio -block.steelconveyor.fulldescription = Blocco avanzato di trasporto. Sposta gli oggetti più velocemente rispetto ai trasportatori standard. -block.poweredconveyor.name = trasportatore di impulsi -block.poweredconveyor.fulldescription = Il blocco di trasporto di ultima generazione. Sposta gli oggetti più velocemente dei trasportatori in acciaio. -block.router.name = router -block.router.fulldescription = Accetta elementi da una direzione e li invia a 3 altre direzioni. Può anche memorizzare una certa quantità di oggetti. Utile per dividere i materiali da un trapano a più torrette. -block.junction.name = giunzione -block.junction.fulldescription = Funziona come un ponte per due nastri trasportatori che la attraversono. Utile in situazioni con due diversi trasportatori che trasportano materiali diversi in luoghi diversi. -block.conveyortunnel.name = tunnel di trasporto -block.conveyortunnel.fulldescription = Trasporta oggetti sotto blocchi. Per utilizzare, posizionare un tunnel che conduce nel blocco da scavare sotto il tunnel e uno sull'altro lato. Assicurarsi che entrambe le gallerie siano rivolte in direzioni opposte, cioè verso i blocchi in cui vengono immesse o in uscita. -block.liquidjunction.name = giunzione liquida -block.liquidjunction.fulldescription = Funziona come un ponte per due condotti di attraversamento. Utile in situazioni con due condotti diversi che trasportano liquidi diversi in luoghi diversi. -block.liquiditemjunction.name = giunzione di oggetti liquidi -block.liquiditemjunction.fulldescription = Funziona come un ponte per attraversare condutture e trasportatori. -block.powerbooster.name = power booster -block.powerbooster.fulldescription = Distribuisce l'energia a tutti i blocchi entro il suo raggio. -block.powerlaser.name = laser energetico -block.powerlaser.fulldescription = Crea un laser che trasmette energia al blocco di fronte ad esso. Non genera alcuna energia. Ideale per generatori o altri laser. -block.powerlaserrouter.name = router laser -block.powerlaserrouter.fulldescription = Laser che distribuisce la potenza in tre direzioni contemporaneamente. Utile in situazioni in cui è necessario alimentare più blocchi da un generatore. -block.powerlasercorner.name = angolo laser -block.powerlasercorner.fulldescription = Laser che distribuisce la potenza in due direzioni contemporaneamente. Utile in situazioni in cui è necessario alimentare più blocchi da un generatore e un router è impreciso. -block.teleporter.name = teletrasporto -block.teleporter.fulldescription = Blocco avanzato di trasporto dell'elemento. I teletrasportatori immettono gli oggetti ad altri teletrasportatori dello stesso colore. Non fa nulla se non esistono teletrasportatori dello stesso colore. Se esistono più teletrasporti dello stesso colore, ne viene selezionato uno casuale. Usa l'energia. Tocca per cambiare colore. -block.sorter.name = sorter -block.sorter.fulldescription = Ordina l'oggetto per tipo di materiale. Il materiale da accettare è indicato dal colore nel blocco. Tutti gli articoli che corrispondono al materiale di ordinamento vengono emessi in avanti, tutto il resto viene emesso a sinistra e a destra. -block.core.name = Centro -block.pump.name = pompa -block.pump.fulldescription = Pompa di liquidi da un blocco sorgente - di solito acqua, lava o petrolio. Emette liquido nei condotti nelle vicinanze. -block.fluxpump.name = pompaflux -block.fluxpump.fulldescription = Una versione avanzata della pompa. Memorizza più liquido e pompa il liquido più velocemente. -block.smelter.name = fonderia -block.smelter.fulldescription = Il blocco di lavorazione essenziale. Quando immesso 1 ferro e 1 carbone come combustibile, emette un acciaio. Si consiglia di inserire ferro e carbone su diverse cinghie per evitare l'intasamento. -block.crucible.name = crogiuolo -block.crucible.fulldescription = Un blocco di lavorazione avanzato. Immettendo 1 titanio, 1 acciaio e 1 carbone come combustibile, emette un diridio. Si consiglia di inserire carbone, acciaio e titanio su nastri diversi per evitare l'intasamento. -block.coalpurifier.name = estrattore di carbone -block.coalpurifier.fulldescription = Un blocco estrattore di base. Emette carbone quando viene fornito con grandi quantità di acqua e pietra. -block.titaniumpurifier.name = estrattore di titanio -block.titaniumpurifier.fulldescription = Un blocco estrattore standard. Produce il titanio quando viene fornito con grandi quantità di acqua e ferro. -block.oilrefinery.name = raffineria d'petrolio -block.oilrefinery.fulldescription = Affina grandi quantità di petrolio in oggetti di carbone. Utile per alimentare torrette a base di carbone quando le vene del carbone scarseggiano. -block.stoneformer.name = forma pietre -block.stoneformer.fulldescription = Solidifica la lava producendo pietra. Utile per produrre enormi quantità di pietra per depuratori di carbone. -block.lavasmelter.name = fonderia a lava -block.lavasmelter.fulldescription = Usa la lava per convertire il ferro in acciaio. Un'alternativa alle smelterie. Utile in situazioni in cui il carbone è scarso. -block.stonedrill.name = trapano di pietra -block.stonedrill.fulldescription = Il trapano essenziale. Se posizionato su delle piastrelle di pietra, emette una pietra a un ritmo lento indefinitamente. -block.irondrill.name = trapano di ferro -block.irondrill.fulldescription = Un trapano di base. Quando viene posizionato su delle piastrelle con il minerale ferro, emette il ferro a un ritmo lento indefinitamente. -block.coaldrill.name = trivella di carbone -block.coaldrill.fulldescription = Un trapano di base. Se posizionato su delle piastrelle di carbone, produce a tempo indeterminato il carbone a un ritmo lento. -block.uraniumdrill.name = trapano all'uranio -block.uraniumdrill.fulldescription = Un trapano avanzato. Se posizionato su delel piastrelle con dell'uranio, emette l'uranio a un ritmo lento indefinitamente. -block.titaniumdrill.name = trapano in titanio -block.titaniumdrill.fulldescription = Un trapano avanzato. Se posizionato su delle piastrelle di titanio, emette il titanio a un ritmo lento indefinitamente. -block.omnidrill.name = omnidrill -block.omnidrill.fulldescription = L'ultimo trapano. Trapanerà qualsiasi minerale su cui è posizionato ad un ritmo rapido. -block.coalgenerator.name = generatore di carbone -block.coalgenerator.fulldescription = Il generatore essenziale. Genera potenza dal carbone. Emette potenza come laser sui suoi 4 lati. -block.thermalgenerator.name = generatore termico -block.thermalgenerator.fulldescription = Genera energia dalla lava. Emette energia come laser sui suoi 4 lati. -block.combustiongenerator.name = generatore a combustione -block.combustiongenerator.fulldescription = Genera energia dall'petrolio. Emette energia come laser sui suoi 4 lati. -block.rtgenerator.name = Generatore RTG -block.rtgenerator.fulldescription = Genera piccole quantità di energia dal decadimento radioattivo dell'uranio. Emette potenza come laser sui suoi 4 lati. -block.nuclearreactor.name = reattore nucleare -block.nuclearreactor.fulldescription = Una versione avanzata del Generatore RTG e il massimo generatore di energia. Genera potenza dall'uranio. Richiede un raffreddamento costante dell'acqua. Altamente volatile; esploderà violentemente se vengono fornite quantità insufficienti di refrigerante. -block.turret.name = torretta -block.turret.fulldescription = Una torretta semplice ed economica. Usa la pietra per le munizioni. Ha un raggio leggermente superiore rispetto alla doppia torretta. -block.doubleturret.name = doppia torretta -block.doubleturret.fulldescription = Una versione leggermente più potente della torretta. Usa la pietra per le munizioni. Fa molto più danni, ma ha un raggio più basso. Spara due proiettili. -block.machineturret.name = torretta di gattling -block.machineturret.fulldescription = Una torretta standard a tutto tondo. Usa il ferro per le munizioni. Ha una velocità di fuoco veloce con danni decenti. -block.shotgunturret.name = torretta di splitter -block.shotgunturret.fulldescription = Una torretta standard. Usa il ferro per le munizioni. Spara una diffusione di 7 proiettili. Gittata inferiore, ma maggiore danno inflitto rispetto alla torretta gattling. -block.flameturret.name = lanciafiamme -block.flameturret.fulldescription = Torretta avanzata a distanza ravvicinata. Usa carbone per munizioni. Ha una portata molto bassa, ma un danno molto alto. Buono per luoghi chiusi. Consigliato per essere usato dietro i muri. -block.sniperturret.name = torretta ellettromagnetica -block.sniperturret.fulldescription = Torretta avanzata a lungo raggio. Utilizza l'acciaio come munizioni. Danno molto alto, ma bassa velocità di fuoco. Costoso da usare, ma può essere posizionato lontano dalle linee nemiche a causa della sua portata. -block.mortarturret.name = torretta di sfogo -block.mortarturret.fulldescription = Torretta a getto d'acqua avanzato a bassa precisione. Usa carbone per munizioni. Spara una raffica di proiettili che esplodono in shrapnel. Utile per grandi folle di nemici. -block.laserturret.name = torretta laser -block.laserturret.fulldescription = Avanzata torretta a bersaglio singolo. Usa l'energia Buona torretta a medio raggio a tutto tondo. Ingaggio singolo. Non manca mai il bersaglio. -block.waveturret.name = Torretta tesla -block.waveturret.fulldescription = Torretta multi-target avanzata. Usa l'energia. Gamma media Non manca mai. Danno basso, ma può colpire più nemici contemporaneamente con dei fulmini a catena. -block.plasmaturret.name = torretta a plasma -block.plasmaturret.fulldescription = Versione altamente avanzata del lanciafiamme. Usa il carbone come munizione. Danno molto alto, da basso a medio raggio. -block.chainturret.name = torretta a catena -block.chainturret.fulldescription = L'ultima torretta a fuoco rapido. Usa l'uranio come munizione. Spara grossi proiettili ad un alto tasso di fuoco. Gamma media Si estende su più tessere. Estremamente duro. -block.titancannon.name = cannone di titano -block.titancannon.fulldescription = L'ultima torretta a lungo raggio. Usa l'uranio come munizione. Spara grossi proiettili di schizzi a una velocità media di fuoco. Lungo raggio. Si estende su più tessere. Estremamente duro. -block.playerspawn.name = spawngiocatore -block.enemyspawn.name = spawnnemico +text.about=Creato da [ROYAL]Anuken[] - [SKY]anukendev@gmail.com[]\nOriginariamente era una voce nel [orange]GDL[] Metal Monstrosity Jam.\n\n Crediti:\n - SFX realizzato con [YELLOW]bfxr [] \n - Musica creata da [GREEN]RoccoW[] / trovata su [lime]FreeMusicArchive.org[]\n\n Un ringraziamento speciale a:\n - [coral]MitchellFJN []: esteso test del gioco e feedback\n - [sky]Luxray5474 []: lavorazione della wiki, contributi col codice\n - [lime]Epowerj []: sistema di costruzione del codice, icone\n - Tutti i beta tester su itch.io e Google Play\n +text.credits=Crediti +text.discord=Unisciti sul server discord di mindustry! +text.link.discord.description=la chatroom ufficiale del server discord di Mindustry +text.link.github.description=Codice sorgente del gioco +text.link.dev-builds.description=Build di sviluppo versioni instabili +text.link.trello.description=Scheda ufficiale trello per funzionalità pianificate +text.link.itch.io.description=pagina di itch.io con download per PC e versione web +text.link.google-play.description=Elenco di Google Play Store +text.link.wiki.description=wiki ufficiale di Mindustry +text.linkfail=Impossibile aprire il link! L'URL è stato copiato nella tua bacheca. +text.editor.web=La versione web non supporta l'editor! Scarica il gioco per usarlo. +text.multiplayer.web=Questa versione del gioco non supporta il multiplayer! Per giocare in multiplayer dal tuo browser, usa il link "versione web multiplayer" nella pagina itch.io. +text.gameover=Il nucleo è stato distrutto. +text.highscore=[YELLOW]Nuovo record! +text.lasted=Sei durato fino all'onda +text.level.highscore=Migliore: [accent]{0} +text.level.delete.title=Conferma Eliminazione +text.level.select=Selezione del livello +text.level.mode=Modalità di gioco: +text.savegame=Salva +text.loadgame=Carica +text.joingame=Gioca MP +text.newgame=Nuovo gioco +text.quit=Esci +text.about.button=Informazioni +text.name=Nome: +text.public=Pubblico +text.players={0} giocatori online +text.server.player.host={0} (host) +text.players.single={0} giocatori online +text.server.mismatch=Errore nel pacchetto: possibile discrepanza nella versione client / server. Assicurati che tu e l'host abbiate l'ultima versione di Mindustry! +text.server.closing=[accent]Chiusura server ... +text.server.kicked.kick=Sei stato cacciato dal server! +text.server.kicked.invalidPassword=10468 = Password non valida. +text.server.kicked.clientOutdated=Versione del client obsoleta! Aggiorna il tuo gioco! +text.server.kicked.serverOutdated=Server obsoleto! Chiedi all'host di aggiornare! +text.server.kicked.banned=Sei stato bannato su questo server. +text.server.kicked.recentKick=Sei stato cacciato di recente. Attendi prima di connetterti di nuovo. +text.server.connected={0} si è connesso +text.server.disconnected={0} si è disconnesso +text.nohost=Impossibile hostare il server con una mappa personalizzata! +text.host.info=Il pulsante [accent]hos [] ospita un server sulle porte [scarlet]6567[] e [scarlet]656.[] Chiunque sulla stessa [LIGHT_GRAY]connessione wifi o rete locale[] dovrebbe essere in grado di vedere il proprio server nel proprio elenco server.\n\n Se vuoi che le persone siano in grado di connettersi ovunque tramite IP, è richiesto il [accent]port forwarding[]. \n\n[LIGHT_GRAY]Nota: se qualcuno sta riscontrando problemi durante la connessione al gioco LAN, assicurati di aver consentito a Mindustry di accedere alla rete locale nelle impostazioni del firewall. +text.join.info=Qui è possibile inserire un [accent]IP del server[] a cui connettersi, o scoprire [accento]un server sulla rete locale[] disponibile.\n Sono supportati sia il multiplayer LAN che WAN. \n\n[LIGHT_GRAY]Nota: non esiste un elenco di server globali automatici; se si desidera connettersi a qualcuno tramite IP, è necessario chiedere all'host il proprio IP. +text.hostserver=Server host +text.host=Host +text.hosting=[accento] Apertura del server ... +text.hosts.refresh=Aggiorna +text.hosts.discovering=Scoperta partite LAN +text.server.refreshing=Aggiornamento server +text.hosts.none=[lightgray]Nessuna partita LAN trovata! +text.host.invalid=[scarlet]Impossibile connettersi all'host. +text.server.friendlyfire=Fuoco amico +text.trace=Trace Player +text.trace.playername=Nome del giocatore: [accent]{0} +text.trace.ip=IP: [accent]{0} +text.trace.id=ID univoco: [accent]{0} +text.trace.android=Client Android: [accent] {0} +text.trace.modclient=Cliente personalizzato: [accent]{0} +text.trace.totalblocksbroken=Totale blocchi interrotti: [accent]{0} +text.trace.structureblocksbroken=Blocchi strutturali distrutti: [accent]{0} +text.trace.lastblockbroken=Ultimo blocco distrutto: [accent]{0} +text.trace.totalblocksplaced=Totale blocchi posizionati: [accent]{0} +text.trace.lastblockplaced=Ultimo blocco inserito: [accent]{0} +text.invalidid=ID cliente non valido! Invia una segnalazione di bug. +text.server.bans=Lista Ban +text.server.bans.none=Nessun giocatore bandito trovato! +text.server.admins=Amministratori +text.server.admins.none=Nessun amministratore trovato! +text.server.add=Aggiungi server +text.server.delete=Sei sicuro di voler eliminare questo server? +text.server.hostname=Host: {0} +text.server.edit=Modifica server +text.server.outdated=[crimson]Server obsoleto![] +text.server.outdated.client=[crimson]Client obsoleto![] +text.server.version=[lightgray]Versione: {0} +text.server.custombuild=[yellow] Costruzione personalizzata +text.confirmban=Sei sicuro di voler bandire questo giocatore? +text.confirmunban=Sei sicuro di voler sbloccare questo giocatore? +text.confirmadmin=Sei sicuro di voler rendere questo giocatore un amministratore? +text.confirmunadmin=Sei sicuro di voler rimuovere lo stato di amministratore da questo player? +text.joingame.byip=Unisciti a IP ... +text.joingame.title=Unisciti alla Partita +text.joingame.ip=IP: +text.disconnect=Disconnesso. +text.disconnect.data=Errore nel caricamento i dati del mondo! +text.connecting=[accent]Connessione in corso ... +text.connecting.data=[accent]Caricamento dei dati del mondo ... +text.connectfail=[crimson] Impossibile connettersi al server: [orange] {0} +text.server.port=Porta: +text.server.addressinuse=Indirizzo già in uso! +text.server.invalidport=Numero di porta non valido! +text.server.error=[crimson]Errore nell'hosting del server: [orange] {0} +text.tutorial.back=< Prec +text.tutorial.next=Succ > +text.save.new=Nuovo Salvataggio +text.save.overwrite=Sei sicuro di voler sovrascrivere questo salvataggio? +text.overwrite=Sostituisci +text.save.none=Nessun salvataggio trovato! +text.saveload=[Accent]Salvataggio ... +text.savefail=Salvataggio del gioco non riuscito! +text.save.delete.confirm=Sei sicuro di voler eliminare questo salvataggio? +text.save.delete=Elimina +text.save.export=Esporta Salva +text.save.import.invalid=[orange]Questo salvataggio non è valido! +text.save.import.fail=[crimson]Impossibile importare salvataggio: [orange]{0} +text.save.export.fail=[crimson]Impossibile esportare il salvataggio: [orange]{0} +text.save.import=Importa Salvataggio +text.save.newslot=Salva nome: +text.save.rename=Rinomina +text.save.rename.text=Nuovo nome: +text.selectslot=Seleziona un salvataggio. +text.slot=[accent]Slot {0} +text.save.corrupted=[orang]File di salvataggio danneggiato o non valido! +text.empty= +text.on=Acceso +text.off=Spento +text.save.autosave=Salvataggio automatico: {0} +text.save.map=mappa +text.save.wave=Ondata: +text.save.difficulty=Difficolta: {0} +text.save.date=Ultimo salvataggio: {0} +text.confirm=Conferma +text.delete=Elimina +text.ok=OK +text.open=Apri +text.cancel=Annulla +text.openlink=Apri Link +text.copylink=Copia link +text.back=Indietro +text.quit.confirm=Sei sicuro di voler uscire? +text.changelog.title=Registro modifiche +text.changelog.loading=Ottenere il registro delle modifiche ... +text.changelog.error.android=[orange]Nota che il log delle modifiche non funziona su Android 4.4 e versioni precedenti! Ciò è dovuto a un bug interno di Android. +text.changelog.error=[scarlet]Errore durante il recupero del changelog! Controlla la tua connessione Internet. +text.changelog.current=[yellow][[Current version] +text.changelog.latest=[orange][[Latest version] +text.loading=[accent]Caricamento in corso ... +text.wave=[orange]Onda {0} +text.wave.waiting=Onda in {0} +text.waiting=In attesa... +text.enemies={0} Nemici +text.enemies.single={0} Nemico +text.loadimage=Carica immagine +text.saveimage=Salva Immagine +text.editor.badsize=[orange]Dimensioni dell'immagine non valide![]\n Dimensioni della mappa valide: {0} +text.editor.errorimageload=Errore durante il caricamento del file immagine:\n [orange]{0} +text.editor.errorimagesave=Errore durante il salvataggio del file immagine:\n [orange]{0} +text.editor.generate=Genera +text.editor.resize=Zomma o \nRiduci +text.editor.loadmap=Carica\nmappa +text.editor.savemap=Salva\nla mappa +text.editor.loadimage=Carica\nimmagine +text.editor.saveimage=Salva\nImmagine +text.editor.unsaved=[scarlet]Hai modifiche non salvate![]\nSei sicuro di voler uscire? +text.editor.brushsize=Dimensione del pennello: {0} +text.editor.noplayerspawn=Questa mappa non ha lo spawnpoint del giocatore! +text.editor.manyplayerspawns=Le mappe non possono avere più di un punto di spawn di un giocatore! +text.editor.manyenemyspawns=Non puoi avere più di {0} spawn nemici! +text.editor.resizemap=Ridimensiona la mappa +text.editor.resizebig=[Scarlet]Attenzione!\n[]Le mappe più grandi di 256 unità potrebbero causare del lag oltre ad essere instabili. +text.editor.mapname=Nome Mappa: +text.editor.overwrite=[Accent]Attenzione!\nQuesto sovrascrive una mappa esistente. +text.editor.selectmap=Seleziona una mappa da caricare: +text.width=Larghezza: +text.height=Altezza: +text.randomize=Randomizza +text.apply=Applicare +text.update=Aggiorna +text.menu=Menu +text.play=Gioca +text.load=Carica +text.save=Salva +text.language.restart=Riavvia il gioco affinché il cambiamento della lingua abbia effetto. +text.settings.language=Lingua +text.settings=Impostazioni +text.tutorial=Lezioni +text.editor=Editor +text.mapeditor=Editor delle mappe +text.donate=Dona +text.settings.reset=Resetta Alle Impostazioni Predefinite +text.settings.controls=Controlli +text.settings.game=Gioco +text.settings.sound=Suono +text.settings.graphics=Grafica +text.upgrades=Miglioramenti +text.purchased=[LIME]Creato! +text.weapons=Armi +text.paused=In pausa +text.info.title=[Accent]Informazioni +text.error.title=[crimson]Si è verificato un errore +text.error.crashmessage=[SCARLET]Si è verificato un errore imprevisto che ha causato un arresto anomalo.[] Si prega di segnalare le circostanze esatte in cui questo errore si è verificato allo sviluppatore:\n[ORANGE]anukendev@gmail.com[] +text.error.crashtitle=Si è verificato un errore +text.blocks.blockinfo=Informazioni sul blocco +text.blocks.powercapacity=Capacità energetica +text.blocks.powershot=Danno/Colpo +text.blocks.size=Grandezza +text.blocks.liquidcapacity=Capacità del liquido +text.blocks.maxitemssecond=Oggetti massimi/secondo +text.blocks.powerrange=Raggio Energia +text.blocks.itemcapacity=Capacità oggetto +text.blocks.inputliquid=Ingresso del liquido +text.blocks.inputitem=Ingresso Oggetto +text.blocks.explosive=Altamente esplosivo! +text.blocks.health=Salute +text.blocks.inaccuracy=inesattezza +text.blocks.shots=Colpi +text.blocks.inputcapacity=Capacità di ingresso +text.blocks.outputcapacity=Capacità di uscita +setting.difficulty.easy=facile +setting.difficulty.normal=medio +setting.difficulty.hard=difficile +setting.difficulty.insane=Folle +setting.difficulty.purge=Epurazione +setting.difficulty.name=Difficoltà: +setting.screenshake.name=Screen Shake +setting.smoothcam.name=Smooth Camera +setting.indicators.name=Indicatori nemici +setting.effects.name=Visualizza effetti +setting.sensitivity.name=Sensibilità del controllore. +setting.saveinterval.name=Intervallo di salvataggio automatico +setting.seconds={0} Secondi +setting.fullscreen.name=Schermo Intero +setting.multithread.name=multithreading +setting.fps.name=Mostra FPS +setting.vsync.name=Sincronizzazione Verticale +setting.lasers.name=Mostra Energia Dei Laser +setting.healthbars.name=Mostra barra della salute delle entità +setting.pixelate.name=Schermo Pixelate +setting.musicvol.name=Volume Musica +setting.mutemusic.name=Musica muta +setting.sfxvol.name=Volume SFX +setting.mutesound.name=Suono muto +map.maze.name=labirinto +map.fortress.name=fortezza +map.sinkhole.name=dolina +map.caves.name=grotte +map.volcano.name=vulcano +map.caldera.name=caldera +map.scorch.name=bruciatura +map.desert.name=Deserto +map.island.name=Isola +map.grassland.name=Prateria +map.tundra.name=Tundra +map.spiral.name=spirale +map.tutorial.name=Tutorial +text.keybind.title=Configurazione Tasti +keybind.move_x.name=move_x +keybind.move_y.name=move_y +keybind.select.name=seleziona +keybind.break.name=rompere +keybind.shoot.name=sparare +keybind.zoom_hold.name=zoom_hold +keybind.zoom.name=zoom +keybind.block_info.name=Informazioni blocco +keybind.menu.name=menu +keybind.pause.name=pausa +keybind.dash.name=corsa +keybind.chat.name=Chat +keybind.player_list.name=lista_giocatori +keybind.console.name=console +keybind.rotate_alt.name=rotate_alt +keybind.rotate.name=Ruotare +mode.text.help.title=Descrizione delle modalità +mode.waves.name=onde +mode.waves.description=modalità normale. risorse limitate e onde in entrata automatiche. +mode.sandbox.name=Sandbox +mode.sandbox.description=risorse infinite e nessun timer per le onde. +mode.freebuild.name=freebuild +mode.freebuild.description=risorse limitate e nessun timer per le onde. +item.stone.name=pietra +item.coal.name=carbone +item.titanium.name=titanio +item.sand.name=sabbia +liquid.water.name=acqua +liquid.lava.name=lava +liquid.oil.name=petrolio +block.door.name=porta +block.door-large.name=grande porta +block.conduit.name=Condotto +block.pulseconduit.name=condotto di impulso +block.liquidrouter.name=router liquido +block.conveyor.name=trasportatore +block.router.name=router +block.junction.name=giunzione +block.liquidjunction.name=giunzione liquida +block.sorter.name=sorter +block.smelter.name=fonderia +text.web.unsupported=The web version does not support this feature! Download the game to use it. +text.host.web=The web version does not support hosting games! Download the game to use this feature. +text.map.delete=Are you sure you want to delete the map "[orange]{0}[]"? +text.construction.title=Block Construction Guide +text.construction=You've just selected [accent]block construction mode[].\n\nTo begin placing, simply tap a valid location near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Shift the selection[] by holding and dragging any block in the selection.\n- [accent]Place blocks in a line[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel construction or selection[] by pressing the X at the bottom left. +text.deconstruction.title=Block Deconstruction Guide +text.deconstruction=You've just selected [accent]block deconstruction mode[].\n\nTo begin breaking, simply tap a block near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin de-constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Remove blocks in an area[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel deconstruction or selection[] by pressing the X at the bottom left. +text.showagain=Don't show again next session +text.unlocks=Unlocks +text.addplayers=Add/Remove Players +text.maps=Maps +text.maps.none=[LIGHT_GRAY]No maps found! +text.unlocked=New Block Unlocked! +text.unlocked.plural=New Blocks Unlocked! +text.server.rollback=Rollback +text.server.rollback.numberfield=Rollback Amount: +text.blocks.editlogs=Edit Logs +text.block.editlogsnotfound=[red]There are no edit logs for this location. +text.server.kicked.fastShoot=You are shooting too quickly. +text.server.kicked.nameInUse=There is someone with that name\nalready on this server. +text.server.kicked.nameEmpty=Your name must contain at least one character or number. +text.server.kicked.idInUse=You are already on this server! Connecting with two accounts is not permitted. +text.server.kicked.customClient=This server does not support custom builds. Download an official version. +text.changelog.error.ios=[orange]The changelog is currently not supported in iOS. +text.saving=[accent]Saving... +text.unknown=Unknown +text.custom=Custom +text.builtin=Built-In +text.map.delete.confirm=Are you sure you want to delete this map? This action cannot be undone! +text.map.random=[accent]Random Map +text.map.nospawn=This map does not have any cores for the player to spawn in! Add a [ROYAL]blue[] core to this map in the editor. +text.editor.slope=\\ +text.editor.openin=Open In Editor +text.editor.oregen=Ore Generation +text.editor.oregen.info=Ore Generation: +text.editor.mapinfo=Map Info +text.editor.author=Author: +text.editor.description=Description: +text.editor.name=Name: +text.editor.teams=Teams +text.editor.elevation=Elevation +text.editor.saved=Saved! +text.editor.save.noname=Your map does not have a name! Set one in the 'map info' menu. +text.editor.save.overwrite=Your map overwrites a built-in map! Pick a different name in the 'map info' menu. +text.editor.import.exists=[scarlet]Unable to import:[] a built-in map named '{0}' already exists! +text.editor.import=Import... +text.editor.importmap=Import Map +text.editor.importmap.description=Import an already existing map +text.editor.importfile=Import File +text.editor.importfile.description=Import an external map file +text.editor.importimage=Import Terrain Image +text.editor.importimage.description=Import an external map image file +text.editor.export=Export... +text.editor.exportfile=Export File +text.editor.exportfile.description=Export a map file +text.editor.exportimage=Export Terrain Image +text.editor.exportimage.description=Export a map image file +text.editor.overwrite.confirm=[scarlet]Warning![] A map with this name already exists. Are you sure you want to overwrite it? +text.fps=FPS: {0} +text.tps=TPS: {0} +text.ping=Ping: {0}ms +text.settings.rebind=Rebind +text.yes=Yes +text.no=No +text.blocks.targetsair=Targets Air +text.blocks.itemspeed=Units Moved +text.blocks.shootrange=Range +text.blocks.poweruse=Power Use +text.blocks.inputitemcapacity=Input Item Capacity +text.blocks.outputitemcapacity=Input Item Capacity +text.blocks.maxpowergeneration=Max Power Generation +text.blocks.powertransferspeed=Power Transfer +text.blocks.craftspeed=Production Speed +text.blocks.inputliquidaux=Aux Liquid +text.blocks.inputitems=Input Items +text.blocks.outputitem=Output Item +text.blocks.drilltier=Drillables +text.blocks.drillspeed=Base Drill Speed +text.blocks.liquidoutput=Liquid Output +text.blocks.liquiduse=Liquid Use +text.blocks.coolant=Coolant +text.blocks.coolantuse=Coolant Use +text.blocks.inputliquidfuel=Fuel Liquid +text.blocks.liquidfueluse=Liquid Fuel Use +text.blocks.reload=Reload +text.blocks.inputfuel=Fuel +text.blocks.fuelburntime=Fuel Burn Time +text.unit.blocks=blocks +text.unit.powersecond=power units/second +text.unit.liquidsecond=liquid units/second +text.unit.itemssecond=items/second +text.unit.pixelssecond=pixels/second +text.unit.liquidunits=liquid units +text.unit.powerunits=power units +text.unit.degrees=degrees +text.unit.seconds=seconds +text.unit.none= +text.unit.items=items +text.category.general=General +text.category.power=Power +text.category.liquids=Liquids +text.category.items=Items +text.category.crafting=Crafting +text.category.shooting=Shooting +setting.previewopacity.name=Placing Preview Opacity +setting.minimap.name=Show Minimap +content.item.name=Items +content.liquid.name=Liquids +content.unit-type.name=Units +content.recipe.name=Blocks +item.stone.description=A common raw material. Used for separating and refining into other materials, or melting into lava. +item.tungsten.name=Tungsten +item.tungsten.description=A common, but very useful structure material. Used in drills and heat-resistant blocks such as generators and smelteries. +item.lead.name=Lead +item.lead.description=A basic starter material. Used extensively in electronics and liquid transportation blocks. +item.coal.description=A common and readily available fuel. +item.carbide.name=Carbide +item.carbide.description=A tough alloy made with tungsten and carbon. Used in advanced transportation blocks and high-tier drills. +item.titanium.description=A rare super-light metal used extensively in liquid transportation, drills and aircraft. +item.thorium.name=Thorium +item.thorium.description=A dense, radioactive metal used as structural support and nuclear fuel. +item.silicon.name=Silicon +item.silcion.description=An extremely useful semiconductor, with applications in solar panels and many complex electronics. +item.plastanium.name=Plastanium +item.plastanium.description=A light, ductile material used in advanced aircraft and fragmentation ammunition. +item.phase-matter.name=Phase Matter +item.surge-alloy.name=Surge Alloy +item.biomatter.name=Biomatter +item.biomatter.description=A clump of organic mush; used for conversion into oil or as a basic fuel. +item.sand.description=A common material that is used extensively in smelting, both in alloying and as a flux. +item.blast-compound.name=Blast Compound +item.blast-compound.description=A volatile compound used in bombs and explosives. While it can burned as fuel, this is not advised. +item.pyratite.name=Pyratite +item.pyratite.description=An extremely flammable substance used in incendiary weapons. +liquid.cryofluid.name=Cryofluid +text.item.explosiveness=[LIGHT_GRAY]Explosiveness: {0} +text.item.flammability=[LIGHT_GRAY]Flammability: {0} +text.item.radioactivity=[LIGHT_GRAY]Radioactivity: {0} +text.item.fluxiness=[LIGHT_GRAY]Flux Power: {0} +text.item.hardness=[LIGHT_GRAY]Hardness: {0} +text.liquid.heatcapacity=[LIGHT_GRAY]Heat Capacity: {0} +text.liquid.viscosity=[LIGHT_GRAY]Viscosity: {0} +text.liquid.temperature=[LIGHT_GRAY]Temperature: {0} +block.tungsten-wall.name=Tungsten Wall +block.tungsten-wall-large.name=Large Tungsten Wall +block.carbide-wall.name=Carbide Wall +block.carbide-wall-large.name=Large Carbide Wall +block.thorium-wall.name=Thorium Wall +block.thorium-wall-large.name=Large Thorium Wall +block.duo.name=Duo +block.scorch.name=Scorch +block.hail.name=Hail +block.lancer.name=Lancer +block.titanium-conveyor.name=Titanium Conveyor +block.splitter.name=Splitter +block.splitter.description=Outputs items into two opposite directions immediately after they are recieved. +block.router.description=Splits items into all 4 directions. Can store items as a buffer. +block.distributor.name=Distributor +block.distributor.description=A splitter that can split items into 8 directions. +block.sorter.description=Sorts items. If an item matches the selection, it is allowed to pass. Otherwise, the item is outputted to the left and right. +block.overflow-gate.name=Overflow Gate +block.overflow-gate.description=A combination splitter and router that only outputs to the left and right if the front path is blocked. +block.bridgeconveyor.name=Bridge Conveyor +block.bridgeconveyor.description=A conveyor that can go over other blocks, for up to two total blocks. +block.arc-smelter.name=Arc Smelter +block.silicon-smelter.name=Silicon Smelter +block.phase-weaver.name=Phase Weaver +block.pulverizer.name=Pulverizer +block.cryofluidmixer.name=Cryofluid Mixer +block.melter.name=Melter +block.incinerator.name=Incinerator +block.biomattercompressor.name=Biomatter Compressor +block.separator.name=Separator +block.centrifuge.name=Centrifuge +block.power-node.name=Power Node +block.power-node-large.name=Large Power Node +block.battery.name=Battery +block.battery-large.name=Large Battery +block.combustion-generator.name=Combustion Generator +block.turbine-generator.name=Turbine Generator +block.tungsten-drill.name=Tungsten Drill +block.carbide-drill.name=Carbide Drill +block.laser-drill.name=Laser Drill +block.water-extractor.name=Water Extractor +block.cultivator.name=Cultivator +block.dart-ship-factory.name=Dart Ship Factory +block.delta-mech-factory.name=Delta Mech Factory +block.dronefactory.name=Drone Factory +block.repairpoint.name=Repair Point +block.resupplypoint.name=Resupply Point +block.liquidtank.name=Liquid Tank +block.bridgeconduit.name=Bridge Conduit +block.mechanical-pump.name=Mechanical Pump +block.itemsource.name=Item Source +block.itemvoid.name=Item Void +block.liquidsource.name=Liquid Source +block.powervoid.name=Power Void +block.powerinfinite.name=Power Infinite +block.unloader.name=Unloader +block.sortedunloader.name=Sorted Unloader +block.vault.name=Vault +block.wave.name=Wave +block.swarmer.name=Swarmer +block.salvo.name=Salvo +block.ripple.name=Ripple +block.phase-conveyor.name=Phase Conveyor +block.bridge-conveyor.name=Bridge Conveyor +block.plastanium-compressor.name=Plastanium Compressor +block.pyratite-mixer.name=Pyratite Mixer +block.blast-mixer.name=Blast Mixer +block.solidifer.name=Solidifer +block.solar-panel.name=Solar Panel +block.solar-panel-large.name=Large Solar Panel +block.oil-extractor.name=Oil Extractor +block.javelin-ship-factory.name=Javelin Ship factory +block.drone-factory.name=Drone Factory +block.fabricator-factory.name=Fabricator Factory +block.repair-point.name=Repair Point +block.resupply-point.name=Resupply Point +block.pulse-conduit.name=Pulse Conduit +block.phase-conduit.name=Phase Conduit +block.liquid-router.name=Liquid Router +block.liquid-tank.name=Liquid Tank +block.liquid-junction.name=Liquid Junction +block.bridge-conduit.name=Bridge Conduit +block.rotary-pump.name=Rotary Pump +block.nuclear-reactor.name=Nuclear Reactor diff --git a/core/assets/bundles/bundle_ko.properties b/core/assets/bundles/bundle_ko.properties index e4ff2ff8c1..ecdc21feee 100644 --- a/core/assets/bundles/bundle_ko.properties +++ b/core/assets/bundles/bundle_ko.properties @@ -1,519 +1,515 @@ -text.about = 제작자 : [ROYAL]Anuken[] - [SKY]anukendev@gmail.com[] -text.credits = 제작자 -text.discord = Mindustry Discord 에 참여하세요! -text.changes = [SCARLET]주의!!\\n[]몇몇 게임 메커니즘이 변경되었습니다\\n\\n- [accent]텔레포터[] 는 이제 전력을 필요로 합니다.\\n- [accent]제련소[]와 [accent]도가니[]는 이제 최대 수용량을 가집니다.\\n- [accent]도가니[]는 이제 연료로 석탄을 필요로 합니다. -text.link.discord.description = 공식 Mindustry Discord 채팅방 -text.link.github.description = 게임 소스코드 -text.link.dev-builds.description = 개발중인 빌드 -text.link.trello.description = 다음 계획된 기능들을 게시한 공식 trello 보드 -text.link.itch.io.description = PC 버전 다운로드 HTML5 버전이 있는 itch.io 사이트 -text.link.google-play.description = Google 플레이 스토어 등록 정보 -text.link.wiki.description = 공식 Mindustry 위키 -text.linkfail = 링크를 여는데 실패했습니다!URL이 기기의 클립보드에 복사되었습니다. -text.editor.web = HTML5 버전은 에디터 기능을 지원하지 않습니다!게임을 다운로드 한 뒤에 사용 해 주세요. -text.web.unsupported = HTML5 버전은 이 기능을 지원하지 않습니다!게임을 다운로드 한 뒤에 사용 해 주세요. -text.multiplayer.web = 이 버전은 멀티플레이를 지원하지 않습니다!멀티플레이를 웹 브라우저에서 즐기고 싶다면, itch.io 페이지에서 \"multiplayer web version\" 링크로 들어가면 됩니다. -text.host.web = HTML5 버전은 게임 호스팅을 지원하지 않습니다!게임을 다운로드 한 뒤에 사용 해 주세요. -text.gameover = 코어가 파괴되었습니다. -text.highscore = [YELLOW]최고점수 달성! -text.lasted = 마지막으로 달성한 단계 -text.level.highscore = 최고 점수 : [accent]{0} -text.level.delete.title = 삭제 확인 -text.map.delete = 정말로 \"[orange]{0}[]\" 맵을 삭제하시겠습니까? -text.level.select = 맵 선택 -text.level.mode = 게임모드: -text.construction.title = 블록 배치 안내서 -text.construction = 당신은 [accent]블록 배치 모드[]를 선택하셨습니다.\n\n블록을 설치하고 싶으면, 자신의 건설 가능 범위 내에서 간단히 탭 하면 됩니다.\n일부 블록을 선택한 후에 확인 버튼을 누르면 배가 배치 작업을 진행할 것입니다.\n- [accent]블록을 삭제[]하고 싶다면 배치하고 싶은 영역을 탭 하세요. \n- [accent]블록을 넓게 배치[]하고 싶다면 배치하고 싶은 시작 영역을 길게 누르며 드래그 하면 됩니다.- [accent]블록을 한줄로 배치[]하고 싶다면 배치하고 싶은 시작 영역을 한번 탭 하고 길게 누르면서 드래그 하면 됩니다. \n- [accent]블록 배치 모드를 취소[]하고 싶다면 화면 하단 왼쪽에 있는 X 버튼을 누르면 됩니다. -text.deconstruction.title = 블록 삭제 안내서 -text.deconstruction = 당신은 [accent]블록 삭제 모드[]를 선택하셨습니다\n블록을 삭제하고 싶다면, 자신의 건설 가능 범위 내에서 간단히 탭 하면 됩니다.\n일부 블록을 선택한 후에 확인 버튼을 누르면 배가 파괴 작업을 진행할 것입니다.\n- [accent]블록을 삭제[]하고 싶다면 배치하고 싶은 영역을 탭 하세요- [accent]블록을 넓은 범위로 삭제[]하고 싶다면 배치하고 싶은 시작 영역을 길게 누르며 드래그 하면 됩니다.- [accent]블록 삭제 모드를 취소[]하고 싶다면 화면 하단 왼쪽에 있는 X 버튼을 누르면 됩니다. -text.showagain = 다음 세션에서 이 메세지를 표시하지 않습니다 -text.unlocks = 잠금 해제 -text.savegame = 게임 저장 -text.loadgame = 게임 불러오기 -text.joingame = 게임 참가\n -text.addplayers = 플레이어 추가/제거 -text.newgame = 새 게임 -text.quit = 나가기 -text.maps = 맵 -text.maps.none = [LIGHT_GRAY]맵을 찾을 수 없습니다! -text.about.button = 정보 -text.name = 이름: -text.unlocked = 새 블록이 잠금 해제되었습니다! -text.unlocked.plural = 새 블록이 잠금 해제되었습니다! -text.server.rollback = 롤백 -text.server.rollback.numberfield = 롤백 개수: -text.blocks.editlogs = 편집 기록 -text.block.editlogsnotfound = [red]이 위치에 대한 편집 기록이 없습니다. -text.public = 공용 -text.players = {0} 플레이어 온라인 -text.server.player.host = 호스트: {0} -text.players.single = {0} 플레이어 온라인 -text.server.mismatch = 패킷 오류: 클라이언트와 서버 버전이 일치하지 않습니다.자신이 서버를 호스트하거나 최신 버전을 사용 해 주세요! -text.server.closing = [accent]서버 닫는중... -text.server.kicked.kick = 당신은 서버에서 추방되었습니다! -text.server.kicked.fastShoot = 당신은 총을 너무 빨리 발사했습니다. -text.server.kicked.invalidPassword = 알 수 없는 비밀번호 입니다! -text.server.kicked.clientOutdated = 오래된 버전의 클라이언트 입니다! 게임을 업데이트 하세요! -text.server.kicked.serverOutdated = 오래된 버전의 서버입니다! 서버 호스트 관리자에게 문의하세요! -text.server.kicked.banned = 당신은 서버에서 밴 망치를 맞아 차단당했습니다. -text.server.kicked.recentKick = 당신은 방금 추방처리 되었습니다.잠시 기다린 후에 접속 해 주세요. -text.server.kicked.nameInUse = 이 닉네임이 이미 서버에서 사용중입니다. -text.server.kicked.nameEmpty = 닉네임에는 반드시 영어 또는 숫자가 있어야 합니다. -text.server.kicked.idInUse = 당신은 이미 서버에 접속중입니다! 다중 계정은 허용되지 않습니다. -text.server.kicked.customClient = 이 서버는 수정된 클라이언트를 지원하지 않습니다. 공식 버전을 사용하세요. -text.server.connected = {0} 님이 접속했습니다. -text.server.disconnected = {0} 님이 나갔습니다. -text.nohost = 커스텀 맵을 호스트 할 수 없습니다! -text.host.info = [accent]호스트[] 버튼은 현재 네트워크의 [scarlet]6567[] 과 [scarlet]6568[] 포트를 사용합니다.\n[LIGHY_GRAY]같은 Wi-Fi 또는 로컬 네트워크[] 에서 서버 목록을 볼 수 있습니다.\n\n만약 플레이어들이 이 IP를 통해 어디에서나 연결할 수 있게 하고 싶다면, 공유기 설정에서 [accent]포트 포워딩[]을 해야 합니다.\n\n[LIGHT_GRAY]참고 : LAN 게임 연결에 문제가 있는 사람이 있다면, 방화벽 설정에서 Mindustry 가 로컬 네트워크에 액세스하도록 허용했는지 확인 해 주세요. -text.join.info = 여기서 [accent]서버 IP[]를 입력하여 다른 서버에 접속할 수 있습니다.\n또는 [accent]로컬 네트워크(LAN)[] 서버를 검색하여 접속할 수 있습니다.\nLAN 및 WAN 멀티 플레이어 모두 지원됩니다.\n\n[LIGHT_GRAY]참고 : 여기에서는 자동으로 글로벌 서버를 추가하지 않습니다. IP로 다른 사람의 서버에 접속할려면 서버장에게 IP를 요청해야 합니다. -text.hostserver = 서버 열기 -text.host = 호스트 -text.hosting = [accent]서버 여는중.. -text.hosts.refresh = 새로고침 -text.hosts.discovering = LAN 게임 찾기 -text.server.refreshing = 서버 목록 새로고치는중... -text.hosts.none = [lightgray]LAN 게임을 찾을 수 없습니다! -text.host.invalid = [scarlet]서버에 연결할 수 없습니다! -text.server.friendlyfire = 팀킬 -text.trace = 플레이어 추적 -text.trace.playername = 플레이어 이름: [accent]{0} -text.trace.ip = IP : [accent] -text.trace.id = 고유 ID: [accent]{0} -text.trace.android = Android 클라이언트 : [accent]{0} -text.trace.modclient = 수정된 클라이언트 : [accent]{0} -text.trace.totalblocksbroken = 총 블럭 파괴 수: [accent]{0} -text.trace.structureblocksbroken = 총 구조 블럭 파괴 수: [accent]{0} -text.trace.lastblockbroken = 마지막으로 파괴한 블럭: [accent]{0} -text.trace.totalblocksplaced = 총 설치한 블럭 수: [accent]{0} -text.trace.lastblockplaced = 마지막으로 설치한 블록: [accent]{0} -text.invalidid = 잘못된 클라이언트 ID 입니다! 공식 Mindustry 으로 버그 보고서를 제출 해 주세요. -text.server.bans = 차단된 유저 -text.server.bans.none = 차단된 플레이어가 없습니다. -text.server.admins = 관리자들 -text.server.admins.none = 관리자가 없습니다! -text.server.add = 서버 추가 -text.server.delete = 이 서버를 삭제 하시겠습니까? -text.server.hostname = 호스트: {0} -text.server.edit = 서버 수정 -text.server.outdated = [crimson]서버 버전이 낮습니다![] -text.server.outdated.client = [Crimson]클라이언트 버전이 낮습니다![] -text.server.version = [lightgray] 버전 : {0} -text.server.custombuild = [노란색]수정된 빌드 -text.confirmban = 이 플레이어를 차단하시겠습니까? -text.confirmunban = 이 플레이어를 차단하시겠습니까? -text.confirmadmin = 이 플레이어를 관리자로 설정 하시겠습니까? -text.confirmunadmin = 이 플레이어의 관리자 상태를 해제하시겠습니까? -text.joingame.byip = IP를 입력해서 참가하기... -text.joingame.title = 게임 참가 -text.joingame.ip = IP: -text.disconnect = 서버와 연결이 해제되었습니다. -text.disconnect.data = 맵 데이터를 불러오지 못했습니다! -text.connecting = [accent]연결중... -text.connecting.data = [accent]월드 데이터 로딩중... -text.connectfail = [crimson]{0}[orange] 서버에 연결하지 못했습니다.[] -text.server.port = 포트: -text.server.addressinuse = 이 주소는 이미 사용중입니다! -text.server.invalidport = 포트 번호가 잘못되었습니다. -text.server.error = [crimson]{0}[orange]서버를 호스팅 하는데 오류가 발생했습니다.[] -text.tutorial.back = < 이전 -text.tutorial.next = 다음 > -text.save.new = 새로 저장\n -text.save.overwrite = 이 저장 슬롯을 덮어씌우겠습니까?\n -text.overwrite = 덮어쓰기\n -text.save.none = 저장 파일을 찾지 못했습니다!\n -text.saveload = [accent]저장중...\n -text.savefail = 게임을 저장하지 못했습니다!\n -text.save.delete.confirm = 이 저장파일을 삭제 하시겠습니까?\n -text.save.delete = 삭제\n -text.save.export = 저장파일 내보내기\n -text.save.import.invalid = [orange]저장파일이 유효한 파일이 아닙니다!\n\n다른 디바이스에 있는 커스텀 맵을 가져오는건 작동하지 않습니다.\n -text.save.import.fail = [crimson]저장파일을 불러오지 못함: [orange]{0}\n -text.save.export.fail = [crimson]저장파일을 내보내지 못함: [orange]{0} -text.save.import = 저장파일 불러오기\n -text.save.newslot = 저장 파일이름 :\n -text.save.rename = 이름 변경\n -text.save.rename.text = 새 이름 :\n -text.selectslot = 저장슬롯을 선택하십시오.\n -text.slot = [accent]{0}번째 슬롯\n -text.save.corrupted = [orange]저장파일이 손상되었습니다!\n -text.empty = <비어있음>\n -text.on = 켜기\n -text.off = 끄기\n -text.save.autosave = 자동저장: {0} -text.save.map = 맵 : -text.save.wave = {0} 단계 -text.save.difficulty = 난이도 : {0} -text.save.date = 마지막 저장 날짜 : {0} -text.confirm = 확인 -text.delete = 삭제\n -text.ok = 승인 -text.open = 열기 -text.cancel = 취소 -text.openlink = 링크 열기 -text.copylink = 링크 복사 -text.back = 뒤로가기 -text.quit.confirm = 정말로 종료하시겠습니까? -text.changelog.title = 변경사항 -text.changelog.loading = 변경사항 가져오는중... -text.changelog.error.android = [orange]게임 변경사항은 가끔 Android 4.4 이하에서 작동하지 않습니다.이것은 내부 Android 버그 때문입니다. -text.changelog.error.ios = [orange]현재 iOS에서는 변경 사항을 지원하지 않습니다. -text.changelog.error = [scarlet]게임 변경사항을 가져오는 중 오류가 발생했습니다![]\n인터넷 연결을 확인하십시오. -text.changelog.current = [orange][[현재 버전] -text.changelog.latest = [orange][[최신 버전] -text.loading = [accent]불러오는중... -text.saving = [accent]저장중...\n -text.wave = [orange]{0} 단계 -text.wave.waiting = 다음 단계 시작까지 {0}초 -text.waiting = 기다리는중... -text.enemies = 남은 몹 : {0} -text.enemies.single = 몹이 1마리 남아있음 -text.loadimage = 사진 불러오기 -text.saveimage = 사진 저장 -text.unknown = 알 수 없음 -text.custom = 커스텀 -text.builtin = 내장 -text.map.delete.confirm = 이 맵을 삭제하시겠습니까? 이 명령은 취소할 수 없습니다! -text.map.random = [accent]랜덤 맵 -text.map.nospawn = 이 맵에는 플레이어가 스폰 할 코어가 없습니다! 맵 편집기에서 [ROYAL]파란색[]코어를 맵에 추가하세요. -text.editor.slope = \\ -text.editor.openin = 편집기 열기 -text.editor.oregen = 광물 무작위 생성 -text.editor.oregen.info = 광물 무작위 생성: -text.editor.mapinfo = 맵 정보 -text.editor.author = 만든이: -text.editor.description = 설명: -text.editor.name = 이름: -text.editor.teams = 팀 -text.editor.elevation = 높이 -text.editor.badsize = [orange]사진 크기가 잘못되었습니다![]유효한 맵 크기 : {0} -text.editor.errorimageload = [orange]{0}[] 파일을 불러오는데 오류가 발생했습니다. -text.editor.errorimagesave = [orange]{0}[] 파일 저장중 오류가 발생했습니다. -text.editor.generate = 생성 -text.editor.resize = 크기 조정 -text.editor.loadmap = 맵 불러오기 -text.editor.savemap = 맵 저장 -text.editor.saved = 저장됨! -text.editor.save.noname = 지도에 이름이 없습니다! '맵 정보' 메뉴에서 설정하세요. -text.editor.save.overwrite = 이 맵의 이름은 기존에 있던 맵을 덮어씁니다! '맵 정보' 메뉴에서 다른 이름을 선택하세요. -text.editor.import.exists = [scarlet]맵을 불러올 수 없음:[] 기존에 있던 '{0}' 맵이 이미 존재합니다! -text.editor.import = 가져오기 -text.editor.importmap = 맵 가져오기 -text.editor.importmap.description = 이미 존재하는 맵 가져오기 -text.editor.importfile = 파일 가져오기 -text.editor.importfile.description = 외부 맵 파일 가져오기 -text.editor.importimage = 지형 사진 가져오기 -text.editor.importimage.description = 외부 맵 이미지 파일 가져오기 -text.editor.export = 내보내기 -text.editor.exportfile = 파일 내보내기 -text.editor.exportfile.description = 맵 파일 내보내기 -text.editor.exportimage = 지형 이미지 내보내기 -text.editor.exportimage.description = 맵 이미지 파일 내보내기 -text.editor.loadimage = 지형 가져오기 -text.editor.saveimage = 지형 내보내기 -text.editor.unsaved = [scarlet]변경사항을 저장하지 않았습니다![]\n정말로 나가시겠습니까?\n -text.editor.brushsize = 브러쉬 크기 : {0} -text.editor.noplayerspawn = 이 맵에는 플레이어의 스폰 지점이 없습니다! -text.editor.manyplayerspawns = 맵에는 플레이어 스폰 지점이 둘 이상 있을 수 없습니다! -text.editor.manyenemyspawns = {0} 개 이상의 몹 스폰 지점을 설정할 수 없습니다! -text.editor.resizemap = 맵 크기 조정 -text.editor.resizebig = [scarlet]경고![]맵 크기가 256보다 큰지도는 많은 랙을 유발할 수 있습니다. -text.editor.mapname = 맵 이름: -text.editor.overwrite = [accept]경고!이 명령은 기존 맵을 덮어씌우게 됩니다.\n -text.editor.overwrite.confirm = [scarlet]경고![] 이 이름을 가진 맵이 이미 있습니다. 덮어 쓰시겠습니까? -text.editor.selectmap = 불러올 맵 선택: -text.width = 넓이: -text.height = 높이: -text.randomize = 무작위 -text.apply = 적용 -text.update = 업데이트 -text.menu = 메뉴 -text.play = 플레이 -text.load = 불러오기 -text.save = 저장 -text.fps = {0} FPS -text.tps = {0} TPS -text.ping = 핑 : {0}ms -text.language.restart = 언어 설정을 적용하려면 게임을 다시 시작하십시오. -text.settings.language = 언어 -text.settings = 설정 -text.tutorial = 게임 방법 -text.editor = 편집기 -text.mapeditor = 맵 편집기 -text.donate = 기부 -text.settings.reset = 기본값으로 재설정 -text.settings.rebind = rebind -text.settings.controls = 컨트롤 -text.settings.game = 게임 -text.settings.sound = 소리 -text.settings.graphics = 화면 -text.upgrades = 업그레이드 -text.purchased = [LIME]생성됨! -text.weapons = 무기 -text.paused = 일시 정지 -text.respawn = 남은 부활시간 : -text.info.title = [accent]정보 -text.error.title = [crimson]오류가 발생했습니다. -text.error.crashmessage = [scarlet]예기치 않은 오류가 발생하여 게임이 강제 종료되었습니다![]\n이 오류가 발생한 정확한 상황을 개발자에게 알려주십시오. [ORANGE]anukendev@gmail.com[] -text.error.crashtitle = 오류가 발생했습니다. -text.mode.break = 파괴 모드: {0} -text.mode.place = 설치 모드 : {0} -placemode.hold.name = 한줄 -placemode.areadelete.name = 구역 -placemode.touchdelete.name = 터치 -placemode.holddelete.name = 길게 누름 -placemode.none.name = 없음 -placemode.touch.name = 터치 -placemode.cursor.name = 커서 -text.blocks.blockinfo = 블록 정보 -text.blocks.powercapacity = 최대 전력 용량 -text.blocks.powershot = 1발당 파워 소모량 -text.blocks.itemspeed = 유닛 이동 속도 -text.blocks.shootrange = 공격 범위 -text.blocks.size = 블록 크기 -text.blocks.liquidcapacity = 최대 액체 용량 -text.blocks.maxitemssecond = 최대 아이템 보관량 -text.blocks.powerrange = 전력 범위 -text.blocks.poweruse = 전력 사용 -text.blocks.inputitemcapacity = 입력 아이템 용량 -text.blocks.outputitemcapacity = 입력 아이템 용량 -text.blocks.itemcapacity = 아이템 용량 -text.blocks.maxpowergeneration = 최대 발전량 -text.blocks.powertransferspeed = 전력 전송량 -text.blocks.craftspeed = 생산 속도 -text.blocks.inputliquid = 입력 액체 -text.blocks.inputliquidaux = 보조 액체 -text.blocks.inputitem = 입력 아이템 -text.blocks.inputitems = 입력 아이템들 -text.blocks.outputitem = 출력 아이템 -text.blocks.drilltier = 드릴 -text.blocks.drillspeed = 기본 드릴 속도 -text.blocks.liquidoutput = 액체 출력 -text.blocks.liquiduse = 액체 사용 -text.blocks.explosive = 이게 터지면 펑 터지면서 주변 블록에게 피해를 입힙니다! -text.blocks.health = 체력 -text.blocks.inaccuracy = 빗맞을 확률 -text.blocks.shots = 총알 -text.blocks.reload = 재장전 -text.blocks.inputfuel = 연료 -text.blocks.fuelburntime = 연료 연소 시간 -text.blocks.inputcapacity = 입력 용량 -text.blocks.outputcapacity = 출력 용량 -text.unit.blocks = 블록들 -text.unit.powersecond = 초당 전력 단위 -text.unit.liquidsecond = 액체 단위 / 초 -text.unit.itemssecond = 항목 / 초 -text.unit.pixelssecond = 초당 픽셀 -text.unit.liquidunits = 액상 단위 -text.unit.powerunits = 전원 장치 -text.unit.degrees = 도 -text.unit.seconds = 초 -text.unit.none = -text.unit.items = 아이템 -text.category.general = 일반 -text.category.power = 전력 -text.category.liquids = 액체 -text.category.items = 아이템 -text.category.crafting = 제작 -text.category.shooting = 발사 -setting.difficulty.easy = 쉬움 -setting.difficulty.normal = 보통 -setting.difficulty.hard = 어려움 -setting.difficulty.insane = 미침 -setting.difficulty.purge = [#FE2E2E]대한[#2E2EFE]민국 -setting.difficulty.name = 난이도: -setting.screenshake.name = 화면 흔들기 -setting.smoothcam.name = 부드러운 카메라 -setting.indicators.name = 적 위치 표시 화살표 -setting.effects.name = 화면 효과 -setting.sensitivity.name = 컨트롤러 감도 -setting.saveinterval.name = 자동저장 간격 -setting.seconds = 초 -setting.fullscreen.name = 전체 화면 -setting.multithread.name = 멀티 스레딩 -setting.fps.name = FPS 표시 -setting.vsync.name = VSync -setting.lasers.name = 파워 레이져 표시 -setting.previewopacity.name = 미리보기 블럭 투명도 -setting.healthbars.name = 몹 체력바 표시 -setting.minimap.name = 미니맵 보기 -setting.pixelate.name = 화면 픽셀화 -setting.musicvol.name = 음악 크기 -setting.mutemusic.name = 음소거 -setting.sfxvol.name = SFX 볼륨 -setting.mutesound.name = 소리 끄기 -map.maze.name = 미로 -map.fortress.name = 요새 -map.sinkhole.name = 싱크홀 -map.caves.name = 동굴 -map.volcano.name = 화산 -map.caldera.name = 칼데라 -map.scorch.name = 타버림 -map.desert.name = 사막 -map.island.name = 섬 -map.grassland.name = 목초지 -map.tundra.name = 툰트라 -map.spiral.name = 나선 -map.tutorial.name = 게임 방법 -text.keybind.title = 키 바인딩 -keybind.move_x.name = move_x -keybind.move_y.name = move_y -keybind.select.name = 선택 -keybind.break.name = 파괴 -keybind.shoot.name = 사격 -keybind.zoom_hold.name = 길게눌러 확대 -keybind.zoom.name = 확대 -keybind.block_info.name = 블럭 정보 -keybind.menu.name = 메뉴 -keybind.pause.name = 일시중지 -keybind.dash.name = 달리기 -keybind.chat.name = 채팅 -keybind.player_list.name = 플레이어 목록 -keybind.console.name = 콘솔 -keybind.rotate_alt.name = 회전_alt -keybind.rotate.name = 회전 -mode.text.help.title = 도움말 -mode.waves.name = 단계 -mode.waves.description = 이것은 일반 모드입니다. 제한된 자원과 자동으로 다음 단계가 시작됩니다. -mode.sandbox.name = 샌드박스 -mode.sandbox.description = 무한한 자원과 다음 단계 시작을 위한 타이머가 없습니다. -mode.freebuild.name = 자유 건축 -mode.freebuild.description = 제한된 자원과 다음 단계 시작을 위한 타이머가 없습니다. -content.item.name = 아이템 -content.liquid.name = 액체 -content.unit-type.name = 종류 -content.recipe.name = 블록 -item.stone.name = 돌 -item.stone.description = 흔히 찾을 수 있는 자원. 바닥에서 돌을 캐거나 용암을 사용하여 얻을 수 있습니다. -item.tungsten.name = 텅스텐 -item.tungsten.description = 일반적이지만 매우 유용한 건축 재료. 드릴 및 생산 건물, 제련소와 같은 내열성 블록에 사용됩니다. -item.lead.name = 납 -item.lead.description = 기본적인 시작 자원. 전자 및 액체 수송 블록에서 광범위하게 사용됩니다. -item.coal.name = 석탄 -item.coal.description = 일반적이고 쉽게 이용할 수 있는 연료. -item.carbide.name = 합금 -item.carbide.description = 텅스텐과 탄소로 만든 합금. 고급 운송 블록 및 상위 티어 드릴에 사용됩니다. -item.titanium.name = 티타늄 -item.titanium.description = 물 운반이나 드릴, 비행기등에서 재료로 사용되는 자원입니다. -item.thorium.name = 토륨 -item.thorium.description = 건물 탄약 또는 핵연료로 사용되는 방사성 금속. -item.silicon.name = 규소 -item.silcion.description = 매우 유용한 반도체로, 태양 전지 패널과 복잡한 전자 제품에 응용할 수 있습니다. -item.plastanium.name = Plastanium -item.plastanium.description = 고급 항공기 및 분열 탄약에 사용되는 가벼운 연성 재료. -item.phase-matter.name = Phase Matter -item.surge-alloy.name = Surge Alloy -item.biomatter.name = Biomatter -item.biomatter.description = 유기농 덤불; 석유로 전환하거나 기본 연료로 사용됩니다. -item.sand.name = 모래 -item.sand.description = 합금 및 플럭스 모두에서 제련시 광범위하게 사용되는 일반적인 재료. -item.blast-compound.name = Blast Compound -item.blast-compound.description = 폭탄 및 폭발물에 사용되는 휘발성 화합물. 그것이 연료로 태울 수 있지만, 이것은 권고하지 않습니다. -item.pyratite.name = Pyratite -item.pyratite.description = 방화 용 무기에 사용되는 극히 가연성 물질. -liquid.water.name = 물 -liquid.lava.name = 용암 -liquid.oil.name = 석유 -liquid.cryofluid.name = Cryofluid -text.item.explosiveness = [LIGHT_GRAY]폭발력 : {0} -text.item.flammability = [LIGHT_GRAY]인화성 : {0} -text.item.radioactivity = [LIGHT_GRAY]방사능 : {0} -text.item.fluxiness = [LIGHT_GRAY]Flux Power : {0} -text.item.hardness = [LIGHT_GRAY]강도 : {0} -text.liquid.heatcapacity = [LIGHT_GRAY]열용량 : {0} -text.liquid.viscosity = [LIGHT_GRAY]점도 : {0} -text.liquid.temperature = [LIGHT_GRAY]온도 : {0} -block.tungsten-wall.name = 텅스텐 장벽 -block.tungsten-wall-large.name = 큰 텅스텐 벽 -block.carbide-wall.name = 합금벽 -block.carbide-wall-large.name = 대형 합금벽 -block.thorium-wall.name = 토륨 장벽 -block.thorium-wall-large.name = 대형 토륨 벽 -block.door.name = 문 -block.door-large.name = 큰 문 -block.duo.name = 샷건 -block.scorch.name = 물총 -block.hail.name = 헤이스트 -block.lancer.name = 팬선 -block.conveyor.name = 컨베이어 -block.titanium-conveyor.name = 티타늄 컨베이어 -block.junction.name = 교차기 -block.splitter.name = 쪼개는 도구 -block.splitter.description = 항목을받은 직후 두 개의 반대 방향으로 항목을 출력합니다. -block.router.name = 분배기 -block.router.description = 아이템을 넣으면 다른 방향으로 아이템을 번갈아서 내보냅니다. -block.multiplexer.name = 멀티플렉서 -block.multiplexer.description = 항목을 8 방향으로 분리 할 수있는 라우터. -block.sorter.name = 필터 -block.sorter.description = 아이템을 받아서 설정된 아이템일 경우 바로 앞으로 통과하며, 그렇지 않을 경우 옆으로 통과합니다. -block.overflowgate.name = 오버플로 게이트 -block.overflowgate.description = 정면 경로가 차단되면 왼쪽 및 오른쪽으로 만 출력하는 조합 스플리터 및 라우터. -block.bridgeconveyor.name = 터널 -block.bridgeconveyor.description = 최대 2블록을 건너 뛰고 자원을 운반하게 해 주는 블럭. -block.smelter.name = 제련소 -block.arc-smelter.name = 아크 제련소 -block.silicon-smelter.name = 실리콘 제련소 -block.phase-weaver.name = 위상 위버 -block.pulverizer.name = 분쇄기 -block.cryofluidmixer.name = 냉동고 혼합기 -block.melter.name = 멜터 -block.incinerator.name = 소각로 -block.biomattercompressor.name = 바이오 매터 압축기 -block.separator.name = 분리 기호 -block.centrifuge.name = 원심 분리기 -block.power-node.name = 전원 노드 -block.power-node-large.name = 대형 전원 노드 -block.battery.name = 배터리 -block.battery-large.name = 대형 배터리 -block.combustion-generator.name = 연소 발전기 -block.turbine-generator.name = 터빈 발전기 -block.tungsten-drill.name = 텅스텐 드릴 -block.carbide-drill.name = 초경 드릴 -block.laser-drill.name = 레이저 드릴 -block.water-extractor.name = 물 추출기 -block.cultivator.name = 경운기 -block.dart-ship-factory.name = 다트 선박 공장 -block.delta-mech-factory.name = 델타 메크 공장 -block.dronefactory.name = 드론 팩토리 -block.repairpoint.name = 수리 점 -block.resupplypoint.name = 재 공급 포인트 -block.conduit.name = 도관 -block.pulseconduit.name = 펄스 도관 -block.liquidrouter.name = 액체 라우터 -block.liquidtank.name = 액체 탱크 -block.liquidjunction.name = 액체 정션 -block.bridgeconduit.name = 브릿지 도관 -block.mechanical-pump.name = 기계 펌프 -block.itemsource.name = 품목 출처 -block.itemvoid.name = 아이템 무효 -block.liquidsource.name = 액체 소스 -block.powervoid.name = 무효 전력 -block.powerinfinite.name = 무한한 힘 -block.unloader.name = 언 로더 -block.sortedunloader.name = 정렬 된 언 로더 -block.vault.name = 둥근 천장 -block.wave.name = 웨이브 -block.swarmer.name = 스머머 -block.salvo.name = 살보 -block.ripple.name = 리플 -block.phase-conveyor.name = 상 컨베이어 -block.overflow-gate.name = 오버플로 게이트 -block.bridge-conveyor.name = 브릿지 컨베이어 -block.plastanium-compressor.name = 플라스터 늄 압축기 -block.pyratite-mixer.name = Pyratite 믹서 -block.blast-mixer.name = 블래스트 믹서 -block.solidifer.name = 고체 -block.solar-panel.name = 태양 전지 패널 -block.solar-panel-large.name = 대형 태양 전지판 -block.oil-extractor.name = 오일 추출기 -block.javelin-ship-factory.name = 창 던지기 선박 공장 -block.drone-factory.name = 드론 팩토리 -block.fabricator-factory.name = Fabricator 공장 -block.repair-point.name = 수리 점 -block.resupply-point.name = 재 공급 포인트 -block.pulse-conduit.name = 펄스 도관 -block.phase-conduit.name = 위상 도관 -block.liquid-router.name = 액체 라우터 -block.liquid-tank.name = 액체 탱크 -block.liquid-junction.name = 액체 정션 -block.bridge-conduit.name = 브릿지 도관 -block.rotary-pump.name = 로타리 펌프 +text.about=제작자 : [ROYAL]Anuken[] - [SKY]anukendev@gmail.com[] +text.credits=제작자 +text.discord=Mindustry Discord 에 참여하세요! +text.link.discord.description=공식 Mindustry Discord 채팅방 +text.link.github.description=게임 소스코드 +text.link.dev-builds.description=개발중인 빌드 +text.link.trello.description=다음 계획된 기능들을 게시한 공식 trello 보드 +text.link.itch.io.description=PC 버전 다운로드 HTML5 버전이 있는 itch.io 사이트 +text.link.google-play.description=Google 플레이 스토어 등록 정보 +text.link.wiki.description=공식 Mindustry 위키 +text.linkfail=링크를 여는데 실패했습니다!URL이 기기의 클립보드에 복사되었습니다. +text.editor.web=HTML5 버전은 에디터 기능을 지원하지 않습니다!게임을 다운로드 한 뒤에 사용 해 주세요. +text.web.unsupported=HTML5 버전은 이 기능을 지원하지 않습니다!게임을 다운로드 한 뒤에 사용 해 주세요. +text.multiplayer.web=이 버전은 멀티플레이를 지원하지 않습니다!멀티플레이를 웹 브라우저에서 즐기고 싶다면, itch.io 페이지에서 "multiplayer web version" 링크로 들어가면 됩니다. +text.host.web=HTML5 버전은 게임 호스팅을 지원하지 않습니다!게임을 다운로드 한 뒤에 사용 해 주세요. +text.gameover=코어가 파괴되었습니다. +text.highscore=[YELLOW]최고점수 달성! +text.lasted=마지막으로 달성한 단계 +text.level.highscore=최고 점수 : [accent]{0} +text.level.delete.title=삭제 확인 +text.map.delete=정말로 "[orange]{0}[]" 맵을 삭제하시겠습니까? +text.level.select=맵 선택 +text.level.mode=게임모드: +text.construction.title=블록 배치 안내서 +text.construction=당신은 [accent]블록 배치 모드[]를 선택하셨습니다.\n\n블록을 설치하고 싶으면, 자신의 건설 가능 범위 내에서 간단히 탭 하면 됩니다.\n일부 블록을 선택한 후에 확인 버튼을 누르면 배가 배치 작업을 진행할 것입니다.\n- [accent]블록을 삭제[]하고 싶다면 배치하고 싶은 영역을 탭 하세요. \n- [accent]블록을 넓게 배치[]하고 싶다면 배치하고 싶은 시작 영역을 길게 누르며 드래그 하면 됩니다.- [accent]블록을 한줄로 배치[]하고 싶다면 배치하고 싶은 시작 영역을 한번 탭 하고 길게 누르면서 드래그 하면 됩니다. \n- [accent]블록 배치 모드를 취소[]하고 싶다면 화면 하단 왼쪽에 있는 X 버튼을 누르면 됩니다. +text.deconstruction.title=블록 삭제 안내서 +text.deconstruction=당신은 [accent]블록 삭제 모드[]를 선택하셨습니다\n블록을 삭제하고 싶다면, 자신의 건설 가능 범위 내에서 간단히 탭 하면 됩니다.\n일부 블록을 선택한 후에 확인 버튼을 누르면 배가 파괴 작업을 진행할 것입니다.\n- [accent]블록을 삭제[]하고 싶다면 배치하고 싶은 영역을 탭 하세요- [accent]블록을 넓은 범위로 삭제[]하고 싶다면 배치하고 싶은 시작 영역을 길게 누르며 드래그 하면 됩니다.- [accent]블록 삭제 모드를 취소[]하고 싶다면 화면 하단 왼쪽에 있는 X 버튼을 누르면 됩니다. +text.showagain=다음 세션에서 이 메세지를 표시하지 않습니다 +text.unlocks=잠금 해제 +text.savegame=게임 저장 +text.loadgame=게임 불러오기 +text.joingame=게임 참가\n +text.addplayers=플레이어 추가/제거 +text.newgame=새 게임 +text.quit=나가기 +text.maps=맵 +text.maps.none=[LIGHT_GRAY]맵을 찾을 수 없습니다! +text.about.button=정보 +text.name=이름: +text.unlocked=새 블록이 잠금 해제되었습니다! +text.unlocked.plural=새 블록이 잠금 해제되었습니다! +text.server.rollback=롤백 +text.server.rollback.numberfield=롤백 개수: +text.blocks.editlogs=편집 기록 +text.block.editlogsnotfound=[red]이 위치에 대한 편집 기록이 없습니다. +text.public=공용 +text.players={0} 플레이어 온라인 +text.server.player.host=호스트: {0} +text.players.single={0} 플레이어 온라인 +text.server.mismatch=패킷 오류: 클라이언트와 서버 버전이 일치하지 않습니다.자신이 서버를 호스트하거나 최신 버전을 사용 해 주세요! +text.server.closing=[accent]서버 닫는중... +text.server.kicked.kick=당신은 서버에서 추방되었습니다! +text.server.kicked.fastShoot=당신은 총을 너무 빨리 발사했습니다. +text.server.kicked.invalidPassword=알 수 없는 비밀번호 입니다! +text.server.kicked.clientOutdated=오래된 버전의 클라이언트 입니다! 게임을 업데이트 하세요! +text.server.kicked.serverOutdated=오래된 버전의 서버입니다! 서버 호스트 관리자에게 문의하세요! +text.server.kicked.banned=당신은 서버에서 밴 망치를 맞아 차단당했습니다. +text.server.kicked.recentKick=당신은 방금 추방처리 되었습니다.잠시 기다린 후에 접속 해 주세요. +text.server.kicked.nameInUse=이 닉네임이 이미 서버에서 사용중입니다. +text.server.kicked.nameEmpty=닉네임에는 반드시 영어 또는 숫자가 있어야 합니다. +text.server.kicked.idInUse=당신은 이미 서버에 접속중입니다! 다중 계정은 허용되지 않습니다. +text.server.kicked.customClient=이 서버는 수정된 클라이언트를 지원하지 않습니다. 공식 버전을 사용하세요. +text.server.connected={0} 님이 접속했습니다. +text.server.disconnected={0} 님이 나갔습니다. +text.nohost=커스텀 맵을 호스트 할 수 없습니다! +text.host.info=[accent]호스트[] 버튼은 현재 네트워크의 [scarlet]6567[] 과 [scarlet]6568[] 포트를 사용합니다.\n[LIGHY_GRAY]같은 Wi-Fi 또는 로컬 네트워크[] 에서 서버 목록을 볼 수 있습니다.\n\n만약 플레이어들이 이 IP를 통해 어디에서나 연결할 수 있게 하고 싶다면, 공유기 설정에서 [accent]포트 포워딩[]을 해야 합니다.\n\n[LIGHT_GRAY]참고 : LAN 게임 연결에 문제가 있는 사람이 있다면, 방화벽 설정에서 Mindustry 가 로컬 네트워크에 액세스하도록 허용했는지 확인 해 주세요. +text.join.info=여기서 [accent]서버 IP[]를 입력하여 다른 서버에 접속할 수 있습니다.\n또는 [accent]로컬 네트워크(LAN)[] 서버를 검색하여 접속할 수 있습니다.\nLAN 및 WAN 멀티 플레이어 모두 지원됩니다.\n\n[LIGHT_GRAY]참고 : 여기에서는 자동으로 글로벌 서버를 추가하지 않습니다. IP로 다른 사람의 서버에 접속할려면 서버장에게 IP를 요청해야 합니다. +text.hostserver=서버 열기 +text.host=호스트 +text.hosting=[accent]서버 여는중.. +text.hosts.refresh=새로고침 +text.hosts.discovering=LAN 게임 찾기 +text.server.refreshing=서버 목록 새로고치는중... +text.hosts.none=[lightgray]LAN 게임을 찾을 수 없습니다! +text.host.invalid=[scarlet]서버에 연결할 수 없습니다! +text.server.friendlyfire=팀킬 +text.trace=플레이어 추적 +text.trace.playername=플레이어 이름: [accent]{0} +text.trace.ip=IP : [accent] +text.trace.id=고유 ID: [accent]{0} +text.trace.android=Android 클라이언트 : [accent]{0} +text.trace.modclient=수정된 클라이언트 : [accent]{0} +text.trace.totalblocksbroken=총 블럭 파괴 수: [accent]{0} +text.trace.structureblocksbroken=총 구조 블럭 파괴 수: [accent]{0} +text.trace.lastblockbroken=마지막으로 파괴한 블럭: [accent]{0} +text.trace.totalblocksplaced=총 설치한 블럭 수: [accent]{0} +text.trace.lastblockplaced=마지막으로 설치한 블록: [accent]{0} +text.invalidid=잘못된 클라이언트 ID 입니다! 공식 Mindustry 으로 버그 보고서를 제출 해 주세요. +text.server.bans=차단된 유저 +text.server.bans.none=차단된 플레이어가 없습니다. +text.server.admins=관리자들 +text.server.admins.none=관리자가 없습니다! +text.server.add=서버 추가 +text.server.delete=이 서버를 삭제 하시겠습니까? +text.server.hostname=호스트: {0} +text.server.edit=서버 수정 +text.server.outdated=[crimson]서버 버전이 낮습니다![] +text.server.outdated.client=[Crimson]클라이언트 버전이 낮습니다![] +text.server.version=[lightgray] 버전 : {0} +text.server.custombuild=[노란색]수정된 빌드 +text.confirmban=이 플레이어를 차단하시겠습니까? +text.confirmunban=이 플레이어를 차단하시겠습니까? +text.confirmadmin=이 플레이어를 관리자로 설정 하시겠습니까? +text.confirmunadmin=이 플레이어의 관리자 상태를 해제하시겠습니까? +text.joingame.byip=IP를 입력해서 참가하기... +text.joingame.title=게임 참가 +text.joingame.ip=IP: +text.disconnect=서버와 연결이 해제되었습니다. +text.disconnect.data=맵 데이터를 불러오지 못했습니다! +text.connecting=[accent]연결중... +text.connecting.data=[accent]월드 데이터 로딩중... +text.connectfail=[crimson]{0}[orange] 서버에 연결하지 못했습니다.[] +text.server.port=포트: +text.server.addressinuse=이 주소는 이미 사용중입니다! +text.server.invalidport=포트 번호가 잘못되었습니다. +text.server.error=[crimson]{0}[orange]서버를 호스팅 하는데 오류가 발생했습니다.[] +text.tutorial.back=< 이전 +text.tutorial.next=다음 > +text.save.new=새로 저장\n +text.save.overwrite=이 저장 슬롯을 덮어씌우겠습니까?\n +text.overwrite=덮어쓰기\n +text.save.none=저장 파일을 찾지 못했습니다!\n +text.saveload=[accent]저장중...\n +text.savefail=게임을 저장하지 못했습니다!\n +text.save.delete.confirm=이 저장파일을 삭제 하시겠습니까?\n +text.save.delete=삭제\n +text.save.export=저장파일 내보내기\n +text.save.import.invalid=[orange]저장파일이 유효한 파일이 아닙니다!\n\n다른 디바이스에 있는 커스텀 맵을 가져오는건 작동하지 않습니다.\n +text.save.import.fail=[crimson]저장파일을 불러오지 못함: [orange]{0}\n +text.save.export.fail=[crimson]저장파일을 내보내지 못함: [orange]{0} +text.save.import=저장파일 불러오기\n +text.save.newslot=저장 파일이름 :\n +text.save.rename=이름 변경\n +text.save.rename.text=새 이름 :\n +text.selectslot=저장슬롯을 선택하십시오.\n +text.slot=[accent]{0}번째 슬롯\n +text.save.corrupted=[orange]저장파일이 손상되었습니다!\n +text.empty=<비어있음>\n +text.on=켜기\n +text.off=끄기\n +text.save.autosave=자동저장: {0} +text.save.map=맵 : +text.save.wave={0} 단계 +text.save.difficulty=난이도 : {0} +text.save.date=마지막 저장 날짜 : {0} +text.confirm=확인 +text.delete=삭제\n +text.ok=승인 +text.open=열기 +text.cancel=취소 +text.openlink=링크 열기 +text.copylink=링크 복사 +text.back=뒤로가기 +text.quit.confirm=정말로 종료하시겠습니까? +text.changelog.title=변경사항 +text.changelog.loading=변경사항 가져오는중... +text.changelog.error.android=[orange]게임 변경사항은 가끔 Android 4.4 이하에서 작동하지 않습니다.이것은 내부 Android 버그 때문입니다. +text.changelog.error.ios=[orange]현재 iOS에서는 변경 사항을 지원하지 않습니다. +text.changelog.error=[scarlet]게임 변경사항을 가져오는 중 오류가 발생했습니다![]\n인터넷 연결을 확인하십시오. +text.changelog.current=[orange][[현재 버전] +text.changelog.latest=[orange][[최신 버전] +text.loading=[accent]불러오는중... +text.saving=[accent]저장중...\n +text.wave=[orange]{0} 단계 +text.wave.waiting=다음 단계 시작까지 {0}초 +text.waiting=기다리는중... +text.enemies=남은 몹 : {0} +text.enemies.single=몹이 1마리 남아있음 +text.loadimage=사진 불러오기 +text.saveimage=사진 저장 +text.unknown=알 수 없음 +text.custom=커스텀 +text.builtin=내장 +text.map.delete.confirm=이 맵을 삭제하시겠습니까? 이 명령은 취소할 수 없습니다! +text.map.random=[accent]랜덤 맵 +text.map.nospawn=이 맵에는 플레이어가 스폰 할 코어가 없습니다! 맵 편집기에서 [ROYAL]파란색[]코어를 맵에 추가하세요. +text.editor.slope=\\ +text.editor.openin=편집기 열기 +text.editor.oregen=광물 무작위 생성 +text.editor.oregen.info=광물 무작위 생성: +text.editor.mapinfo=맵 정보 +text.editor.author=만든이: +text.editor.description=설명: +text.editor.name=이름: +text.editor.teams=팀 +text.editor.elevation=높이 +text.editor.badsize=[orange]사진 크기가 잘못되었습니다![]유효한 맵 크기 : {0} +text.editor.errorimageload=[orange]{0}[] 파일을 불러오는데 오류가 발생했습니다. +text.editor.errorimagesave=[orange]{0}[] 파일 저장중 오류가 발생했습니다. +text.editor.generate=생성 +text.editor.resize=크기 조정 +text.editor.loadmap=맵 불러오기 +text.editor.savemap=맵 저장 +text.editor.saved=저장됨! +text.editor.save.noname=지도에 이름이 없습니다! '맵 정보' 메뉴에서 설정하세요. +text.editor.save.overwrite=이 맵의 이름은 기존에 있던 맵을 덮어씁니다! '맵 정보' 메뉴에서 다른 이름을 선택하세요. +text.editor.import.exists=[scarlet]맵을 불러올 수 없음:[] 기존에 있던 '{0}' 맵이 이미 존재합니다! +text.editor.import=가져오기 +text.editor.importmap=맵 가져오기 +text.editor.importmap.description=이미 존재하는 맵 가져오기 +text.editor.importfile=파일 가져오기 +text.editor.importfile.description=외부 맵 파일 가져오기 +text.editor.importimage=지형 사진 가져오기 +text.editor.importimage.description=외부 맵 이미지 파일 가져오기 +text.editor.export=내보내기 +text.editor.exportfile=파일 내보내기 +text.editor.exportfile.description=맵 파일 내보내기 +text.editor.exportimage=지형 이미지 내보내기 +text.editor.exportimage.description=맵 이미지 파일 내보내기 +text.editor.loadimage=지형 가져오기 +text.editor.saveimage=지형 내보내기 +text.editor.unsaved=[scarlet]변경사항을 저장하지 않았습니다![]\n정말로 나가시겠습니까?\n +text.editor.brushsize=브러쉬 크기 : {0} +text.editor.noplayerspawn=이 맵에는 플레이어의 스폰 지점이 없습니다! +text.editor.manyplayerspawns=맵에는 플레이어 스폰 지점이 둘 이상 있을 수 없습니다! +text.editor.manyenemyspawns={0} 개 이상의 몹 스폰 지점을 설정할 수 없습니다! +text.editor.resizemap=맵 크기 조정 +text.editor.resizebig=[scarlet]경고![]맵 크기가 256보다 큰지도는 많은 랙을 유발할 수 있습니다. +text.editor.mapname=맵 이름: +text.editor.overwrite=[accept]경고!이 명령은 기존 맵을 덮어씌우게 됩니다.\n +text.editor.overwrite.confirm=[scarlet]경고![] 이 이름을 가진 맵이 이미 있습니다. 덮어 쓰시겠습니까? +text.editor.selectmap=불러올 맵 선택: +text.width=넓이: +text.height=높이: +text.randomize=무작위 +text.apply=적용 +text.update=업데이트 +text.menu=메뉴 +text.play=플레이 +text.load=불러오기 +text.save=저장 +text.fps={0} FPS +text.tps={0} TPS +text.ping=핑 : {0}ms +text.language.restart=언어 설정을 적용하려면 게임을 다시 시작하십시오. +text.settings.language=언어 +text.settings=설정 +text.tutorial=게임 방법 +text.editor=편집기 +text.mapeditor=맵 편집기 +text.donate=기부 +text.settings.reset=기본값으로 재설정 +text.settings.rebind=rebind +text.settings.controls=컨트롤 +text.settings.game=게임 +text.settings.sound=소리 +text.settings.graphics=화면 +text.upgrades=업그레이드 +text.purchased=[LIME]생성됨! +text.weapons=무기 +text.paused=일시 정지 +text.info.title=[accent]정보 +text.error.title=[crimson]오류가 발생했습니다. +text.error.crashmessage=[scarlet]예기치 않은 오류가 발생하여 게임이 강제 종료되었습니다![]\n이 오류가 발생한 정확한 상황을 개발자에게 알려주십시오. [ORANGE]anukendev@gmail.com[] +text.error.crashtitle=오류가 발생했습니다. +text.blocks.blockinfo=블록 정보 +text.blocks.powercapacity=최대 전력 용량 +text.blocks.powershot=1발당 파워 소모량 +text.blocks.itemspeed=유닛 이동 속도 +text.blocks.shootrange=공격 범위 +text.blocks.size=블록 크기 +text.blocks.liquidcapacity=최대 액체 용량 +text.blocks.maxitemssecond=최대 아이템 보관량 +text.blocks.powerrange=전력 범위 +text.blocks.poweruse=전력 사용 +text.blocks.inputitemcapacity=입력 아이템 용량 +text.blocks.outputitemcapacity=입력 아이템 용량 +text.blocks.itemcapacity=아이템 용량 +text.blocks.maxpowergeneration=최대 발전량 +text.blocks.powertransferspeed=전력 전송량 +text.blocks.craftspeed=생산 속도 +text.blocks.inputliquid=입력 액체 +text.blocks.inputliquidaux=보조 액체 +text.blocks.inputitem=입력 아이템 +text.blocks.inputitems=입력 아이템들 +text.blocks.outputitem=출력 아이템 +text.blocks.drilltier=드릴 +text.blocks.drillspeed=기본 드릴 속도 +text.blocks.liquidoutput=액체 출력 +text.blocks.liquiduse=액체 사용 +text.blocks.explosive=이게 터지면 펑 터지면서 주변 블록에게 피해를 입힙니다! +text.blocks.health=체력 +text.blocks.inaccuracy=빗맞을 확률 +text.blocks.shots=총알 +text.blocks.reload=재장전 +text.blocks.inputfuel=연료 +text.blocks.fuelburntime=연료 연소 시간 +text.blocks.inputcapacity=입력 용량 +text.blocks.outputcapacity=출력 용량 +text.unit.blocks=블록들 +text.unit.powersecond=초당 전력 단위 +text.unit.liquidsecond=액체 단위 / 초 +text.unit.itemssecond=항목 / 초 +text.unit.pixelssecond=초당 픽셀 +text.unit.liquidunits=액상 단위 +text.unit.powerunits=전원 장치 +text.unit.degrees=도 +text.unit.seconds=초 +text.unit.none= +text.unit.items=아이템 +text.category.general=일반 +text.category.power=전력 +text.category.liquids=액체 +text.category.items=아이템 +text.category.crafting=제작 +text.category.shooting=발사 +setting.difficulty.easy=쉬움 +setting.difficulty.normal=보통 +setting.difficulty.hard=어려움 +setting.difficulty.insane=미침 +setting.difficulty.purge=[#FE2E2E]대한[#2E2EFE]민국 +setting.difficulty.name=난이도: +setting.screenshake.name=화면 흔들기 +setting.smoothcam.name=부드러운 카메라 +setting.indicators.name=적 위치 표시 화살표 +setting.effects.name=화면 효과 +setting.sensitivity.name=컨트롤러 감도 +setting.saveinterval.name=자동저장 간격 +setting.seconds=초 +setting.fullscreen.name=전체 화면 +setting.multithread.name=멀티 스레딩 +setting.fps.name=FPS 표시 +setting.vsync.name=VSync +setting.lasers.name=파워 레이져 표시 +setting.previewopacity.name=미리보기 블럭 투명도 +setting.healthbars.name=몹 체력바 표시 +setting.minimap.name=미니맵 보기 +setting.pixelate.name=화면 픽셀화 +setting.musicvol.name=음악 크기 +setting.mutemusic.name=음소거 +setting.sfxvol.name=SFX 볼륨 +setting.mutesound.name=소리 끄기 +map.maze.name=미로 +map.fortress.name=요새 +map.sinkhole.name=싱크홀 +map.caves.name=동굴 +map.volcano.name=화산 +map.caldera.name=칼데라 +map.scorch.name=타버림 +map.desert.name=사막 +map.island.name=섬 +map.grassland.name=목초지 +map.tundra.name=툰트라 +map.spiral.name=나선 +map.tutorial.name=게임 방법 +text.keybind.title=키 바인딩 +keybind.move_x.name=move_x +keybind.move_y.name=move_y +keybind.select.name=선택 +keybind.break.name=파괴 +keybind.shoot.name=사격 +keybind.zoom_hold.name=길게눌러 확대 +keybind.zoom.name=확대 +keybind.block_info.name=블럭 정보 +keybind.menu.name=메뉴 +keybind.pause.name=일시중지 +keybind.dash.name=달리기 +keybind.chat.name=채팅 +keybind.player_list.name=플레이어 목록 +keybind.console.name=콘솔 +keybind.rotate_alt.name=회전_alt +keybind.rotate.name=회전 +mode.text.help.title=도움말 +mode.waves.name=단계 +mode.waves.description=이것은 일반 모드입니다. 제한된 자원과 자동으로 다음 단계가 시작됩니다. +mode.sandbox.name=샌드박스 +mode.sandbox.description=무한한 자원과 다음 단계 시작을 위한 타이머가 없습니다. +mode.freebuild.name=자유 건축 +mode.freebuild.description=제한된 자원과 다음 단계 시작을 위한 타이머가 없습니다. +content.item.name=아이템 +content.liquid.name=액체 +content.unit-type.name=종류 +content.recipe.name=블록 +item.stone.name=돌 +item.stone.description=흔히 찾을 수 있는 자원. 바닥에서 돌을 캐거나 용암을 사용하여 얻을 수 있습니다. +item.tungsten.name=텅스텐 +item.tungsten.description=일반적이지만 매우 유용한 건축 재료. 드릴 및 생산 건물, 제련소와 같은 내열성 블록에 사용됩니다. +item.lead.name=납 +item.lead.description=기본적인 시작 자원. 전자 및 액체 수송 블록에서 광범위하게 사용됩니다. +item.coal.name=석탄 +item.coal.description=일반적이고 쉽게 이용할 수 있는 연료. +item.carbide.name=합금 +item.carbide.description=텅스텐과 탄소로 만든 합금. 고급 운송 블록 및 상위 티어 드릴에 사용됩니다. +item.titanium.name=티타늄 +item.titanium.description=물 운반이나 드릴, 비행기등에서 재료로 사용되는 자원입니다. +item.thorium.name=토륨 +item.thorium.description=건물 탄약 또는 핵연료로 사용되는 방사성 금속. +item.silicon.name=규소 +item.silcion.description=매우 유용한 반도체로, 태양 전지 패널과 복잡한 전자 제품에 응용할 수 있습니다. +item.plastanium.name=Plastanium +item.plastanium.description=고급 항공기 및 분열 탄약에 사용되는 가벼운 연성 재료. +item.phase-matter.name=Phase Matter +item.surge-alloy.name=Surge Alloy +item.biomatter.name=Biomatter +item.biomatter.description=유기농 덤불; 석유로 전환하거나 기본 연료로 사용됩니다. +item.sand.name=모래 +item.sand.description=합금 및 플럭스 모두에서 제련시 광범위하게 사용되는 일반적인 재료. +item.blast-compound.name=Blast Compound +item.blast-compound.description=폭탄 및 폭발물에 사용되는 휘발성 화합물. 그것이 연료로 태울 수 있지만, 이것은 권고하지 않습니다. +item.pyratite.name=Pyratite +item.pyratite.description=방화 용 무기에 사용되는 극히 가연성 물질. +liquid.water.name=물 +liquid.lava.name=용암 +liquid.oil.name=석유 +liquid.cryofluid.name=Cryofluid +text.item.explosiveness=[LIGHT_GRAY]폭발력 : {0} +text.item.flammability=[LIGHT_GRAY]인화성 : {0} +text.item.radioactivity=[LIGHT_GRAY]방사능 : {0} +text.item.fluxiness=[LIGHT_GRAY]Flux Power : {0} +text.item.hardness=[LIGHT_GRAY]강도 : {0} +text.liquid.heatcapacity=[LIGHT_GRAY]열용량 : {0} +text.liquid.viscosity=[LIGHT_GRAY]점도 : {0} +text.liquid.temperature=[LIGHT_GRAY]온도 : {0} +block.tungsten-wall.name=텅스텐 장벽 +block.tungsten-wall-large.name=큰 텅스텐 벽 +block.carbide-wall.name=합금벽 +block.carbide-wall-large.name=대형 합금벽 +block.thorium-wall.name=토륨 장벽 +block.thorium-wall-large.name=대형 토륨 벽 +block.door.name=문 +block.door-large.name=큰 문 +block.duo.name=샷건 +block.scorch.name=물총 +block.hail.name=헤이스트 +block.lancer.name=팬선 +block.conveyor.name=컨베이어 +block.titanium-conveyor.name=티타늄 컨베이어 +block.junction.name=교차기 +block.splitter.name=쪼개는 도구 +block.splitter.description=항목을받은 직후 두 개의 반대 방향으로 항목을 출력합니다. +block.router.name=분배기 +block.router.description=아이템을 넣으면 다른 방향으로 아이템을 번갈아서 내보냅니다. +block.sorter.name=필터 +block.sorter.description=아이템을 받아서 설정된 아이템일 경우 바로 앞으로 통과하며, 그렇지 않을 경우 옆으로 통과합니다. +block.bridgeconveyor.name=터널 +block.bridgeconveyor.description=최대 2블록을 건너 뛰고 자원을 운반하게 해 주는 블럭. +block.smelter.name=제련소 +block.arc-smelter.name=아크 제련소 +block.silicon-smelter.name=실리콘 제련소 +block.phase-weaver.name=위상 위버 +block.pulverizer.name=분쇄기 +block.cryofluidmixer.name=냉동고 혼합기 +block.melter.name=멜터 +block.incinerator.name=소각로 +block.biomattercompressor.name=바이오 매터 압축기 +block.separator.name=분리 기호 +block.centrifuge.name=원심 분리기 +block.power-node.name=전원 노드 +block.power-node-large.name=대형 전원 노드 +block.battery.name=배터리 +block.battery-large.name=대형 배터리 +block.combustion-generator.name=연소 발전기 +block.turbine-generator.name=터빈 발전기 +block.tungsten-drill.name=텅스텐 드릴 +block.carbide-drill.name=초경 드릴 +block.laser-drill.name=레이저 드릴 +block.water-extractor.name=물 추출기 +block.cultivator.name=경운기 +block.dart-ship-factory.name=다트 선박 공장 +block.delta-mech-factory.name=델타 메크 공장 +block.dronefactory.name=드론 팩토리 +block.repairpoint.name=수리 점 +block.resupplypoint.name=재 공급 포인트 +block.conduit.name=도관 +block.pulseconduit.name=펄스 도관 +block.liquidrouter.name=액체 라우터 +block.liquidtank.name=액체 탱크 +block.liquidjunction.name=액체 정션 +block.bridgeconduit.name=브릿지 도관 +block.mechanical-pump.name=기계 펌프 +block.itemsource.name=품목 출처 +block.itemvoid.name=아이템 무효 +block.liquidsource.name=액체 소스 +block.powervoid.name=무효 전력 +block.powerinfinite.name=무한한 힘 +block.unloader.name=언 로더 +block.sortedunloader.name=정렬 된 언 로더 +block.vault.name=둥근 천장 +block.wave.name=웨이브 +block.swarmer.name=스머머 +block.salvo.name=살보 +block.ripple.name=리플 +block.phase-conveyor.name=상 컨베이어 +block.overflow-gate.name=오버플로 게이트 +block.bridge-conveyor.name=브릿지 컨베이어 +block.plastanium-compressor.name=플라스터 늄 압축기 +block.pyratite-mixer.name=Pyratite 믹서 +block.blast-mixer.name=블래스트 믹서 +block.solidifer.name=고체 +block.solar-panel.name=태양 전지 패널 +block.solar-panel-large.name=대형 태양 전지판 +block.oil-extractor.name=오일 추출기 +block.javelin-ship-factory.name=창 던지기 선박 공장 +block.drone-factory.name=드론 팩토리 +block.fabricator-factory.name=Fabricator 공장 +block.repair-point.name=수리 점 +block.resupply-point.name=재 공급 포인트 +block.pulse-conduit.name=펄스 도관 +block.phase-conduit.name=위상 도관 +block.liquid-router.name=액체 라우터 +block.liquid-tank.name=액체 탱크 +block.liquid-junction.name=액체 정션 +block.bridge-conduit.name=브릿지 도관 +block.rotary-pump.name=로타리 펌프 +text.yes=Yes +text.no=No +text.blocks.targetsair=Targets Air +text.blocks.coolant=Coolant +text.blocks.coolantuse=Coolant Use +text.blocks.inputliquidfuel=Fuel Liquid +text.blocks.liquidfueluse=Liquid Fuel Use +block.distributor.name=Distributor +block.distributor.description=A splitter that can split items into 8 directions. +block.overflow-gate.description=A combination splitter and router that only outputs to the left and right if the front path is blocked. +block.nuclear-reactor.name=Nuclear Reactor diff --git a/core/assets/bundles/bundle_pl.properties b/core/assets/bundles/bundle_pl.properties index fb5027cddf..17fe566c7e 100644 --- a/core/assets/bundles/bundle_pl.properties +++ b/core/assets/bundles/bundle_pl.properties @@ -1,488 +1,515 @@ -text.about = Stworzony przez [ROYAL] Anuken. []\nPierwotnie wpis w [orange] GDL [] MM Jam.\n\nNapisy:\n- SFX wykonane z pomocą [YELLOW] bfxr []\n- Muzyka wykonana przez [GREEN] RoccoW [] / znaleziona na [lime] FreeMusicArchive.org []\n\nSpecjalne podziękowania dla:\n- [coral] MitchellFJN []: obszerne testowanie i feedback\n- [niebo] Luxray5474 []: prace związane z wiki, pomoc z kodem\n- Wszystkich beta testerów na itch.io i Google Play\n -text.discord = Odwiedź nasz serwer Discord -text.gameover = Rdzeń został zniszczony. -text.highscore = [YELLOW] Nowy rekord! -text.lasted = Wytrwałeś do fali -text.level.highscore = Rekord: [accent]{0} -text.level.delete.title = Potwierdź kasowanie -text.level.delete = Czy na pewno chcesz usunąć mapę \"[orange]{0}\"? -text.level.select = Wybrany poziom -text.level.mode = Tryb gry: -text.savegame = Zapisz Grę -text.loadgame = Wczytaj grę -text.joingame = Gra wieloosobowa -text.quit = Wyjdź -text.about.button = O grze -text.name = Nazwa: -text.public = Publiczny -text.players = {0} graczy online -text.server.player.host = {0} (host) -text.players.single = {0} gracz online -text.server.mismatch = Błąd pakietu: możliwa niezgodność wersji klienta/serwera.\nUpewnij się, że Ty i host macie najnowszą wersję Mindustry! -text.server.closing = [accent] Zamykanie serwera ... -text.server.kicked.kick = Zostałeś wyrzucony z serwera! -text.server.kicked.invalidPassword = Nieprawidłowe hasło! -text.server.kicked.clientOutdated = Nieaktualna gra! Zaktualizują ją! -text.server.kicked.serverOutdated = Nieaktualna gra! Zaktualizują ją! -text.server.connected = {0} dołączył do gry . -text.server.disconnected = {0} został rozłączony. -text.nohost = Nie można hostować serwera na mapie niestandardowej! -text.hostserver = Serwer hosta -text.host = Host -text.hosting = [accent] Otwieranie serwera ... -text.hosts.refresh = Odśwież -text.hosts.discovering = Wyszukiwanie gier w sieci LAN -text.server.refreshing = Odświeżanie serwera -text.hosts.none = [lightgray] Brak serwerów w sieci LAN! -text.host.invalid = [scarlet] Nie można połączyć się z hostem. -text.server.friendlyfire = Bratobójczy ogień -text.server.add = Dodaj serwer -text.server.delete = Czy na pewno chcesz usunąć ten serwer? -text.server.hostname = Host: {0} -text.server.edit = Edytuj serwer -text.joingame.byip = Dołącz przez IP... -text.joingame.title = Dołącz do gry -text.joingame.ip = IP: -text.disconnect = Rozłączony. -text.connecting = [accent]Łączenie ... -text.connecting.data = [accent]Ładowanie danych świata... -text.connectfail = [crimson]Nie można połączyć się z serwerem: [orange] {0} -text.server.port = Port: -text.server.addressinuse = Adres jest już w użyciu! -text.server.invalidport = Nieprawidłowy numer portu. -text.server.error = [crimson] Błąd hostowania serwera: [orange] {0} -text.tutorial.back = < Cofnij -text.tutorial.next = Dalej > -text.save.new = Nowy zapis -text.save.overwrite = Czy na pewno chcesz nadpisać zapis gry? -text.overwrite = Nadpisz -text.save.none = Nie znaleziono zapisów gry! -text.saveload = [akcent]Zapisywanie... -text.savefail = Nie udało się zapisać gry! -text.save.delete.confirm = Czy na pewno chcesz usunąć ten zapis gry? -text.save.delete = Usuń -text.save.export = Eksportuj -text.save.import.invalid = [orange]Zapis gry jest niepoprawny! -text.save.import.fail = [crimson]Nie udało się zaimportować zapisu: [orange] {0} -text.save.export.fail = [crimson]Nie można wyeksportować zapisu: [orange] {0} -text.save.import = Importuj -text.save.newslot = Zapisz nazwę: -text.save.rename = Zmień nazwę -text.save.rename.text = Zmień nazwę -text.selectslot = Wybierz zapis. -text.slot = [accent]Slot {0} -text.save.corrupted = [orange]Zapis gry jest uszkodzony lub nieprawidłowy! -text.empty = -text.on = Włączone -text.off = Wyłączone -text.save.autosave = Zapisywanie automatyczne -text.save.map = Mapa: {0} -text.save.wave = Fala: {0} -text.save.date = Ostatnio zapisano: {0} -text.confirm = Potwierdź -text.delete = Usuń -text.ok = Ok -text.open = Otwórz -text.cancel = Anuluj -text.openlink = Otwórz link -text.back = Wróć -text.quit.confirm = Czy na pewno chcesz wyjść? -text.loading = [accent]Ładowanie ... -text.wave = [orange]Fala {0} -text.wave.waiting = Fala w {0} -text.waiting = Oczekiwanie... -text.enemies = {0} wrogów -text.enemies.single = {0} wróg -text.loadimage = Załaduj obraz -text.saveimage = Zapisz obraz -text.editor.badsize = [orange]Nieprawidłowe wymiary obrazu![]\nWymiary poprawne: {0} -text.editor.errorimageload = Błąd podczas ładowania pliku obrazu: [orange]{0} -text.editor.errorimagesave = Błąd podczas zapisywania pliku obrazu: [orange]{0} -text.editor.generate = Generuj -text.editor.resize = Zmień rozmiar -text.editor.loadmap = Załaduj mapę -text.editor.savemap = Zapisz mapę -text.editor.loadimage = Załaduj obraz -text.editor.saveimage = Zapisz obraz -text.editor.unsaved = [scarlet]Masz niezapisane zmiany![]\nCzy na pewno chcesz wyjść? -text.editor.brushsize = Rozmiar pędzla: {0} -text.editor.noplayerspawn = Ta mapa nie ma ustawionego spawnu gracza! -text.editor.manyplayerspawns = Mapy nie mogą mieć więcej niż jeden punkt spawnu gracza! -text.editor.manyenemyspawns = Nie może mieć więcej niż {0} punktów spawnu wroga! -text.editor.resizemap = Zmień rozmiar mapy -text.editor.resizebig = [scarlet]Uwaga![]\nMapy większe niż 256 jednostek mogą przycinać i być niestabilne. -text.editor.mapname = Nazwa mapy: -text.editor.overwrite = [accent]Uwaga!\nSpowoduje to nadpisanie istniejącej mapy. -text.editor.failoverwrite = [crimson]Nie można nadpisać mapy podstawowej! -text.editor.selectmap = Wybierz mapę do załadowania: -text.width = Szerokość: -text.height = Wysokość: -text.randomize = Wylosuj -text.apply = Zastosuj -text.update = Zaktualizuj -text.menu = Menu -text.play = Graj -text.load = Wczytaj -text.save = Zapisz -text.language.restart = Uruchom grę ponownie aby ustawiony język zaczął funkcjonować. -text.settings.language = Język -text.settings = Ustawienia -text.tutorial = Poradnik -text.editor = Edytor -text.mapeditor = Edytor map -text.donate = Wspomóż nas -text.settings.reset = Przywróć domyślne -text.settings.controls = Sterowanie -text.settings.game = Gra -text.settings.sound = Dźwięk -text.settings.graphics = Grafika -text.upgrades = Ulepszenia -text.purchased = [LIME]Stworzono! -text.weapons = Bronie -text.paused = Wstrzymano -text.respawn = Odrodzenie za -text.info.title = [accent]Informacje -text.error.title = [crimson]Wystąpił błąd -text.error.crashmessage = [SCARLET]Wystąpił nieoczekiwany błąd, który spowodowałby awarię.[]\nProszę, powiadom dewelopera gry o tym błędzie, pisząc jak do niego doszło: [ORANGE]anukendev@gmail.com[] -text.error.crashtitle = Wystąpił błąd -text.mode.break = Tryb przerw: {0} -text.mode.place = Tryb układania: {0} -placemode.hold.name = linia -placemode.areadelete.name = obszar -placemode.touchdelete.name = Dotyk -placemode.holddelete.name = Przytrzymać -placemode.none.name = żaden -placemode.touch.name = Dotyk -placemode.cursor.name = kursor -text.blocks.extrainfo = [accent]Dodatkowe informacje o bloku: -text.blocks.blockinfo = Informacje o bloku -text.blocks.powercapacity = Moc znamionowa -text.blocks.powershot = moc / strzał -text.blocks.powersecond = moc / sekunda -text.blocks.powerdraindamage = siła ataku / obrażenia -text.blocks.shieldradius = Promień osłony -text.blocks.itemspeedsecond = prędkość ​​/ sekundy -text.blocks.range = Zakres -text.blocks.size = Rozmiar -text.blocks.powerliquid = moc / ciecz -text.blocks.maxliquidsecond = maksymalna ilość cieczy / sekunda -text.blocks.liquidcapacity = Pojemność cieczy -text.blocks.liquidsecond = ciecz / sekunda -text.blocks.damageshot = obrażenia / strzał -text.blocks.ammocapacity = Pojemność amunicji -text.blocks.ammo = Amunicja: -text.blocks.ammoitem = amunicja / przedmiot -text.blocks.maxitemssecond = Maksymalna liczba przedmiotów / Sekunda -text.blocks.powerrange = Zakres mocy -text.blocks.lasertilerange = Zasięg lasera -text.blocks.capacity = Wydajność -text.blocks.itemcapacity = Pojemność przedmiotów -text.blocks.maxpowergenerationsecond = maksymalne generowanie energii / sekunda -text.blocks.powergenerationsecond = wytwarzanie energii / sekunda -text.blocks.generationsecondsitem = sekunda / przedmiot -text.blocks.input = Wkład -text.blocks.inputliquid = Potrzebna ciecz -text.blocks.inputitem = Potrzebne przedmioty -text.blocks.output = Wyjście -text.blocks.secondsitem = sekundy / przedmiot -text.blocks.maxpowertransfersecond = maksymalny transfer mocy / sekundę -text.blocks.explosive = Wysoce wybuchowy! -text.blocks.repairssecond = naprawa / sekunda -text.blocks.health = Zdrowie -text.blocks.inaccuracy = Niedokładność -text.blocks.shots = Strzały -text.blocks.shotssecond = Strzały / Sekunda -text.blocks.fuel = Paliwo -text.blocks.fuelduration = Wydajność paliwa -text.blocks.maxoutputsecond = maksymalne wyjście / sekunda -text.blocks.inputcapacity = Pojemność wejściowa -text.blocks.outputcapacity = Wydajność wyjściowa -text.blocks.poweritem = moc / przedmiot -text.placemode = Tryb miejsca -text.breakmode = Tryb przerwania -text.health = Zdrowie: -setting.difficulty.easy = łatwy -setting.difficulty.normal = normalny -setting.difficulty.hard = trudny -setting.difficulty.insane = szalony -setting.difficulty.purge = Czystka -setting.difficulty.name = Poziom trudności -setting.screenshake.name = Trzęsienie się ekranu -setting.smoothcam.name = Płynna kamera -setting.indicators.name = Wskaźniki wroga -setting.effects.name = Wyświetlanie efektów -setting.sensitivity.name = Czułość kontrolera -setting.saveinterval.name = Interwał automatycznego zapisywania -setting.seconds = Sekundy -setting.fps.name = Widoczny licznik FPS -setting.vsync.name = Synchronizacja pionowa -setting.lasers.name = Pokaż lasery zasilające -setting.healthbars.name = Pokaż paski zdrowia jednostki -setting.pixelate.name = Rozpikselizowany obraz -setting.musicvol.name = Głośność muzyki -setting.mutemusic.name = Wycisz muzykę -setting.sfxvol.name = Głośność dźwięków -setting.mutesound.name = Wycisz dźwięki -map.maze.name = labirynt -map.fortress.name = twierdza -map.sinkhole.name = wgłębienie -map.caves.name = jaskinie -map.volcano.name = wulkan -map.caldera.name = kaldera -map.scorch.name = opalacz -map.desert.name = pustynia -map.island.name = wyspa -map.grassland.name = łąka -map.tundra.name = tundra -map.spiral.name = spirala -map.tutorial.name = Poradnik -tutorial.intro.text = [yellow]Witamy w poradniku do gry.[]\nAby rozpocząć, naciśnij \"Dalej\". -tutorial.moveDesktop.text = Aby się poruszać, użyj klawiszy [orange]W A S[] oraz [orange]D[]. Przytrzymaj [orange]shift[], aby przyśpieszyć.\nPrzytrzymując [orange]ctrl[] podczas kręcenia [orange]rolką myszy[] możesz zmieniać poziom przybliżenia mapy. -tutorial.shoot.text = Do celowania używasz kursora myszy.\n[orange]Lewy przycisk myszy[] służy do strzelania. Poćwicz na [yellow]celu[]. -tutorial.moveAndroid.text = Aby przesunąć widok, przeciągnij jednym palcem po ekranie. Ściśnij i przeciągnij, aby powiększyć lub pomniejszyć. -tutorial.placeSelect.text = Wybierz [yellow]przenośnik[] z menu blokowego w prawym dolnym rogu. -tutorial.placeConveyorDesktop.text = Użyj [orange]rolki myszy[] aby obrócić przenośnik [orange]do przodu[], a następnie umieść go w [yellow]oznaczonym miejscu[] za pomocą [orange]lewego przycisku myszy[]. -tutorial.placeConveyorAndroid.text = Użyj [orange]przycisk obracania[], aby obrócić przenośnik [orange]do przodu[]. Jednym palcem przeciągnij go na [yellow]wskazaną pozycję[], a następnie umieść go za pomocą [orange]znacznika wyboru[]. -tutorial.placeConveyorAndroidInfo.text = Możesz też nacisnąć ikonę celownika w lewym dolnym rogu, aby przełączyć na [pomarańczowy] [[tryb dotykowy] [] i umieścić bloki, dotykając ekranu. W trybie dotykowym bloki można obracać strzałką w lewym dolnym rogu. Naciśnij [żółty] następny [], aby go wypróbować. -tutorial.placeDrill.text = Teraz wybierz i umieść [yellow]wiertło do kamienia[] w oznaczonym miejscu. -tutorial.blockInfo.text = Jeśli chcesz się dowiedzieć więcej na temat wybranego bloku, kliknij [orange]znak zapytania[] w prawym górnym rogu, aby przeczytać jego opis. -tutorial.deselectDesktop.text = Możesz anulować wybór bloku za pomocą [orange]prawego przycisku myszy[]. -tutorial.deselectAndroid.text = Możesz odznaczyć blok, naciskając przycisk [orange]X[]. -tutorial.drillPlaced.text = Wiertło będzie teraz produkować [yellow]kamień[] i podawać go na przenośnik, który przeniesie go do [yellow]rdzenia[] -tutorial.drillInfo.text = Różne rudy wymagają różnych wierteł. Kamień wymaga wiertła do kamieniu, żelazo wymaga wiertła do żelaza, itd. -tutorial.drillPlaced2.text = Przeniesienie przedmiotów do rdzenia powoduje umieszczenie ich w [yellow]inwentarzu przedmiotów[] w lewym górnym rogu. Stawianie bloków te przedmioty zużywa. -tutorial.moreDrills.text = Możesz połączyć wiertła i przenośników w przedstawiony sposób: -tutorial.deleteBlock.text = Możesz usuwać bloki, klikając [orange]prawy przycisk myszy[] na bloku, który chcesz usunąć.\nSpróbuj usunąć [yellow]oznaczony[] przenośnik. -tutorial.deleteBlockAndroid.text = Możesz usuwać bloki za pomocą [orange]krzyżyka[] w [orange]menu przerywania[] w lewym dolnym rogu i stukając w blok.\nSpróbuj usunąć [yellow]oznaczony[] przenośnik. -tutorial.placeTurret.text = Teraz wybierz i umieść [yellow]działko[] w [yellow]zaznaczonym miejscu[]. -tutorial.placedTurretAmmo.text = Działko przyjmie teraz [yellow]amunicję[] z przenośnika. Możesz zobaczyć, ile ma amunicji, najeżdżając na działko kursorem i sprawdzając [green]zielony pasek[]. -tutorial.turretExplanation.text = Wieżyczki będą strzelać automatycznie do najbliższego wroga w zasięgu, o ile będą miały wystarczającą ilość amunicji. -tutorial.waves.text = Co każde [yellow]60[] sekund, fala [coral]wrogów[] pojawi się w określonym miejscu na mapie i spróbuje zniszczyć rdzeń. -tutorial.coreDestruction.text = Twoim celem jest [yellow]obrona rdzenia[]. Zniszczenie rdzenia jest równoznaczne z [coral]przegraną[]. -tutorial.pausingDesktop.text = Jeśli chcesz zrobić przerwę, naciśnij [orange]spację[] lub [orange]przycisk pauzy[] w lewym górnym rogu. Nadal możesz wybierać i wstawiać klocki podczas pauzy, ale nie możesz niszczyć i strzelać. -tutorial.pausingAndroid.text = Jeśli chcesz zrobić przerwę, naciśnij [orange]przycisk pauzy[]. Nadal możesz stawiać i niszczyć bloki. -tutorial.purchaseWeapons.text = Możesz kupić nowe [żółte] bronie [] dla swojego mecha, otwierając menu aktualizacji w lewym dolnym rogu. -tutorial.switchWeapons.text = Zmień broń, klikając jej ikonę w lewym dolnym rogu lub używając cyfr [orange][1-9[]. -tutorial.spawnWave.text = Fala wrogów nadchodzi! Zniszcz ich. -tutorial.pumpDesc.text = W późniejszych falach może być konieczne użycie [yellow]pompy[] do rozprowadzania cieczy dla generatorów lub ekstraktorów. -tutorial.pumpPlace.text = Pompy działają podobnie do wierteł, z tym wyjątkiem, że wytwarzają ciecze zamiast ród.\nSpróbuj umieścić pompę na [yellow]oleju[]. -tutorial.conduitUse.text = Teraz umieść [orange]rurę[] wychodzącą od pompy. -tutorial.conduitUse2.text = I tak jeszcze jedną... -tutorial.conduitUse3.text = I jeszcze jedeną... -tutorial.generator.text = Teraz umieść [orange]generator spalinowy[] na końcu kanału. -tutorial.generatorExplain.text = Generator ten będzie teraz wytwarzać [yellow]energię[] z oleju. -tutorial.lasers.text = Energia jest dystrybuowana za pomocą [yellow]przekaźników[]. Umieść takowy przekaźnik na [yellow]wskazanej pozycji[]. -tutorial.laserExplain.text = Generator przeniesie teraz moc do przekaźnika. Wiązka [orange]nieprzeźroczysta[] oznacza, że ​​aktualnie transmituje moc. Wiązka [yellow]przeźroczysta[] wskazuje na brak przekazywanej energii. -tutorial.laserMore.text = Możesz sprawdzić ile energii przechowuje blok najeżdżając na niego kursorem i sprawdzając [yellow]żółty pasek[]. -tutorial.healingTurret.text = Energia może być używany do zasilania [lime]wież naprawczych[]. Umieść takową [yellow]tutaj[]. -tutorial.healingTurretExplain.text = Dopóki dostarczana jest do niej energia będzie [lime]naprawiać pobliskie bloki[]. Podczas gry ustawiłeś ją niedaleko swojej bazy tak szybko, jak to tylko możliwe! -tutorial.smeltery.text = Wiele bloków wymaga do pracy [orange]stali[], którą można wytwarzać w [orange]hucie[]. A skoro tak, to postaw ją teraz. -tutorial.smelterySetup.text = Huta wytworzy teraz z żelaza [orange]stal[], wykorzystując węgiel jako paliwo. -tutorial.tunnelExplain.text = Zwróć też uwagę, że przedmioty przechodzą przez [orange]tunel[] i wynurzają się po drugiej stronie, przechodząc przez kamienny blok. Pamiętaj, że tunele mogą przechodzić tylko do 2 bloków. -tutorial.end.text = I na tym poradnik się kończy!\nPozostaje tylko życzyć Ci [orange]powodzenia[]! -keybind.move_x.name = Poruszanie w poziomie -keybind.move_y.name = Poruszanie w pionie -keybind.select.name = Wybieranie -keybind.break.name = Niszczenie -keybind.shoot.name = Strzelanie -keybind.zoom_hold.name = Inicjator przybliżania -keybind.zoom.name = Przybliżanie -keybind.menu.name = menu -keybind.pause.name = pauza -keybind.dash.name = przyśpieszenie -keybind.rotate_alt.name = Obracanie (1) -keybind.rotate.name = Obracanie (2) -keybind.weapon_1.name = Broń 1 -keybind.weapon_2.name = Broń 2 -keybind.weapon_3.name = Broń 3 -keybind.weapon_4.name = Broń 4 -keybind.weapon_5.name = Broń 5 -keybind.weapon_6.name = Broń 6 -mode.waves.name = Fale -mode.sandbox.name = sandbox -mode.freebuild.name = budowanie -upgrade.standard.name = Standardowy -upgrade.standard.description = Standardowy mech. -upgrade.blaster.name = blaster -upgrade.blaster.description = Wystrzeliwuje powolną, słabą kulę. -upgrade.triblaster.name = triblaster -upgrade.triblaster.description = Strzela 3 pociskami w rozprzestrzenianiu. -upgrade.clustergun.name = clustergun -upgrade.clustergun.description = Wystrzeliwuje w różnych kierunkach kilka granatów. -upgrade.beam.name = Działko laserowe -upgrade.beam.description = Strzela laserem o dalekim zasięgu. -upgrade.vulcan.name = wulkan -upgrade.vulcan.description = Wystrzeliwuje grad szybkich pocisków. -upgrade.shockgun.name = Działko elektryczne -upgrade.shockgun.description = Wystrzeliwuje niszczycielski podmuch naładowanych odłamków. -item.stone.name = kamień -item.iron.name = żelazo -item.coal.name = węgiel -item.steel.name = stal -item.titanium.name = tytan -item.dirium.name = dirium -item.thorium.name = uran -item.sand.name = piasek -liquid.water.name = woda -liquid.plasma.name = plazma -liquid.lava.name = lawa -liquid.oil.name = olej -block.weaponfactory.name = fabryka broni -block.air.name = Powietrze. -block.blockpart.name = Kawałek bloku -block.deepwater.name = głęboka woda -block.water.name = woda -block.lava.name = lawa -block.oil.name = olej -block.stone.name = kamień -block.blackstone.name = czarny kamień -block.iron.name = żelazo -block.coal.name = węgiel -block.titanium.name = tytan -block.thorium.name = uran -block.dirt.name = ziemia -block.sand.name = piasek -block.ice.name = lód -block.snow.name = śnieg -block.grass.name = trawa -block.sandblock.name = blok z piasku -block.snowblock.name = blok ze śniegu -block.stoneblock.name = blok z kamienia -block.blackstoneblock.name = blok z czarnego kamienia -block.grassblock.name = blok z trawy -block.mossblock.name = porośnięty blok -block.shrub.name = krzew -block.rock.name = kamyk -block.icerock.name = kamyk lodowy -block.blackrock.name = czarny kamyk -block.dirtblock.name = Brudny blok -block.stonewall.name = Kamienna ściana -block.stonewall.fulldescription = Tani blok obronny. Przydatny do ochrony rdzenia i wież w pierwszych kilku falach. -block.ironwall.name = Żelazna ściana -block.ironwall.fulldescription = Podstawowy blok obronny. Zapewnia ochronę przed wrogami. -block.steelwall.name = stalowa ściana -block.steelwall.fulldescription = Standardowy blok obronny. odpowiednia ochrona przed wrogami. -block.titaniumwall.name = tytanowa ściana -block.titaniumwall.fulldescription = Silny blok obronny. Zapewnia ochronę przed wrogami. -block.duriumwall.name = ściana z dirium -block.duriumwall.fulldescription = Bardzo silny blok obronny. Zapewnia ochronę przed wrogami. -block.compositewall.name = ściana kompozytowa -block.steelwall-large.name = duża stalowa ściana -block.steelwall-large.fulldescription = Standardowy blok obronny. Rozpiętość wielu płytek. -block.titaniumwall-large.name = duża tytanowa ściana -block.titaniumwall-large.fulldescription = Silny blok obronny. Rozpiętość wielu płytek. -block.duriumwall-large.name = duża ściana z dirium -block.duriumwall-large.fulldescription = Bardzo silny blok obronny. Rozpiętość wielu płytek. -block.titaniumshieldwall.name = Ściana z polem obronnym -block.titaniumshieldwall.fulldescription = Silny blok obronny z dodatkową wbudowaną tarczą. Wymaga zasilania. Używa energii do pochłaniania pocisków wroga. W celu dostarczenia zasilania zaleca się stosowanie wzmacniaczy energii. -block.repairturret.name = Wieża naprawcza -block.repairturret.fulldescription = Naprawia pobliskie uszkodzone bloki w niedużej prędkości. Wykorzystuje niewielkie ilości energii. -block.megarepairturret.name = Wieża naprawcza II -block.megarepairturret.fulldescription = Naprawia pobliskie uszkodzone bloki z przyzwoitą prędkośą. Do działania wykorzystuje energię. -block.shieldgenerator.name = Generator tarczy -block.shieldgenerator.fulldescription = Zaawansowany blok obronny. Osłania wszystkie bloki w promieniu przed atakiem wroga. Zużywa energię z niewielką prędkością gdy jest w stanie bezczynności, ale z kolei gdy jest w użyciu, energię pobiera bardzo szybko. -block.door.name = drzwi -block.door.fulldescription = Blok, który można otworzyć i zamknąć poprzez dotknięcie -block.door-large.name = duże drzwi -block.door-large.fulldescription = Blok, który można otworzyć i zamknąć poprzez dotknięcie -block.conduit.name = Rura -block.conduit.fulldescription = Podstawowy blok transportu cieczy. Działa jak przenośnik, tylko że z płynami. Najlepiej stosować z pompami bądź razem innymi rurami.\nMoże być używany jako pomost nad płynami dla wrogów i graczy. -block.pulseconduit.name = Rura impulsowa -block.pulseconduit.fulldescription = Zaawansowany blok transportu cieczy. Transportuje ciecze szybciej i przechowuje więcej niż rury standardowe. -block.liquidrouter.name = Rozdzielacz płynów -block.liquidrouter.fulldescription = Działa podobnie do zwykłego rozdzielacza. Akceptuje wejście cieczy z jednej strony i przekazuje ją na pozostałe strony. Przydatny do rozdzielania cieczy z jednej rury do wielu. -block.conveyor.name = Przenośnik -block.conveyor.fulldescription = Podstawowy blok transportu przedmiotów. Przenosi przedmioty do przodu i automatycznie umieszcza je w blokach. Może być używany jako pomost nad płynami dla wrogów i graczy. -block.steelconveyor.name = Przenośnik stalowy -block.steelconveyor.fulldescription = Zaawansowany blok transportu przedmiotów. Przenosi elementy szybciej niż standardowe przenośniki. -block.poweredconveyor.name = przenośnik impulsowy -block.poweredconveyor.fulldescription = Najszybszy blok transportowy przedmiotów. -block.router.name = Rozdzielacz -block.router.fulldescription = Przyjmuje przedmioty z jednego kierunku i przekazuje je w 3 innych kierunkach. Może również przechowywać pewną liczbę przedmiotów. Przydatny do dzielenia materiałów z jednego wiertła na wiele dział. -block.junction.name = węzeł -block.junction.fulldescription = Działa jako pomost dla dwóch skrzyżowanych przenośników taśmowych. Przydatny w sytuacjach, gdy dwa różne przenośniki przenoszą różne materiały do ​​różnych lokalizacji. -block.conveyortunnel.name = tunel przenośnikowy -block.conveyortunnel.fulldescription = Transportuje przedmiot pod blokami. Aby użyć, umieść jeden tunel prowadzący do bloku, który ma zostać tunelowany, i jeden po drugiej stronie. Upewnij się, że oba tunele są skierowane w przeciwnych kierunkach, czyli w wejście do bloku podającego, a wyjście do bloku odbierającego. -block.liquidjunction.name = Węzeł dla płynów -block.liquidjunction.fulldescription = Skrzyżowanie dla rurociągów. Przydatne w sytuacji, w której transportujemy różne ciecze w rurach, które muszą się przeciąć. -block.liquiditemjunction.name = Węzeł rur i przenośników -block.liquiditemjunction.fulldescription = Skrzyżowanie przenośników taśmowych oraz rur. -block.powerbooster.name = Wzmacniacz energii -block.powerbooster.fulldescription = Dystrybuuje moc do wszystkich bloków w swoim promieniu. -block.powerlaser.name = Przekaźnik -block.powerlaser.fulldescription = Tworzy laser, który przesyła moc do bloku przed nim. Nie generuje energii. Najlepiej stosować z generatorami lub innymi przekaźnikami. -block.powerlaserrouter.name = Duży rozdzielacz energii -block.powerlaserrouter.fulldescription = Przekaźnik, który dystrybuuje energię w trzech kierunkach jednocześnie. Przydatne w sytuacjach, w których wymagane jest zasilanie wielu bloków z jednego generatora. -block.powerlasercorner.name = Rozdzielacz energii -block.powerlasercorner.fulldescription = Przekaźnik, który dystrybuuje energię w dwóch kierunkach jednocześnie. Przydatny w sytuacjach, gdy wymagane jest zasilanie wielu bloków z jednego generatora, a router jest nieprecyzyjny. -block.teleporter.name = teleporter -block.teleporter.fulldescription = Zaawansowany blok transportu przedmiotów. Teleporty przenoszą przedmioty do innych teleportów tego samego koloru. Jeżeli nie istnieją teleporty z tym samym kolorem, to nie niczego nigdzie nie przenosi. Jeśli istnieje wiele teleportów tego samego koloru, wybierany jest losowy. Zużywa energię. Stuknij aby zmienić kolor. -block.sorter.name = Sortownik -block.sorter.fulldescription = Sortuje przedmioty według rodzaju materiału. Materiał do zaakceptowania jest oznaczony w bloku. Wszystkie pasujące przedmioty są wysyłane do przodu, wszystko inne jest wyprowadzane na lewo i na prawo. -block.core.name = Rdzeń -block.pump.name = pompa -block.pump.fulldescription = Pompuje ciecze z bloku źródłowego - zwykle wody, lawy lub oleju. Wyprowadza ciecz do pobliskich rur. -block.fluxpump.name = pompa strumieniowa -block.fluxpump.fulldescription = Zaawansowana wersja pompy. Przechowuje więcej cieczy i szybciej pompuje. -block.smelter.name = huta -block.smelter.fulldescription = Niezbędny blok rzemieślniczy. Po wprowadzeniu 1 żelaza i 1 węgla jako paliwa, wytwarza 1 stal. Zaleca się wprowadzanie żelaza i węgla na różne pasy, aby zapobiec zatkaniu. -block.crucible.name = tygiel -block.crucible.fulldescription = Zaawansowany blok rzemieślniczy. Po wprowadzeniu 1 tytanu, 1 stali i 1 węgla jako paliwa, otrzymuje 1 dirium. Zaleca się wprowadzanie węgla, stali i tytanu z różnych taśm, aby zapobiec zatkaniu. -block.coalpurifier.name = ekstraktor węgla -block.coalpurifier.fulldescription = Wyprowadza węgiel, gdy jest dostarczana duża ilością wody i kamienia. -block.titaniumpurifier.name = ekstraktor tytanu -block.titaniumpurifier.fulldescription = Wyprowadza tytan, gdy jest dostarczana duża ilości wody i żelaza. -block.oilrefinery.name = Rafineria ropy -block.oilrefinery.fulldescription = Rafinuje duże ilości oleju do postaci węgla. Przydatne do napędzania działek napędzanych węglem, gry nie ma w pobliżu wystarcząjacej ilości rud węgla. -block.stoneformer.name = Wytwarzacz kamienia -block.stoneformer.fulldescription = Schładza lawę do postaci kamień. Przydatny do produkcji ogromnych ilości kamienia do oczyszczania węgla. -block.lavasmelter.name = Huta lawowa -block.lavasmelter.fulldescription = Używa lawy, by przekształcić żelazo w stal. Alternatywa dla zwykłych hut. Przydatny w sytuacjach, gdy brakuje węgla. -block.stonedrill.name = wiertło do kamienia -block.stonedrill.fulldescription = Niezbędne wiertło. Po umieszczeniu na kamiennym podłożu, powoli i w nieskończoność wydobywa kamień. -block.irondrill.name = wiertło do żelaza -block.irondrill.fulldescription = Po umieszczeniu na rudzie żelaza, powoli i w nieskończoność wydobywa żelazo. -block.coaldrill.name = wiertło do węgla -block.coaldrill.fulldescription = Po umieszczeniu na rudzie węgla, powoli i w nieskończoność wydobywa węgiel. -block.thoriumdrill.name = wiertło do uranu -block.thoriumdrill.fulldescription = Wiertło zaawansowane. Po umieszczeniu na rudzie uranu, wydobywa uran w wolnym tempie przez czas nieokreślony. -block.titaniumdrill.name = wiertło do tytanu -block.titaniumdrill.fulldescription = Wiertło zaawansowane. Po umieszczeniu na rudzie tytanu, wydobywa tytan w wolnym tempie przez czas nieokreślony. -block.omnidrill.name = omnidril -block.omnidrill.fulldescription = Wiertło wielofunkcyjne. W szybkim tempie wydobywa każdą rudę. -block.coalgenerator.name = generator na węgiel -block.coalgenerator.fulldescription = Niezbędny generator. Generuje energię z węgla na wszystkie strony. -block.thermalgenerator.name = generator termiczny -block.thermalgenerator.fulldescription = Generuje energię z lawy. Generuje energię z węgla na wszystkie strony. -block.combustiongenerator.name = generator spalinowy -block.combustiongenerator.fulldescription = Generuje moc z oleju. Generuje energię z węgla na wszystkie strony. -block.rtgenerator.name = Generator RTG -block.rtgenerator.fulldescription = Generuje niewielkie ilości energii z rozpadu promieniotwórczego uranu. Generuje energię z węgla na wszystkie strony. -block.nuclearreactor.name = reaktor jądrowy -block.nuclearreactor.fulldescription = Zaawansowana wersja generatora RTG i zarazem najlepsze źródło energii. Generuje ją z uranu. Wymaga stałego chłodzenia wodą. Wybucha niemal natychmiast w momencie, gdy dostarczona zostanie niewystarczająca ilość płynu chłodzącego. -block.turret.name = działko -block.turret.fulldescription = Podstawowa, nieduża wieżyczka. Używa kamienia jako amunicji. Ma nieco większy zasięg niż działko podwójne. -block.doubleturret.name = działko podwójne -block.doubleturret.fulldescription = Nieco mocniejsza wersja działka. Używa kamienia jako amunicji. Znacznie więcej obrażeń, ale ma mniejszy zasięg. Wystrzeliwuje dwie kule. -block.machineturret.name = działko szybkostrzelne -block.machineturret.fulldescription = Standardowa, wszechstronna wieża. Używa żelaza jako amunicji. Strzela dość szybko i ma przyzwoite uszkodzenia. -block.shotgunturret.name = działko odłamkowe -block.shotgunturret.fulldescription = Standardowa wieża. Używa żelaza do amunicji. Wystrzeliwuje 7 pocisków. Niższy zasięg, ale większe obrażenia niż działko szybkostrzelne. -block.flameturret.name = miotacz ognia -block.flameturret.fulldescription = Zaawansowana wieżyczka bliskiego zasięgu. Używa węgla jako amunicji. Ma bardzo niski zasięg, ale bardzo duże obrażenia. Dobra na krótkie dystanse. Zalecana do stosowania za ścianami. -block.sniperturret.name = karabin -block.sniperturret.fulldescription = Zaawansowana wieżyczka dalekiego zasięgu. Używa stali jako amunicji. Ma bardzo duże obrażenia, ale niski współczynnik ognia. Kosztowne w użyciu, ale można je umieścić z dala od linii wroga ze względu na jego zasięg. -block.mortarturret.name = Miotacz odłamków -block.mortarturret.fulldescription = Zaawansowana, bardzo dokładne działko rozpryskowe. Używa węgla jako amunicji. Wystrzeliwuje grad eksplodujących pocisków. Przydatny dla dużych hord wrogów. -block.laserturret.name = działo laserowe -block.laserturret.fulldescription = Zaawansowana wieża z jednym celem. Używa energii. Dobra wieża o średnim zasięgu. Atakuje tylko 1 cel i nigdy nie pudłuje. -block.waveturret.name = Działo Tesli -block.waveturret.fulldescription = Zaawansowana wieża celującai. Używa mocy. Średni zasięg. Nigdy nie trafia. Stosuje niskie obrażenia, ale może trafić wielu wrogów jednocześnie z oświetleniem łańcuszkiem. -block.plasmaturret.name = Działo plazmowe -block.plasmaturret.fulldescription = Wysoce zaawansowana wersja miotacza ognia. Używa węgla jako amunicji. Zadaje bardzo duże obrażenia i posiada średni zasięg. -block.chainturret.name = Działo uranowe -block.chainturret.fulldescription = Najlepsze działo szybkiego ognia. Używa uranu jako amunicji. Wystrzeliwuje duże spirale z dużą szybkością. Posiada średni zasięg. -block.titancannon.name = Potężne działo uranowe -block.titancannon.fulldescription = Najlepsze działo dalekiego zasięgu. Używa uranu jako amunicji. Wystrzeliwuje duże pociski z odłamkami. Ma średnią szybkostrzelność i duży promień rażenia. -block.playerspawn.name = Spawn gracza -block.enemyspawn.name = Spawn wroga +text.about=Stworzony przez [ROYAL] Anuken. []\nPierwotnie wpis w [orange] GDL [] MM Jam.\n\nNapisy:\n- SFX wykonane z pomocą [YELLOW] bfxr []\n- Muzyka wykonana przez [GREEN] RoccoW [] / znaleziona na [lime] FreeMusicArchive.org []\n\nSpecjalne podziękowania dla:\n- [coral] MitchellFJN []: obszerne testowanie i feedback\n- [niebo] Luxray5474 []: prace związane z wiki, pomoc z kodem\n- Wszystkich beta testerów na itch.io i Google Play\n +text.discord=Odwiedź nasz serwer Discord +text.gameover=Rdzeń został zniszczony. +text.highscore=[YELLOW] Nowy rekord! +text.lasted=Wytrwałeś do fali +text.level.highscore=Rekord: [accent]{0} +text.level.delete.title=Potwierdź kasowanie +text.level.select=Wybrany poziom +text.level.mode=Tryb gry: +text.savegame=Zapisz Grę +text.loadgame=Wczytaj grę +text.joingame=Gra wieloosobowa +text.quit=Wyjdź +text.about.button=O grze +text.name=Nazwa: +text.public=Publiczny +text.players={0} graczy online +text.server.player.host={0} (host) +text.players.single={0} gracz online +text.server.mismatch=Błąd pakietu: możliwa niezgodność wersji klienta/serwera.\nUpewnij się, że Ty i host macie najnowszą wersję Mindustry! +text.server.closing=[accent] Zamykanie serwera ... +text.server.kicked.kick=Zostałeś wyrzucony z serwera! +text.server.kicked.invalidPassword=Nieprawidłowe hasło! +text.server.kicked.clientOutdated=Nieaktualna gra! Zaktualizują ją! +text.server.kicked.serverOutdated=Nieaktualna gra! Zaktualizują ją! +text.server.connected={0} dołączył do gry . +text.server.disconnected={0} został rozłączony. +text.nohost=Nie można hostować serwera na mapie niestandardowej! +text.hostserver=Serwer hosta +text.host=Host +text.hosting=[accent] Otwieranie serwera ... +text.hosts.refresh=Odśwież +text.hosts.discovering=Wyszukiwanie gier w sieci LAN +text.server.refreshing=Odświeżanie serwera +text.hosts.none=[lightgray] Brak serwerów w sieci LAN! +text.host.invalid=[scarlet] Nie można połączyć się z hostem. +text.server.friendlyfire=Bratobójczy ogień +text.server.add=Dodaj serwer +text.server.delete=Czy na pewno chcesz usunąć ten serwer? +text.server.hostname=Host: {0} +text.server.edit=Edytuj serwer +text.joingame.byip=Dołącz przez IP... +text.joingame.title=Dołącz do gry +text.joingame.ip=IP: +text.disconnect=Rozłączony. +text.connecting=[accent]Łączenie ... +text.connecting.data=[accent]Ładowanie danych świata... +text.connectfail=[crimson]Nie można połączyć się z serwerem: [orange] {0} +text.server.port=Port: +text.server.addressinuse=Adres jest już w użyciu! +text.server.invalidport=Nieprawidłowy numer portu. +text.server.error=[crimson] Błąd hostowania serwera: [orange] {0} +text.tutorial.back=< Cofnij +text.tutorial.next=Dalej > +text.save.new=Nowy zapis +text.save.overwrite=Czy na pewno chcesz nadpisać zapis gry? +text.overwrite=Nadpisz +text.save.none=Nie znaleziono zapisów gry! +text.saveload=[akcent]Zapisywanie... +text.savefail=Nie udało się zapisać gry! +text.save.delete.confirm=Czy na pewno chcesz usunąć ten zapis gry? +text.save.delete=Usuń +text.save.export=Eksportuj +text.save.import.invalid=[orange]Zapis gry jest niepoprawny! +text.save.import.fail=[crimson]Nie udało się zaimportować zapisu: [orange] {0} +text.save.export.fail=[crimson]Nie można wyeksportować zapisu: [orange] {0} +text.save.import=Importuj +text.save.newslot=Zapisz nazwę: +text.save.rename=Zmień nazwę +text.save.rename.text=Zmień nazwę +text.selectslot=Wybierz zapis. +text.slot=[accent]Slot {0} +text.save.corrupted=[orange]Zapis gry jest uszkodzony lub nieprawidłowy! +text.empty= +text.on=Włączone +text.off=Wyłączone +text.save.autosave=Zapisywanie automatyczne +text.save.map=Mapa: {0} +text.save.wave=Fala: {0} +text.save.date=Ostatnio zapisano: {0} +text.confirm=Potwierdź +text.delete=Usuń +text.ok=Ok +text.open=Otwórz +text.cancel=Anuluj +text.openlink=Otwórz link +text.back=Wróć +text.quit.confirm=Czy na pewno chcesz wyjść? +text.loading=[accent]Ładowanie ... +text.wave=[orange]Fala {0} +text.wave.waiting=Fala w {0} +text.waiting=Oczekiwanie... +text.enemies={0} wrogów +text.enemies.single={0} wróg +text.loadimage=Załaduj obraz +text.saveimage=Zapisz obraz +text.editor.badsize=[orange]Nieprawidłowe wymiary obrazu![]\nWymiary poprawne: {0} +text.editor.errorimageload=Błąd podczas ładowania pliku obrazu: [orange]{0} +text.editor.errorimagesave=Błąd podczas zapisywania pliku obrazu: [orange]{0} +text.editor.generate=Generuj +text.editor.resize=Zmień rozmiar +text.editor.loadmap=Załaduj mapę +text.editor.savemap=Zapisz mapę +text.editor.loadimage=Załaduj obraz +text.editor.saveimage=Zapisz obraz +text.editor.unsaved=[scarlet]Masz niezapisane zmiany![]\nCzy na pewno chcesz wyjść? +text.editor.brushsize=Rozmiar pędzla: {0} +text.editor.noplayerspawn=Ta mapa nie ma ustawionego spawnu gracza! +text.editor.manyplayerspawns=Mapy nie mogą mieć więcej niż jeden punkt spawnu gracza! +text.editor.manyenemyspawns=Nie może mieć więcej niż {0} punktów spawnu wroga! +text.editor.resizemap=Zmień rozmiar mapy +text.editor.resizebig=[scarlet]Uwaga![]\nMapy większe niż 256 jednostek mogą przycinać i być niestabilne. +text.editor.mapname=Nazwa mapy: +text.editor.overwrite=[accent]Uwaga!\nSpowoduje to nadpisanie istniejącej mapy. +text.editor.selectmap=Wybierz mapę do załadowania: +text.width=Szerokość: +text.height=Wysokość: +text.randomize=Wylosuj +text.apply=Zastosuj +text.update=Zaktualizuj +text.menu=Menu +text.play=Graj +text.load=Wczytaj +text.save=Zapisz +text.language.restart=Uruchom grę ponownie aby ustawiony język zaczął funkcjonować. +text.settings.language=Język +text.settings=Ustawienia +text.tutorial=Poradnik +text.editor=Edytor +text.mapeditor=Edytor map +text.donate=Wspomóż nas +text.settings.reset=Przywróć domyślne +text.settings.controls=Sterowanie +text.settings.game=Gra +text.settings.sound=Dźwięk +text.settings.graphics=Grafika +text.upgrades=Ulepszenia +text.purchased=[LIME]Stworzono! +text.weapons=Bronie +text.paused=Wstrzymano +text.info.title=[accent]Informacje +text.error.title=[crimson]Wystąpił błąd +text.error.crashmessage=[SCARLET]Wystąpił nieoczekiwany błąd, który spowodowałby awarię.[]\nProszę, powiadom dewelopera gry o tym błędzie, pisząc jak do niego doszło: [ORANGE]anukendev@gmail.com[] +text.error.crashtitle=Wystąpił błąd +text.blocks.blockinfo=Informacje o bloku +text.blocks.powercapacity=Moc znamionowa +text.blocks.powershot=moc / strzał +text.blocks.size=Rozmiar +text.blocks.liquidcapacity=Pojemność cieczy +text.blocks.maxitemssecond=Maksymalna liczba przedmiotów / Sekunda +text.blocks.powerrange=Zakres mocy +text.blocks.itemcapacity=Pojemność przedmiotów +text.blocks.inputliquid=Potrzebna ciecz +text.blocks.inputitem=Potrzebne przedmioty +text.blocks.explosive=Wysoce wybuchowy! +text.blocks.health=Zdrowie +text.blocks.inaccuracy=Niedokładność +text.blocks.shots=Strzały +text.blocks.inputcapacity=Pojemność wejściowa +text.blocks.outputcapacity=Wydajność wyjściowa +setting.difficulty.easy=łatwy +setting.difficulty.normal=normalny +setting.difficulty.hard=trudny +setting.difficulty.insane=szalony +setting.difficulty.purge=Czystka +setting.difficulty.name=Poziom trudności +setting.screenshake.name=Trzęsienie się ekranu +setting.smoothcam.name=Płynna kamera +setting.indicators.name=Wskaźniki wroga +setting.effects.name=Wyświetlanie efektów +setting.sensitivity.name=Czułość kontrolera +setting.saveinterval.name=Interwał automatycznego zapisywania +setting.seconds=Sekundy +setting.fps.name=Widoczny licznik FPS +setting.vsync.name=Synchronizacja pionowa +setting.lasers.name=Pokaż lasery zasilające +setting.healthbars.name=Pokaż paski zdrowia jednostki +setting.pixelate.name=Rozpikselizowany obraz +setting.musicvol.name=Głośność muzyki +setting.mutemusic.name=Wycisz muzykę +setting.sfxvol.name=Głośność dźwięków +setting.mutesound.name=Wycisz dźwięki +map.maze.name=labirynt +map.fortress.name=twierdza +map.sinkhole.name=wgłębienie +map.caves.name=jaskinie +map.volcano.name=wulkan +map.caldera.name=kaldera +map.scorch.name=opalacz +map.desert.name=pustynia +map.island.name=wyspa +map.grassland.name=łąka +map.tundra.name=tundra +map.spiral.name=spirala +map.tutorial.name=Poradnik +keybind.move_x.name=Poruszanie w poziomie +keybind.move_y.name=Poruszanie w pionie +keybind.select.name=Wybieranie +keybind.break.name=Niszczenie +keybind.shoot.name=Strzelanie +keybind.zoom_hold.name=Inicjator przybliżania +keybind.zoom.name=Przybliżanie +keybind.menu.name=menu +keybind.pause.name=pauza +keybind.dash.name=przyśpieszenie +keybind.rotate_alt.name=Obracanie (1) +keybind.rotate.name=Obracanie (2) +mode.waves.name=Fale +mode.sandbox.name=sandbox +mode.freebuild.name=budowanie +item.stone.name=kamień +item.coal.name=węgiel +item.titanium.name=tytan +item.thorium.name=uran +item.sand.name=piasek +liquid.water.name=woda +liquid.lava.name=lawa +liquid.oil.name=olej +block.door.name=drzwi +block.door-large.name=duże drzwi +block.conduit.name=Rura +block.pulseconduit.name=Rura impulsowa +block.liquidrouter.name=Rozdzielacz płynów +block.conveyor.name=Przenośnik +block.router.name=Rozdzielacz +block.junction.name=węzeł +block.liquidjunction.name=Węzeł dla płynów +block.sorter.name=Sortownik +block.smelter.name=huta +text.credits=Credits +text.link.discord.description=the official Mindustry discord chatroom +text.link.github.description=Game source code +text.link.dev-builds.description=Unstable development builds +text.link.trello.description=Official trello board for planned features +text.link.itch.io.description=itch.io page with PC downloads and web version +text.link.google-play.description=Google Play store listing +text.link.wiki.description=official Mindustry wiki +text.linkfail=Failed to open link!\nThe URL has been copied to your cliboard. +text.editor.web=The web version does not support the editor!\nDownload the game to use it. +text.web.unsupported=The web version does not support this feature! Download the game to use it. +text.multiplayer.web=This version of the game does not support multiplayer!\nTo play multiplayer from your browser, use the "multiplayer web version" link at the itch.io page. +text.host.web=The web version does not support hosting games! Download the game to use this feature. +text.map.delete=Are you sure you want to delete the map "[orange]{0}[]"? +text.construction.title=Block Construction Guide +text.construction=You've just selected [accent]block construction mode[].\n\nTo begin placing, simply tap a valid location near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Shift the selection[] by holding and dragging any block in the selection.\n- [accent]Place blocks in a line[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel construction or selection[] by pressing the X at the bottom left. +text.deconstruction.title=Block Deconstruction Guide +text.deconstruction=You've just selected [accent]block deconstruction mode[].\n\nTo begin breaking, simply tap a block near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin de-constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Remove blocks in an area[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel deconstruction or selection[] by pressing the X at the bottom left. +text.showagain=Don't show again next session +text.unlocks=Unlocks +text.addplayers=Add/Remove Players +text.newgame=New Game +text.maps=Maps +text.maps.none=[LIGHT_GRAY]No maps found! +text.unlocked=New Block Unlocked! +text.unlocked.plural=New Blocks Unlocked! +text.server.rollback=Rollback +text.server.rollback.numberfield=Rollback Amount: +text.blocks.editlogs=Edit Logs +text.block.editlogsnotfound=[red]There are no edit logs for this location. +text.server.kicked.fastShoot=You are shooting too quickly. +text.server.kicked.banned=You are banned on this server. +text.server.kicked.recentKick=You have been kicked recently.\nWait before connecting again. +text.server.kicked.nameInUse=There is someone with that name\nalready on this server. +text.server.kicked.nameEmpty=Your name must contain at least one character or number. +text.server.kicked.idInUse=You are already on this server! Connecting with two accounts is not permitted. +text.server.kicked.customClient=This server does not support custom builds. Download an official version. +text.host.info=The [accent]host[] button hosts a server on ports [scarlet]6567[] and [scarlet]6568.[]\nAnybody on the same [LIGHT_GRAY]wifi or local network[] should be able to see your server in their server list.\n\nIf you want people to be able to connect from anywhere by IP, [accent]port forwarding[] is required.\n\n[LIGHT_GRAY]Note: If someone is experiencing trouble connecting to your LAN game, make sure you have allowed Mindustry access to your local network in your firewall settings. +text.join.info=Here, you can enter a [accent]server IP[] to connect to, or discover [accent]local network[] servers to connect to.\nBoth LAN and WAN multiplayer is supported.\n\n[LIGHT_GRAY]Note: There is no automatic global server list; if you want to connect to someone by IP, you would need to ask the host for their IP. +text.trace=Trace Player +text.trace.playername=Player name: [accent]{0} +text.trace.ip=IP: [accent]{0} +text.trace.id=Unique ID: [accent]{0} +text.trace.android=Android Client: [accent]{0} +text.trace.modclient=Custom Client: [accent]{0} +text.trace.totalblocksbroken=Total blocks broken: [accent]{0} +text.trace.structureblocksbroken=Structure blocks broken: [accent]{0} +text.trace.lastblockbroken=Last block broken: [accent]{0} +text.trace.totalblocksplaced=Total blocks placed: [accent]{0} +text.trace.lastblockplaced=Last block placed: [accent]{0} +text.invalidid=Invalid client ID! Submit a bug report. +text.server.bans=Bans +text.server.bans.none=No banned players found! +text.server.admins=Admins +text.server.admins.none=No admins found! +text.server.outdated=[crimson]Outdated Server![] +text.server.outdated.client=[crimson]Outdated Client![] +text.server.version=[lightgray]Version: {0} +text.server.custombuild=[yellow]Custom Build +text.confirmban=Are you sure you want to ban this player? +text.confirmunban=Are you sure you want to unban this player? +text.confirmadmin=Are you sure you want to make this player an admin? +text.confirmunadmin=Are you sure you want to remove admin status from this player? +text.disconnect.data=Failed to load world data! +text.save.difficulty=Difficulty: {0} +text.copylink=Copy Link +text.changelog.title=Changelog +text.changelog.loading=Getting changelog... +text.changelog.error.android=[orange]Note that the changelog sometimes does not work on Android 4.4 and below!\nThis is due to an internal Android bug. +text.changelog.error.ios=[orange]The changelog is currently not supported in iOS. +text.changelog.error=[scarlet]Error getting changelog!\nCheck your internet connection. +text.changelog.current=[yellow][[Current version] +text.changelog.latest=[orange][[Latest version] +text.saving=[accent]Saving... +text.unknown=Unknown +text.custom=Custom +text.builtin=Built-In +text.map.delete.confirm=Are you sure you want to delete this map? This action cannot be undone! +text.map.random=[accent]Random Map +text.map.nospawn=This map does not have any cores for the player to spawn in! Add a [ROYAL]blue[] core to this map in the editor. +text.editor.slope=\\ +text.editor.openin=Open In Editor +text.editor.oregen=Ore Generation +text.editor.oregen.info=Ore Generation: +text.editor.mapinfo=Map Info +text.editor.author=Author: +text.editor.description=Description: +text.editor.name=Name: +text.editor.teams=Teams +text.editor.elevation=Elevation +text.editor.saved=Saved! +text.editor.save.noname=Your map does not have a name! Set one in the 'map info' menu. +text.editor.save.overwrite=Your map overwrites a built-in map! Pick a different name in the 'map info' menu. +text.editor.import.exists=[scarlet]Unable to import:[] a built-in map named '{0}' already exists! +text.editor.import=Import... +text.editor.importmap=Import Map +text.editor.importmap.description=Import an already existing map +text.editor.importfile=Import File +text.editor.importfile.description=Import an external map file +text.editor.importimage=Import Terrain Image +text.editor.importimage.description=Import an external map image file +text.editor.export=Export... +text.editor.exportfile=Export File +text.editor.exportfile.description=Export a map file +text.editor.exportimage=Export Terrain Image +text.editor.exportimage.description=Export a map image file +text.editor.overwrite.confirm=[scarlet]Warning![] A map with this name already exists. Are you sure you want to overwrite it? +text.fps=FPS: {0} +text.tps=TPS: {0} +text.ping=Ping: {0}ms +text.settings.rebind=Rebind +text.yes=Yes +text.no=No +text.blocks.targetsair=Targets Air +text.blocks.itemspeed=Units Moved +text.blocks.shootrange=Range +text.blocks.poweruse=Power Use +text.blocks.inputitemcapacity=Input Item Capacity +text.blocks.outputitemcapacity=Input Item Capacity +text.blocks.maxpowergeneration=Max Power Generation +text.blocks.powertransferspeed=Power Transfer +text.blocks.craftspeed=Production Speed +text.blocks.inputliquidaux=Aux Liquid +text.blocks.inputitems=Input Items +text.blocks.outputitem=Output Item +text.blocks.drilltier=Drillables +text.blocks.drillspeed=Base Drill Speed +text.blocks.liquidoutput=Liquid Output +text.blocks.liquiduse=Liquid Use +text.blocks.coolant=Coolant +text.blocks.coolantuse=Coolant Use +text.blocks.inputliquidfuel=Fuel Liquid +text.blocks.liquidfueluse=Liquid Fuel Use +text.blocks.reload=Reload +text.blocks.inputfuel=Fuel +text.blocks.fuelburntime=Fuel Burn Time +text.unit.blocks=blocks +text.unit.powersecond=power units/second +text.unit.liquidsecond=liquid units/second +text.unit.itemssecond=items/second +text.unit.pixelssecond=pixels/second +text.unit.liquidunits=liquid units +text.unit.powerunits=power units +text.unit.degrees=degrees +text.unit.seconds=seconds +text.unit.none= +text.unit.items=items +text.category.general=General +text.category.power=Power +text.category.liquids=Liquids +text.category.items=Items +text.category.crafting=Crafting +text.category.shooting=Shooting +setting.fullscreen.name=Fullscreen +setting.multithread.name=Multithreading +setting.previewopacity.name=Placing Preview Opacity +setting.minimap.name=Show Minimap +text.keybind.title=Rebind Keys +keybind.block_info.name=block_info +keybind.chat.name=chat +keybind.player_list.name=player_list +keybind.console.name=console +mode.text.help.title=Description of modes +mode.waves.description=the normal mode. limited resources and automatic incoming waves. +mode.sandbox.description=infinite resources and no timer for waves. +mode.freebuild.description=limited resources and no timer for waves. +content.item.name=Items +content.liquid.name=Liquids +content.unit-type.name=Units +content.recipe.name=Blocks +item.stone.description=A common raw material. Used for separating and refining into other materials, or melting into lava. +item.tungsten.name=Tungsten +item.tungsten.description=A common, but very useful structure material. Used in drills and heat-resistant blocks such as generators and smelteries. +item.lead.name=Lead +item.lead.description=A basic starter material. Used extensively in electronics and liquid transportation blocks. +item.coal.description=A common and readily available fuel. +item.carbide.name=Carbide +item.carbide.description=A tough alloy made with tungsten and carbon. Used in advanced transportation blocks and high-tier drills. +item.titanium.description=A rare super-light metal used extensively in liquid transportation, drills and aircraft. +item.thorium.description=A dense, radioactive metal used as structural support and nuclear fuel. +item.silicon.name=Silicon +item.silcion.description=An extremely useful semiconductor, with applications in solar panels and many complex electronics. +item.plastanium.name=Plastanium +item.plastanium.description=A light, ductile material used in advanced aircraft and fragmentation ammunition. +item.phase-matter.name=Phase Matter +item.surge-alloy.name=Surge Alloy +item.biomatter.name=Biomatter +item.biomatter.description=A clump of organic mush; used for conversion into oil or as a basic fuel. +item.sand.description=A common material that is used extensively in smelting, both in alloying and as a flux. +item.blast-compound.name=Blast Compound +item.blast-compound.description=A volatile compound used in bombs and explosives. While it can burned as fuel, this is not advised. +item.pyratite.name=Pyratite +item.pyratite.description=An extremely flammable substance used in incendiary weapons. +liquid.cryofluid.name=Cryofluid +text.item.explosiveness=[LIGHT_GRAY]Explosiveness: {0} +text.item.flammability=[LIGHT_GRAY]Flammability: {0} +text.item.radioactivity=[LIGHT_GRAY]Radioactivity: {0} +text.item.fluxiness=[LIGHT_GRAY]Flux Power: {0} +text.item.hardness=[LIGHT_GRAY]Hardness: {0} +text.liquid.heatcapacity=[LIGHT_GRAY]Heat Capacity: {0} +text.liquid.viscosity=[LIGHT_GRAY]Viscosity: {0} +text.liquid.temperature=[LIGHT_GRAY]Temperature: {0} +block.tungsten-wall.name=Tungsten Wall +block.tungsten-wall-large.name=Large Tungsten Wall +block.carbide-wall.name=Carbide Wall +block.carbide-wall-large.name=Large Carbide Wall +block.thorium-wall.name=Thorium Wall +block.thorium-wall-large.name=Large Thorium Wall +block.duo.name=Duo +block.scorch.name=Scorch +block.hail.name=Hail +block.lancer.name=Lancer +block.titanium-conveyor.name=Titanium Conveyor +block.splitter.name=Splitter +block.splitter.description=Outputs items into two opposite directions immediately after they are recieved. +block.router.description=Splits items into all 4 directions. Can store items as a buffer. +block.distributor.name=Distributor +block.distributor.description=A splitter that can split items into 8 directions. +block.sorter.description=Sorts items. If an item matches the selection, it is allowed to pass. Otherwise, the item is outputted to the left and right. +block.overflow-gate.name=Overflow Gate +block.overflow-gate.description=A combination splitter and router that only outputs to the left and right if the front path is blocked. +block.bridgeconveyor.name=Bridge Conveyor +block.bridgeconveyor.description=A conveyor that can go over other blocks, for up to two total blocks. +block.arc-smelter.name=Arc Smelter +block.silicon-smelter.name=Silicon Smelter +block.phase-weaver.name=Phase Weaver +block.pulverizer.name=Pulverizer +block.cryofluidmixer.name=Cryofluid Mixer +block.melter.name=Melter +block.incinerator.name=Incinerator +block.biomattercompressor.name=Biomatter Compressor +block.separator.name=Separator +block.centrifuge.name=Centrifuge +block.power-node.name=Power Node +block.power-node-large.name=Large Power Node +block.battery.name=Battery +block.battery-large.name=Large Battery +block.combustion-generator.name=Combustion Generator +block.turbine-generator.name=Turbine Generator +block.tungsten-drill.name=Tungsten Drill +block.carbide-drill.name=Carbide Drill +block.laser-drill.name=Laser Drill +block.water-extractor.name=Water Extractor +block.cultivator.name=Cultivator +block.dart-ship-factory.name=Dart Ship Factory +block.delta-mech-factory.name=Delta Mech Factory +block.dronefactory.name=Drone Factory +block.repairpoint.name=Repair Point +block.resupplypoint.name=Resupply Point +block.liquidtank.name=Liquid Tank +block.bridgeconduit.name=Bridge Conduit +block.mechanical-pump.name=Mechanical Pump +block.itemsource.name=Item Source +block.itemvoid.name=Item Void +block.liquidsource.name=Liquid Source +block.powervoid.name=Power Void +block.powerinfinite.name=Power Infinite +block.unloader.name=Unloader +block.sortedunloader.name=Sorted Unloader +block.vault.name=Vault +block.wave.name=Wave +block.swarmer.name=Swarmer +block.salvo.name=Salvo +block.ripple.name=Ripple +block.phase-conveyor.name=Phase Conveyor +block.bridge-conveyor.name=Bridge Conveyor +block.plastanium-compressor.name=Plastanium Compressor +block.pyratite-mixer.name=Pyratite Mixer +block.blast-mixer.name=Blast Mixer +block.solidifer.name=Solidifer +block.solar-panel.name=Solar Panel +block.solar-panel-large.name=Large Solar Panel +block.oil-extractor.name=Oil Extractor +block.javelin-ship-factory.name=Javelin Ship factory +block.drone-factory.name=Drone Factory +block.fabricator-factory.name=Fabricator Factory +block.repair-point.name=Repair Point +block.resupply-point.name=Resupply Point +block.pulse-conduit.name=Pulse Conduit +block.phase-conduit.name=Phase Conduit +block.liquid-router.name=Liquid Router +block.liquid-tank.name=Liquid Tank +block.liquid-junction.name=Liquid Junction +block.bridge-conduit.name=Bridge Conduit +block.rotary-pump.name=Rotary Pump +block.nuclear-reactor.name=Nuclear Reactor diff --git a/core/assets/bundles/bundle_pt_BR.properties b/core/assets/bundles/bundle_pt_BR.properties index f4fd7b0cee..873cfa0d5a 100644 --- a/core/assets/bundles/bundle_pt_BR.properties +++ b/core/assets/bundles/bundle_pt_BR.properties @@ -3,9 +3,8 @@ text.discord=Junte-se ao Discord do Mindustry! (Lá nós falamos em inglês) text.gameover=O núcleo foi destruído. text.highscore=[YELLOW]Novo recorde! text.lasted=Você durou até a horda -text.level.highscore= Melhor\npontuação: [accent] {0} +text.level.highscore=Melhor\npontuação: [accent] {0} text.level.delete.title=Confirmar exclusão -text.level.delete=Você tem certeza que quer excluir\no mapa "[orange]{0}"? text.level.select=Seleção de Fase text.level.mode=Modo de Jogo: text.savegame=Salvar Jogo @@ -33,7 +32,6 @@ text.loading=[accent]Carregando... text.wave=[orange]Horda {0} text.wave.waiting=Horda em {0} text.waiting=Aguardando... -text.countdown=Horda em {0} text.enemies={0} Inimigos restantes text.enemies.single={0} Inimigo restante text.loadimage=Carregar\nImagem @@ -56,7 +54,6 @@ text.editor.resizemap=Redimensionar Mapa text.editor.resizebig=[scarlet]Aviso!\n[]Mapas maiores que 256 unidades podem ser 'lentos' e instáveis text.editor.mapname=Nome do Mapa: text.editor.overwrite=[accent]Aviso!\nIsso sobrescreve um mapa existente. -text.editor.failoverwrite=[crimson]Não é possível salvar sobre o mapa padrão! text.editor.selectmap=Selecione uma mapa para carregar: text.width=Largura: text.height=Altura: @@ -81,58 +78,29 @@ text.upgrades=Melhorias text.purchased=[LIME]Comprado! text.weapons=Arsenal text.paused=Pausado -text.respawn=Reaparecendo em text.error.title=[crimson]Um erro ocorreu text.error.crashmessage=[SCARLET]Um erro inesperado aconteceu, que pode ter causado o jogo a fechar. []Por favor, informe as exatas circunstâncias em que o erro ocorreu ao desenvolvidor:\n[ORANGE]anukendev@gmail.com[] text.error.crashtitle=Um erro ocorreu. -text.blocks.extrainfo=[accent]Informação extra: text.blocks.blockinfo=Informação do Bloco text.blocks.powercapacity=Capacidade de Energia text.blocks.powershot=Energia/tiro -text.blocks.powersecond=Energia/segundo -text.blocks.powerdraindamage=Energia/dano -text.blocks.shieldradius=Raio do Escudo -text.blocks.itemspeedsecond=Itens/segundo -text.blocks.range=Alcance text.blocks.size=Tamanho -text.blocks.powerliquid=Energia/Líquido -text.blocks.maxliquidsecond=Entrada Máx. Líquido/segundo text.blocks.liquidcapacity=Capacidade de Líquido -text.blocks.liquidsecond=Líquido/segundo -text.blocks.damageshot=Dano/tiro -text.blocks.ammocapacity=Munição Máxima -text.blocks.ammo=Munição -text.blocks.ammoitem=Munição/item text.blocks.maxitemssecond=Máximo de itens/segundo text.blocks.powerrange=Alcance da Energia -text.blocks.lasertilerange=Alcance do Laser (em células) -text.blocks.capacity=Capacidade text.blocks.itemcapacity=Capacidade de Itens -text.blocks.powergenerationsecond=Geração de Energia/segundo -text.blocks.generationsecondsitem=Tempo de geração/item -text.blocks.input=Entrada text.blocks.inputliquid=Líquido de entrada text.blocks.inputitem=Item de entrada -text.blocks.output=Saída -text.blocks.secondsitem=Segundos/item -text.blocks.maxpowertransfersecond=Transferência máxima de Energia/segundo text.blocks.explosive=Altamente Explosivo! -text.blocks.repairssecond=Reparo/segundo text.blocks.health=Saúde text.blocks.inaccuracy=Imprecisão text.blocks.shots=Tiros -text.blocks.shotssecond=Taxa de tiro -text.placemode=Modo construção -text.breakmode=Modo remoção -text.health=Saúde setting.difficulty.easy=Fácil setting.difficulty.normal=Normal setting.difficulty.hard=Difícil setting.difficulty.name=Dificuldade setting.screenshake.name=Balanço da Tela -#Tremor da tela? setting.smoothcam.name=Câmera suave -#Suavizar Câmera? setting.indicators.name=Indicadores de Inimigos setting.effects.name=Particulas setting.sensitivity.name=Sensibilidade do Controle @@ -158,54 +126,10 @@ map.grassland.name=grassland map.tundra.name=tundra map.spiral.name=spiral map.tutorial.name=tutorial -tutorial.intro.text=[yellow]Bem-vindo ao tutorial.[] Para começar aperte 'próximo'. -tutorial.moveDesktop.text=Para mover, use as teclas [orange][[WASD][]. Segure [orange]shift[] para mover rápido. Segure [orange]CTRL[] enquanto usa a [orange]roda do mouse[] para aumentar ou diminuir o zoom. -tutorial.shootInternal.text=Use o mouse para mirar, segure [orange]botão esquerdo do mouse[] para atirar. Tente praticar no [yellow]alvo[]. -tutorial.moveAndroid.text=Para arrastar a visão, passe um dedo pela tela. Pince com os dedos para aumentar ou diminuir o zoom. -tutorial.placeSelect.text=Tente selecionar uma [yellow]esteira[] do menu de blocos no canto inferior direito. -tutorial.placeConveyorDesktop.text=Use a [orange][[roda do mouse][] para girar a esteira até que aponte [orange]para frente[], então coloque-a no [yellow]local marcado[] usando o [orange][[botão esquerdo do mouse][]. -tutorial.placeConveyorAndroid.text=Use o [orange][[botão de girar][] para girar a esteira para que aponte [orange]para frente[], arraste-a para a posição e então coloque-a na [yellow]posição marcada[] usando o [orange][[botão de confirmação][]. -tutorial.placeConveyorAndroidInfo.text=Você também pode apertar no ícone com uma cruz no canto inferior esquerdo para alterar para o [orange][[modo de toque][], e colocar blocos apertando na tela. No modo de toque, blocos podem ser girados com a seta no canto inferior esquerdo. Aperte [yellow]próximo[] para tentar. -tutorial.placeDrill.text=Agora selecione e coloque uma [yellow]broca de pedra[] no local marcado. -tutorial.blockInfo.text=Se quiser saber mais sobre os blocos, você pode apertar o [orange]símbolo de interrogação[] no canto superior direito para ler mais. -tutorial.deselectDesktop.text=Você pode cancelar a seleção de um bloco usando o [orange][botão direito do mouse[]. -tutorial.deselectAndroid.text=ocê pode cancelar a seleção de um bloco apertando o botão [orange]X[]. -tutorial.drillPlaced.text=A broca produzirá [yellow]pedra[], direcionando o produzido para a esteira a qual moverá a pedra para o [yellow]núcleo[]. -tutorial.drillInfo.text=Minérios diferentes precisam de diferentes brocas. Pedra precisam de brocas de pedra, Ferro de brocas de ferro, etc. -tutorial.drillPlaced2.text=Itens movidos para o núcleo são colocados em seu [yellow]inventário[], no canto superior esquerdo. Colocar blocos gasta os recursos do inventário. -tutorial.moreDrills.text=Você pode conectar várias brocas e esteiras, veja. -tutorial.deleteBlock.text=Você pode excluir blocos clickando com o [orange]botão direito do mouse[] no bloco que quiser destruir. Tente excluir esta esteira. -tutorial.deleteBlockAndroid.text=Você pode excluir blocos [orange]apertando na cruz[] no [orange]menu modo de quebra[] no canto inferior esquerdo e então apertando no bloco desejado. Tente excluir esta esteira. -tutorial.placeTurret.text=Agora, selecione e construa uma [yellow]torre[] no [yellow]local marcado[]. -tutorial.placedTurretAmmo.text=Esta torre aceitará [yellow]munição[] da esteira. Você pode ver quanta munição elas tem passando o mouse sobre elas e verificando a [green]barra verde[]. -tutorial.turretExplanation.text=As torres irão atirar no inimigo mais próximo que estiver ao alcance, contanto que tenham munição suficiente. -tutorial.waves.text=A cada [yellow]60[] segundos, uma horda de [coral]inimigos[] irá aparecer em locais específicos e tentará destruir o núcleo. -tutorial.coreDestruction.text=Seu objetivo é [yellow]defender o núcleo[]. Se o núcleo for destruído, vecê [coral]perde o jogo[]. -tutorial.pausingDesktop.text=Se você precisar parar por alguns instantes, aperte o [orange]botão de pausa[] no canto superior esquerdo ou [orange]barra de espaço[] para pausar o jogo. Você pode colocar blocos enquanto o jogo esta pausado, porém não poderá se mover ou atirar. -tutorial.pausingAndroid.text=Se você precisar parar por alguns instantes, aperte o [orange]botão de pausa[] no canto superior esquerdo ou [orange]barra de espaço[] para pausar o jogo. Você pode colocar blocos enquanto o jogo esta pausado. -tutorial.purchaseWeapons.text=Você pode comprar novas [yellow]armas[] para seu mecha, basta abrir o menu de melhorias no canto inferior esquerdo. -tutorial.switchWeapons.text=Alterne entre suas armas clickando em seu ícone ou usando as teclas numéricas [orange][[1-9][]. -tutorial.spawnWave.text=Uma horda esta vindo. Destrúa-os. -tutorial.pumpDesc.text=Em hordas mais avançadas, você talvez precise de [yellow]bombas[] para distribuir líquidos para geradores ou extratores. -tutorial.pumpPlace.text=Bombas trabalham de forma semelhante às brocas, porém elas produzem líquidos ao envés de minérios. Tente colocar uma bomba na [yellow]célula de petróleo designada[]. -tutorial.conduitUse.text=Agora coloque um [orange]cano[] levando para longe da bomba. -tutorial.conduitUse2.text=E mais alguns... -tutorial.conduitUse3.text=E mais alguns... -tutorial.generator.text=Agora coloque um [orange]gerador a combustão[] no final do cano. -tutorial.generatorExplain.text=Este gerador irá produzir [yellow]energia[] do petróleo. -tutorial.lasers.text=Energia é distribuida usando [yellow]lasers[]. Gire e coloque um aqui. -tutorial.laserExplain.text=O gerador irá mover energia para o bloco do laser. Um feixe [yellow]opaco[] significa que a energia está sendo transmitida, e um feixe [yellow]transparente[] significa que não. -tutorial.laserMore.text=Você pode verificar quanta energia um bloco tem ao passar o mouse sobre eles e verificando a [yellow]barra amarela[] no topo. -tutorial.healingTurret.text=Este laser pode ser usado para energizar uma [lime]torre de reparo[]. Coloque uma aqui. -tutorial.healingTurretExplain.text=Enquanto tiver energia, esta torre irá [lime]reparar blocos próximos.[] Quando jogar, tenha certeza de construir uma dessas próximas do núcleo o mais rápido possível! -tutorial.smeltery.text=Muitos blocos precisam de [orange]aço[] para serem construídos, o que requer uma [orange]fundidora[] para ser feito. Coloque uma aqui. -tutorial.smelterySetup.text=Esta fundidora irá produzir [orange]aço[] quando receber carvão e ferro. -tutorial.end.text=E este é o fim do Tutorial! Boa Sorte! keybind.move_x.name=move_x keybind.move_y.name=move_y keybind.select.name=selecionar keybind.break.name=quebrar -keybind.shootInternal.name=atirar keybind.zoom_hold.name=segurar_zoom keybind.zoom.name=zoom keybind.menu.name=menu @@ -213,259 +137,379 @@ keybind.pause.name=pausar keybind.dash.name=Correr keybind.rotate_alt.name=girar_alt* keybind.rotate.name=girar -keybind.weapon_1.name=Arma 1 -keybind.weapon_2.name=Arma 2 -keybind.weapon_3.name=Arma 3 -keybind.weapon_4.name=Arma 4 -keybind.weapon_5.name=Arma 5 -keybind.weapon_6.name=Arma 6 mode.waves.name=hordas mode.sandbox.name=sandbox -#CAIXINHA DE AREIA mode.freebuild.name=construção \nlivre -weapon.blaster.name=Blaster -weapon.blaster.description=Atira um projétil lento e fraco. -weapon.triblaster.name=Blaster Triplo -weapon.triblaster.description=Atira 3 balas que se espalham. -weapon.multigun.name=Escopeta -weapon.multigun.description=Atira balas com baixa precisão e uma\n alta taxa de disparo. -weapon.flamer.name=Lança-Chamas -weapon.railgun.name=Rifle Sniper -weapon.flamer.description=É um lança-chamas. O que mais ele faria? -weapon.railgun.description=Atira um projétil de longo alcance. -weapon.mortar.name=Morteiro -weapon.mortar.description=Atira um projétil lento, porém devastador. item.stone.name=Pedra -item.iron.name=Ferro item.coal.name=Carvão -item.steel.name=Aço item.titanium.name=Titânio -item.dirium.name=Dírio item.thorium.name=Urânio liquid.water.name=Água -liquid.plasma.name=Plasma liquid.lava.name=Lava liquid.oil.name=Petróleo -block.air.name=Ar -block.blockpart.name=blockpart -#que? -block.deepwater.name=Água Profunda -block.water.name=Água -block.lava.name=Lava -block.oil.name=Petróleo -block.stone.name=Pedra -block.blackstone.name=Pedra Escura -block.iron.name=Ferro -block.coal.name=Carvão -block.titanium.name=Titânio -block.thorium.name=Urânio -block.dirt.name=Terra -block.sand.name=Areia -block.ice.name=Gelo -block.snow.name=Neve -block.grass.name=Grama -block.sandblock.name=Bloco de Areia -block.snowblock.name=Bloco de Neve -block.stoneblock.name=Rocha -block.blackstoneblock.name=Rocha Escura -block.grassblock.name=Bloco de Grama -block.mossblock.name=Musgo -block.shrub.name=Arbusto -block.rock.name=Rocha -block.icerock.name=Rocha de Gelo -block.blackrock.name=Rocha Escura -block.dirtblock.name=Bloco de Terra -block.stonewall.name=Parede de Pedra -block.stonewall.fulldescription=Um bloco defensivo barato. Útil para proteger o núcleo e torres nas primeiras hordas. -block.ironwall.name=Parede de Ferro -block.ironwall.fulldescription=Um bloco defensivo básico. Fornece proteção contra inimigos. -block.steelwall.name=Parede de aço -block.steelwall.fulldescription=Um bloco defensivo padrão. Fornece proteção contra inimigos. -block.titaniumwall.name=Parede de Titânio -block.titaniumwall.fulldescription=Um bloco defensivo forte. Fornece proteção contra inimigos. -block.duriumwall.name=Parede de Dírio -block.duriumwall.fulldescription=Um bloco defensivo muito forte. Fornece proteção contra inimigos. -block.compositewall.name=Parede de Composto -block.compositewall.fulldescription= Um bloco defensivo extremamente forte. Fornece a melhor proteção contra inimigos. -block.steelwall-large.name=Parede Grande de Aço -block.steelwall-large.fulldescription=Um bloco defensivo padrão. Ocupa multiplas células. -block.titaniumwall-large.name=Parede Grande de Titânio -block.titaniumwall-large.fulldescription=Um bloco defensivo forte. Ocupa multiplas células. -block.duriumwall-large.name=Parede Grande de Dírio -block.duriumwall-large.fulldescription=Um bloco defensivo muito forte. Ocupa multiplas células. -block.titaniumshieldwall.name=Parede com Escudo -block.titaniumshieldwall.fulldescription=Um bloco defensivo forte, com um escudo de energia imbutido. Usa energia passivamente e para absorver projéteis inimigos. É recomendado usar distribuidores de energia para abastecer este bloco. -#A strong defensive block, with an extra built-in shield. Requires power. Uses energy to absorb enemy bullets. It is recommended to use power boosters to provide energy to this block. -block.repairturret.name=Torre de Reparo -block.repairturret.fulldescription=Lentamente repara blocos danificados dentro do seu alcance. Consome um pouco de energia. -#Repairs nearby damaged blocks in range at a slow rate. Uses small amounts of power. -block.repairturret.description=[powerinfo]Consome Energia.[white]\nRepara blocos próximos. -block.megarepairturret.name=Torre de Reparo II -block.megarepairturret.fulldescription=Repara blocos danificados dentro do seu alcance. Consome um pouco de energia. -block.megarepairturret.description=[powerinfo]Consome Energia.[white]\nRepara blocos próximos. -block.shieldgenerator.name=Gerador de Escudo -block.shieldgenerator.fulldescription= Um bloco defensivo avançado. Protege todos os blocos em um raio. Lentamente usa energia quando parado, mas rapidamente drena em contato com projéteis. -#An advanced defensive block. Shields all the blocks in a radius from attack. Uses power at a slow rate when idle, but drains energy quickly on bullet contact. block.door.name=Porta -block.door.fulldescription=Um bloco que pode ser aberto e fechado ao tocar nele. -block.door.description=Abre e Fecha.\n[interact]Toque para alternar o estado. block.door-large.name=Porta Grande -block.door-large.fulldescription=Um bloco que pode ser aberto e fechado ao tocar nele. -block.door-large.description=Abre e Fecha.\n[interact]Toque para alternar o estado. block.conduit.name=Cano -block.conduit.fulldescription=Bloco de transporte de líquido básico. Funciona como uma esteira, mas com líquidos. Pode ser usado como uma ponte para inimigos e jogadores. -#Basic liquid transport block. Works like a conveyor, but with liquids. Best used with pumps or other conduits. Can be used as a bridge over liquids for enemies and players. block.pulseconduit.name=Cano de impulso -block.pulseconduit.fulldescription=Bloco de transporte de líquido avançado. Transporta líquidos mais rapidamente e armazena mais que canos normais. -#Advanced liquid transport block. Transports liquids faster and stores more than standard conduits. block.liquidrouter.name=Roteador de líquido -block.liquidrouter.fulldescription=Aceita líquido de uma direção e o redireciona para as outras 3 direções. Útil para dividir o líquido entre vários canos. -#Works similarly to a router. Accepts liquid input from one side and outputs it to the other sides. Useful for splitting liquid from a single conduit into multiple other conduits. -block.liquidrouter.description=Divide líquidos em 3 direções. block.conveyor.name=Esteira -block.conveyor.fulldescription=Bloco de transporte básico. Movimenta itens para frente e automaticamente os deposita em torres ou blocos de fabricação. Pode ser girado. Pode ser usado como uma ponte para inimigos e jogadores. -#Basic item transport block. Moves items forward and automatically deposits them into turrets or crafters. Rotatable. Can be used as a bridge over liquids for enemies and players. -block.steelconveyor.name=Esteira de aço -block.steelconveyor.fulldescription=Bloco de transporte avançado. Movimenta itens mais rapidamente que esteiras normais. -#Advanced item transport block. Moves items faster than standard conveyors. -block.poweredconveyor.name=Esteira de Impulso -block.poweredconveyor.fulldescription=O Bloco supremo de transporte. Movimenta itens mais rapidamente que esteiras de aço. -#The ultimate item transport block. Moves items faster than carbide conveyors. block.router.name=Roteador -block.router.fulldescription=Aceita itens de uma direção e os redireciona para as outras 3 direções. Pode guardar uma certa quantidade de itens. Útil para dividir materiais entre várias torres. block.router.description=Divide materiais em 3 direções. block.junction.name=Junção -block.junction.fulldescription=Funciona como uma ponte para 2 linhas de esteiras que se cruzam. Útil em situações onde duas esteiras carregam diferentes materiais para diferentes locais. -block.junction.description=Funciona como uma junção para as esteiras. -block.conveyortunnel.name=Túnel de esteira -block.conveyortunnel.fulldescription=Transporta itens por baixo de blocos. Para usar coloque um túnel apontado para o bloco que deseja passar por baixo, e outro apontado para o primeiro túnel. -block.conveyortunnel.description=Transporta intes por baixo de blocos. block.liquidjunction.name=Junção de líquido -block.liquidjunction.fulldescription=Funciona como uma ponte para 2 canos que se cruzam. Útil em situações onde 2 canos diferentes carregam diferentes líquidos para diferentes locais. -block.liquiditemjunction.name=liquid-item junction -block.liquiditemjunction.fulldescription=Acts as a bridge for crossing conduits and conveyors. -block.liquiditemjunction.description=Serves as a junction for items and liquids. -block.powerbooster.name=Distribuidor de energia -block.powerbooster.fulldescription=Distribui energia para todos os blocos dentro de seu raio. -block.powerbooster.description=Distribui energia em um raio. -block.powerlaser.name=Laser -#Laser de energia? -block.powerlaser.fulldescription=Cria um laser que transmite energia para o bloco à sua frente. Melhor usado com geradores ou outros lasers. Não gera energia. -block.powerlaser.description=Transmite energia. -block.powerlaserrouter.name=laser duplo -block.powerlaserrouter.fulldescription=Divide a entrada de energia em 3 lasers. Útil em situações onde é necessário conectar muitos blocos a partir de um gerador. -block.powerlaserrouter.description=Divide a entrada de energia em 3 lasers. -block.powerlasercorner.name=laser triplo -#*Essa nomeação ficou escrota -block.powerlasercorner.fulldescription=Laser que distribui energia para duas direções ao mesmo tempo. Útil em situações onde é necessário conectar muitos blocos a partir de um gerador. -block.powerlasercorner.description=Divide a entrada de energia em 2 lasers. -block.teleporter.name=Teleportador -block.teleporter.fulldescription=Bloco avançado de transporte de itens. Teleportadores transferem itens para outros teleportadores da mesma cor. Não faz nada se não houverem outros da mesma cor. Se houverem múltiplos da mesma cor, um aleatório será selecionado. Toque nas flechas para mudar de cor. -block.teleporter.description=[interact]Tap block to config[] block.sorter.name=Ordenador -block.sorter.fulldescription=Separa itens pelo tipo de material. O material a ser aceito é indicado pela cor do bloco. Todos os itens que correspondem ao material a ser separado são direcionados para frente, todo o resto é direcionado para os lados. block.sorter.description=[interact]Aperte no bloco para configurar[] -block.core.name=núcleo -block.pump.name=bomba -block.pump.fulldescription=Bombeia líquidos de um bloco, geralmente água, lava ou petróleo. Os líquidos são bombeados para canos próximos. -block.pump.description=Bombeia líquidos para canos próximos. -block.fluxpump.name=Bomba de fluxo -block.fluxpump.fulldescription=Uma versão avançada da bomba comum. Guarda mais líquido e bombeia mais rápido. -block.fluxpump.description=Bombeia líquidos para canos próximos. block.smelter.name=Fornalha -block.smelter.fulldescription=O bloco de produção essencial. Quando recebe 1 carvão e\n1 ferro produz 1 aço -block.smelter.description=Converte carvão + ferro em aço. -block.crucible.name=Usina de fundição -block.crucible.fulldescription=Um bloco de produção avançado. Quando recebe 1 titânio e 1 aço produz 1 dírio. -block.crucible.description=Converte aço + titânio em dírio. -block.coalpurifier.name=Extrator de carvão -block.coalpurifier.fulldescription=Um bloco extrator básico. Produz carvão quando fornecido com grandes quantidades de água e pedra. -block.coalpurifier.description=Converte pedra + água em carvão. -block.titaniumpurifier.name=Extrator de titânio -block.titaniumpurifier.fulldescription=Um bloco extrator padrão. Produz titânio quando fornecido com grandes quantidas de água e ferro. -block.titaniumpurifier.description=Converte água e ferro em titânio. -block.oilrefinery.name=Refinaria de Petróleo -block.oilrefinery.fulldescription=Refina grande quantidades de petróleo para produzir carvão. Útil para abastecer torres que utilizam carvão quando jazidas de carvão são escassas. -block.oilrefinery.description=Converte petróleo em carvão. -block.stoneformer.name=Formador de Pedra -block.stoneformer.fulldescription=Solidifica lava para formar pedra. Útil para produzir grandes quantidades de pedra para extratores de carvão. -block.stoneformer.description=Converte lava em pedra. -block.lavasmelter.name=Fornalha à Lava -block.lavasmelter.fulldescription=Usa lava para converter ferro em aço. Uma alternativa para a fundidora. Útil em situações onde não há carvão por perto. -block.lavasmelter.description=Converte ferro + lava em aço. -block.stonedrill.name=Broca de pedra -block.stonedrill.fulldescription=A broca essencial. Quando colocada em uma jazida de pedra gera pedra indefinidamente. -block.stonedrill.description=Gera 1 pedra a cada 4 segundos. -#Mines 1 stone every 4 seconds. -block.irondrill.name=Broca de Ferro -block.irondrill.fulldescription=Uma broca básica. Quando colocada sobre uma jazida de ferro, lentamente gera ferro. -#A basic drill. When placed on tungsten ore tiles, outputs tungsten at a slow pace indefinitely. -block.irondrill.description=Gera 1 ferro a cada 5 segundos. -block.coaldrill.name=Broca de Carvão -block.coaldrill.fulldescription=Uma broca básica. Quando colocada sobre uma jazida de carvão, lentamente gera carvão. -block.coaldrill.description=Gera 1 carvão a cada 5 segundos. -block.thoriumdrill.name=Broca de Urânio -block.thoriumdrill.fulldescription=Uma broca avançada. Quando colocada sobre uma jazida de urânio, lentamente gera urânio. -block.thoriumdrill.description=Gera 1 Urânio a cada 7 segundos. -block.titaniumdrill.name=Broca de Titânio -block.titaniumdrill.fulldescription=Uma broca avançada. Quando colocada sobre uma jazida de titânio, lentamente gera titânio. -block.titaniumdrill.description=Gera 1 Titânio a cada 5 segundos. -block.omnidrill.name=Omnibroca -block.omnidrill.fulldescription=A broca suprema. Rapidamente extrai qualquer minério em que é colocada. -#The ultimate drill. Will mine any ore it is placed on at a rapid pace. -block.omnidrill.description=Gera 1 de qualquer recurso a cada 3 segundos. -block.coalgenerator.name=Gerador à Carvão -#Crase ou não? -block.coalgenerator.fulldescription=O gerador essencial. Gera energia a partir de carvão. Distribui energia em forma de laser para os 4 lados. -block.coalgenerator.description=Gera energia a partir de carvão. -block.thermalgenerator.name=Gerador Térmico -block.thermalgenerator.fulldescription=Gera energia a partir de lava. Distribui energia em forma de laser para os 4 lados. -block.thermalgenerator.description=Gera energia a partir de lava. -block.combustiongenerator.name=Gerador à Combustão -block.combustiongenerator.fulldescription=Gera energia a partir de petróleo. Distribui energia em forma de laser para os 4 lados. -block.combustiongenerator.description=Gera energia a partir de petróleo. -block.rtgenerator.name=Gerador RTG -block.rtgenerator.fulldescription=Gera pouca quantidade de energia a partir do decaimento radioativo do urânio. Distribui energia em forma de laser para os 4 lados. -block.rtgenerator.description=Gera energia a partir de Urânio. -block.nuclearreactor.name=Reator Nuclear -block.nuclearreactor.fulldescription=Uma versão avançada do gerador RTG. Gera energia a partir de Urânio. Requer constante resfriamento à água. Altamente volátil; explodirá violentamente se não for suprido com quantiddades suficientes de água. -block.turret.name=Torre Comum -block.turret.fulldescription=Uma torre básica e barata. Usa pedra como munição. Tem alcance um pouco maior que a torre dupla. -block.turret.description=[turretinfo]Munição: pedra -block.doubleturret.name=Torre Dupla -block.doubleturret.fulldescription=Uma versão um pouco mais poderosa do que a torre comum. Usa pedra como munição. Causa um dano maior, porém tem menor alcance. Atira dois projéteis. -block.doubleturret.description=[turretinfo]Munição: pedra -block.machineturret.name=Torre Automática -block.machineturret.fulldescription=Uma torre padrão completa. Usa ferro como munição. Tem alta taxa de disparo e dano decente. -block.machineturret.description=[turretinfo]Munição: ferro -block.shotgunturret.name=Torre Splitter -#Splitter turret -block.shotgunturret.fulldescription=Uma torre padrão. Usa ferro como munição. Atira 7 balas em forma de cone. Pouco alcance, porém maior dano do que a Torre Dupla. -block.shotgunturret.description=[turretinfo]Munição: ferro -block.flameturret.name=Torre lança-\nchamas -block.flameturret.fulldescription=Torre avançada de baixo alcance. Usa carvão. Pouco alcance mas alto dano. Boa para trechos estreitos. Recomenda-se usá-la atŕas de paredes. -block.flameturret.description=[turretinfo]Munição: carvão -block.sniperturret.name=Torre Sniper -#Torre Railgun? -block.sniperturret.fulldescription=Torre avançada de longo alcance. Usa aço como munição. Dano altíssimo, porém baixa taxa de disparo. Cara para usar, porém pode ser colocada longe das linhas inimigas dado seu alcance. -block.sniperturret.description=[turretinfo]Munição: aço -block.mortarturret.name=Torre Flak -block.mortarturret.fulldescription=Torre avançada de dano em área. Usa carvão. Taxa de disparo e balas lentas, mas alto dano em alvo único ou distribuído. -block.mortarturret.description=[turretinfo]Munição: carvão -block.laserturret.name=Torre laser -block.laserturret.fulldescription=Torre de alvo único avançada. Usa energia. Boa torre de alcance médio e uso geral. Alvo único apenas. Nunca erra. -block.laserturret.description=[turretinfo]Usa Energia -block.waveturret.name=Torre Tesla -block.waveturret.fulldescription=Torre de múltiplos alvos avançada. Usa Energia. Alcance médio. Nunca erra. Dano médio-baixo, porém pode acertar vários inimigos simultaneamente com raios conectados. -block.waveturret.description=[turretinfo]Usa Energia -block.plasmaturret.name=Torre de Plasma -block.plasmaturret.fulldescription=Versão altamente avançada da Torre lança-chamas. Usa carvão. Dano altíssimo e alcance médio-baixo. -block.plasmaturret.description=[turretinfo]Munição: carvão -block.chainturret.name=Canhão automático -block.chainturret.fulldescription=A torre de tiro rápido mais avançada. Usa Urânio como munição. Atira grandes projéteis rapidamente. Alcance médio. Ocupa várias células. Extremamente resistente. -block.chainturret.description=[turretinfo]Munição: Urânio -block.titancannon.name=Canhão Titã -block.titancannon.fulldescription=A torre de longo alcance mais avançada. Usa Urânio como munição. Atira várias balas de dano em área à uma taxa de disparo média. Alto alcance. Ocupa várias células. Extremamente resistente. -block.titancannon.description=[turretinfo]Munição: Urânio -block.playerspawn.name=playerspawn -block.enemyspawn.name=enemyspawn +text.credits=Credits +text.link.discord.description=the official Mindustry discord chatroom +text.link.github.description=Game source code +text.link.dev-builds.description=Unstable development builds +text.link.trello.description=Official trello board for planned features +text.link.itch.io.description=itch.io page with PC downloads and web version +text.link.google-play.description=Google Play store listing +text.link.wiki.description=official Mindustry wiki +text.linkfail=Failed to open link!\nThe URL has been copied to your cliboard. +text.editor.web=The web version does not support the editor!\nDownload the game to use it. +text.web.unsupported=The web version does not support this feature! Download the game to use it. +text.multiplayer.web=This version of the game does not support multiplayer!\nTo play multiplayer from your browser, use the "multiplayer web version" link at the itch.io page. +text.host.web=The web version does not support hosting games! Download the game to use this feature. +text.map.delete=Are you sure you want to delete the map "[orange]{0}[]"? +text.construction.title=Block Construction Guide +text.construction=You've just selected [accent]block construction mode[].\n\nTo begin placing, simply tap a valid location near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Shift the selection[] by holding and dragging any block in the selection.\n- [accent]Place blocks in a line[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel construction or selection[] by pressing the X at the bottom left. +text.deconstruction.title=Block Deconstruction Guide +text.deconstruction=You've just selected [accent]block deconstruction mode[].\n\nTo begin breaking, simply tap a block near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin de-constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Remove blocks in an area[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel deconstruction or selection[] by pressing the X at the bottom left. +text.showagain=Don't show again next session +text.unlocks=Unlocks +text.joingame=Join Game +text.addplayers=Add/Remove Players +text.newgame=New Game +text.maps=Maps +text.maps.none=[LIGHT_GRAY]No maps found! +text.about.button=About +text.name=Name: +text.unlocked=New Block Unlocked! +text.unlocked.plural=New Blocks Unlocked! +text.server.rollback=Rollback +text.server.rollback.numberfield=Rollback Amount: +text.blocks.editlogs=Edit Logs +text.block.editlogsnotfound=[red]There are no edit logs for this location. +text.public=Public +text.players={0} players online +text.server.player.host={0} (host) +text.players.single={0} player online +text.server.mismatch=Packet error: possible client/server version mismatch.\nMake sure you and the host have the\nlatest version of Mindustry! +text.server.closing=[accent]Closing server... +text.server.kicked.kick=You have been kicked from the server! +text.server.kicked.fastShoot=You are shooting too quickly. +text.server.kicked.invalidPassword=Invalid password! +text.server.kicked.clientOutdated=Outdated client! Update your game! +text.server.kicked.serverOutdated=Outdated server! Ask the host to update! +text.server.kicked.banned=You are banned on this server. +text.server.kicked.recentKick=You have been kicked recently.\nWait before connecting again. +text.server.kicked.nameInUse=There is someone with that name\nalready on this server. +text.server.kicked.nameEmpty=Your name must contain at least one character or number. +text.server.kicked.idInUse=You are already on this server! Connecting with two accounts is not permitted. +text.server.kicked.customClient=This server does not support custom builds. Download an official version. +text.server.connected={0} has joined. +text.server.disconnected={0} has disconnected. +text.nohost=Can't host server on a custom map! +text.host.info=The [accent]host[] button hosts a server on ports [scarlet]6567[] and [scarlet]6568.[]\nAnybody on the same [LIGHT_GRAY]wifi or local network[] should be able to see your server in their server list.\n\nIf you want people to be able to connect from anywhere by IP, [accent]port forwarding[] is required.\n\n[LIGHT_GRAY]Note: If someone is experiencing trouble connecting to your LAN game, make sure you have allowed Mindustry access to your local network in your firewall settings. +text.join.info=Here, you can enter a [accent]server IP[] to connect to, or discover [accent]local network[] servers to connect to.\nBoth LAN and WAN multiplayer is supported.\n\n[LIGHT_GRAY]Note: There is no automatic global server list; if you want to connect to someone by IP, you would need to ask the host for their IP. +text.hostserver=Host Server +text.host=Host +text.hosting=[accent]Opening server... +text.hosts.refresh=Refresh +text.hosts.discovering=Discovering LAN games +text.server.refreshing=Refreshing server +text.hosts.none=[lightgray]No LAN games found! +text.host.invalid=[scarlet]Can't connect to host. +text.server.friendlyfire=Friendly Fire +text.trace=Trace Player +text.trace.playername=Player name: [accent]{0} +text.trace.ip=IP: [accent]{0} +text.trace.id=Unique ID: [accent]{0} +text.trace.android=Android Client: [accent]{0} +text.trace.modclient=Custom Client: [accent]{0} +text.trace.totalblocksbroken=Total blocks broken: [accent]{0} +text.trace.structureblocksbroken=Structure blocks broken: [accent]{0} +text.trace.lastblockbroken=Last block broken: [accent]{0} +text.trace.totalblocksplaced=Total blocks placed: [accent]{0} +text.trace.lastblockplaced=Last block placed: [accent]{0} +text.invalidid=Invalid client ID! Submit a bug report. +text.server.bans=Bans +text.server.bans.none=No banned players found! +text.server.admins=Admins +text.server.admins.none=No admins found! +text.server.add=Add Server +text.server.delete=Are you sure you want to delete this server? +text.server.hostname=Host: {0} +text.server.edit=Edit Server +text.server.outdated=[crimson]Outdated Server![] +text.server.outdated.client=[crimson]Outdated Client![] +text.server.version=[lightgray]Version: {0} +text.server.custombuild=[yellow]Custom Build +text.confirmban=Are you sure you want to ban this player? +text.confirmunban=Are you sure you want to unban this player? +text.confirmadmin=Are you sure you want to make this player an admin? +text.confirmunadmin=Are you sure you want to remove admin status from this player? +text.joingame.byip=Join by IP... +text.joingame.title=Join Game +text.joingame.ip=IP: +text.disconnect=Disconnected. +text.disconnect.data=Failed to load world data! +text.connecting=[accent]Connecting... +text.connecting.data=[accent]Loading world data... +text.connectfail=[crimson]Failed to connect to server: [orange]{0} +text.server.port=Port: +text.server.addressinuse=Address already in use! +text.server.invalidport=Invalid port number! +text.server.error=[crimson]Error hosting server: [orange]{0} +text.tutorial.back=< Prev +text.tutorial.next=Next > +text.save.new=New Save +text.save.none=No saves found! +text.save.delete.confirm=Are you sure you want to delete this save? +text.save.delete=Delete +text.save.export=Export Save +text.save.import.invalid=[orange]This save is invalid! +text.save.import.fail=[crimson]Failed to import save: [orange]{0} +text.save.export.fail=[crimson]Failed to export save: [orange]{0} +text.save.import=Import Save +text.save.newslot=Save name: +text.save.rename=Rename +text.save.rename.text=New name: +text.on=On +text.off=Off +text.save.autosave=Autosave: {0} +text.save.map=Map: {0} +text.save.difficulty=Difficulty: {0} +text.copylink=Copy Link +text.changelog.title=Changelog +text.changelog.loading=Getting changelog... +text.changelog.error.android=[orange]Note that the changelog sometimes does not work on Android 4.4 and below!\nThis is due to an internal Android bug. +text.changelog.error.ios=[orange]The changelog is currently not supported in iOS. +text.changelog.error=[scarlet]Error getting changelog!\nCheck your internet connection. +text.changelog.current=[yellow][[Current version] +text.changelog.latest=[orange][[Latest version] +text.saving=[accent]Saving... +text.unknown=Unknown +text.custom=Custom +text.builtin=Built-In +text.map.delete.confirm=Are you sure you want to delete this map? This action cannot be undone! +text.map.random=[accent]Random Map +text.map.nospawn=This map does not have any cores for the player to spawn in! Add a [ROYAL]blue[] core to this map in the editor. +text.editor.slope=\\ +text.editor.openin=Open In Editor +text.editor.oregen=Ore Generation +text.editor.oregen.info=Ore Generation: +text.editor.mapinfo=Map Info +text.editor.author=Author: +text.editor.description=Description: +text.editor.name=Name: +text.editor.teams=Teams +text.editor.elevation=Elevation +text.editor.saved=Saved! +text.editor.save.noname=Your map does not have a name! Set one in the 'map info' menu. +text.editor.save.overwrite=Your map overwrites a built-in map! Pick a different name in the 'map info' menu. +text.editor.import.exists=[scarlet]Unable to import:[] a built-in map named '{0}' already exists! +text.editor.import=Import... +text.editor.importmap=Import Map +text.editor.importmap.description=Import an already existing map +text.editor.importfile=Import File +text.editor.importfile.description=Import an external map file +text.editor.importimage=Import Terrain Image +text.editor.importimage.description=Import an external map image file +text.editor.export=Export... +text.editor.exportfile=Export File +text.editor.exportfile.description=Export a map file +text.editor.exportimage=Export Terrain Image +text.editor.exportimage.description=Export a map image file +text.editor.overwrite.confirm=[scarlet]Warning![] A map with this name already exists. Are you sure you want to overwrite it? +text.fps=FPS: {0} +text.tps=TPS: {0} +text.ping=Ping: {0}ms +text.language.restart=Please restart your game for the language settings to take effect. +text.settings.language=Language +text.settings.rebind=Rebind +text.yes=Yes +text.no=No +text.info.title=[accent]Info +text.blocks.targetsair=Targets Air +text.blocks.itemspeed=Units Moved +text.blocks.shootrange=Range +text.blocks.poweruse=Power Use +text.blocks.inputitemcapacity=Input Item Capacity +text.blocks.outputitemcapacity=Input Item Capacity +text.blocks.maxpowergeneration=Max Power Generation +text.blocks.powertransferspeed=Power Transfer +text.blocks.craftspeed=Production Speed +text.blocks.inputliquidaux=Aux Liquid +text.blocks.inputitems=Input Items +text.blocks.outputitem=Output Item +text.blocks.drilltier=Drillables +text.blocks.drillspeed=Base Drill Speed +text.blocks.liquidoutput=Liquid Output +text.blocks.liquiduse=Liquid Use +text.blocks.coolant=Coolant +text.blocks.coolantuse=Coolant Use +text.blocks.inputliquidfuel=Fuel Liquid +text.blocks.liquidfueluse=Liquid Fuel Use +text.blocks.reload=Reload +text.blocks.inputfuel=Fuel +text.blocks.fuelburntime=Fuel Burn Time +text.blocks.inputcapacity=Input capacity +text.blocks.outputcapacity=Output capacity +text.unit.blocks=blocks +text.unit.powersecond=power units/second +text.unit.liquidsecond=liquid units/second +text.unit.itemssecond=items/second +text.unit.pixelssecond=pixels/second +text.unit.liquidunits=liquid units +text.unit.powerunits=power units +text.unit.degrees=degrees +text.unit.seconds=seconds +text.unit.none= +text.unit.items=items +text.category.general=General +text.category.power=Power +text.category.liquids=Liquids +text.category.items=Items +text.category.crafting=Crafting +text.category.shooting=Shooting +setting.difficulty.insane=insane +setting.difficulty.purge=purge +setting.saveinterval.name=Autosave Interval +setting.seconds={0} Seconds +setting.fullscreen.name=Fullscreen +setting.multithread.name=Multithreading +setting.previewopacity.name=Placing Preview Opacity +setting.minimap.name=Show Minimap +text.keybind.title=Rebind Keys +keybind.shoot.name=shoot +keybind.block_info.name=block_info +keybind.chat.name=chat +keybind.player_list.name=player_list +keybind.console.name=console +mode.text.help.title=Description of modes +mode.waves.description=the normal mode. limited resources and automatic incoming waves. +mode.sandbox.description=infinite resources and no timer for waves. +mode.freebuild.description=limited resources and no timer for waves. +content.item.name=Items +content.liquid.name=Liquids +content.unit-type.name=Units +content.recipe.name=Blocks +item.stone.description=A common raw material. Used for separating and refining into other materials, or melting into lava. +item.tungsten.name=Tungsten +item.tungsten.description=A common, but very useful structure material. Used in drills and heat-resistant blocks such as generators and smelteries. +item.lead.name=Lead +item.lead.description=A basic starter material. Used extensively in electronics and liquid transportation blocks. +item.coal.description=A common and readily available fuel. +item.carbide.name=Carbide +item.carbide.description=A tough alloy made with tungsten and carbon. Used in advanced transportation blocks and high-tier drills. +item.titanium.description=A rare super-light metal used extensively in liquid transportation, drills and aircraft. +item.thorium.description=A dense, radioactive metal used as structural support and nuclear fuel. +item.silicon.name=Silicon +item.silcion.description=An extremely useful semiconductor, with applications in solar panels and many complex electronics. +item.plastanium.name=Plastanium +item.plastanium.description=A light, ductile material used in advanced aircraft and fragmentation ammunition. +item.phase-matter.name=Phase Matter +item.surge-alloy.name=Surge Alloy +item.biomatter.name=Biomatter +item.biomatter.description=A clump of organic mush; used for conversion into oil or as a basic fuel. +item.sand.name=Sand +item.sand.description=A common material that is used extensively in smelting, both in alloying and as a flux. +item.blast-compound.name=Blast Compound +item.blast-compound.description=A volatile compound used in bombs and explosives. While it can burned as fuel, this is not advised. +item.pyratite.name=Pyratite +item.pyratite.description=An extremely flammable substance used in incendiary weapons. +liquid.cryofluid.name=Cryofluid +text.item.explosiveness=[LIGHT_GRAY]Explosiveness: {0} +text.item.flammability=[LIGHT_GRAY]Flammability: {0} +text.item.radioactivity=[LIGHT_GRAY]Radioactivity: {0} +text.item.fluxiness=[LIGHT_GRAY]Flux Power: {0} +text.item.hardness=[LIGHT_GRAY]Hardness: {0} +text.liquid.heatcapacity=[LIGHT_GRAY]Heat Capacity: {0} +text.liquid.viscosity=[LIGHT_GRAY]Viscosity: {0} +text.liquid.temperature=[LIGHT_GRAY]Temperature: {0} +block.tungsten-wall.name=Tungsten Wall +block.tungsten-wall-large.name=Large Tungsten Wall +block.carbide-wall.name=Carbide Wall +block.carbide-wall-large.name=Large Carbide Wall +block.thorium-wall.name=Thorium Wall +block.thorium-wall-large.name=Large Thorium Wall +block.duo.name=Duo +block.scorch.name=Scorch +block.hail.name=Hail +block.lancer.name=Lancer +block.titanium-conveyor.name=Titanium Conveyor +block.splitter.name=Splitter +block.splitter.description=Outputs items into two opposite directions immediately after they are recieved. +block.distributor.name=Distributor +block.distributor.description=A splitter that can split items into 8 directions. +block.overflow-gate.name=Overflow Gate +block.overflow-gate.description=A combination splitter and router that only outputs to the left and right if the front path is blocked. +block.bridgeconveyor.name=Bridge Conveyor +block.bridgeconveyor.description=A conveyor that can go over other blocks, for up to two total blocks. +block.arc-smelter.name=Arc Smelter +block.silicon-smelter.name=Silicon Smelter +block.phase-weaver.name=Phase Weaver +block.pulverizer.name=Pulverizer +block.cryofluidmixer.name=Cryofluid Mixer +block.melter.name=Melter +block.incinerator.name=Incinerator +block.biomattercompressor.name=Biomatter Compressor +block.separator.name=Separator +block.centrifuge.name=Centrifuge +block.power-node.name=Power Node +block.power-node-large.name=Large Power Node +block.battery.name=Battery +block.battery-large.name=Large Battery +block.combustion-generator.name=Combustion Generator +block.turbine-generator.name=Turbine Generator +block.tungsten-drill.name=Tungsten Drill +block.carbide-drill.name=Carbide Drill +block.laser-drill.name=Laser Drill +block.water-extractor.name=Water Extractor +block.cultivator.name=Cultivator +block.dart-ship-factory.name=Dart Ship Factory +block.delta-mech-factory.name=Delta Mech Factory +block.dronefactory.name=Drone Factory +block.repairpoint.name=Repair Point +block.resupplypoint.name=Resupply Point +block.liquidtank.name=Liquid Tank +block.bridgeconduit.name=Bridge Conduit +block.mechanical-pump.name=Mechanical Pump +block.itemsource.name=Item Source +block.itemvoid.name=Item Void +block.liquidsource.name=Liquid Source +block.powervoid.name=Power Void +block.powerinfinite.name=Power Infinite +block.unloader.name=Unloader +block.sortedunloader.name=Sorted Unloader +block.vault.name=Vault +block.wave.name=Wave +block.swarmer.name=Swarmer +block.salvo.name=Salvo +block.ripple.name=Ripple +block.phase-conveyor.name=Phase Conveyor +block.bridge-conveyor.name=Bridge Conveyor +block.plastanium-compressor.name=Plastanium Compressor +block.pyratite-mixer.name=Pyratite Mixer +block.blast-mixer.name=Blast Mixer +block.solidifer.name=Solidifer +block.solar-panel.name=Solar Panel +block.solar-panel-large.name=Large Solar Panel +block.oil-extractor.name=Oil Extractor +block.javelin-ship-factory.name=Javelin Ship factory +block.drone-factory.name=Drone Factory +block.fabricator-factory.name=Fabricator Factory +block.repair-point.name=Repair Point +block.resupply-point.name=Resupply Point +block.pulse-conduit.name=Pulse Conduit +block.phase-conduit.name=Phase Conduit +block.liquid-router.name=Liquid Router +block.liquid-tank.name=Liquid Tank +block.liquid-junction.name=Liquid Junction +block.bridge-conduit.name=Bridge Conduit +block.rotary-pump.name=Rotary Pump +block.nuclear-reactor.name=Nuclear Reactor diff --git a/core/assets/bundles/bundle_ru.properties b/core/assets/bundles/bundle_ru.properties index 8a8102b84e..2e816db671 100644 --- a/core/assets/bundles/bundle_ru.properties +++ b/core/assets/bundles/bundle_ru.properties @@ -1,555 +1,515 @@ -text.about = Создатель [ROYAL] Anuken. [] \nИзначально игра была создана для участия в [orange] GDL [] MM Jam. \n\nАвторы: \n- Звуковые эффекты, сделаны с помощью [YELLOW] bfxr [] \n- Музыка, создана [GREEN] RoccoW [] / найденная на [lime] FreeMusicArchive.org [] \n\nОсобая благодарность: \n- [coral] MitchellFJN []: в тестировании и отзывах \n- [sky] Luxray5474 []: работа в вики, помощь в разработке \n- Все бета-тестеры на itch.io и Google Play\n\nИгра переведена полностью на русский язык [GREEN]krocotavus[] и [GREEN]lexa1549. Дополнил перевод [GREEN]Prosta4ok_ua[]\n -text.credits = Авторы -text.discord = Присоединяйтесь к нашему Discord чату! -text.changes=[SCARLET] Внимание!\n[]Изменена некоторая важная игровая механика.\n\n-[accent]Телепортеры[]теперь используют силу.\n-[accent]Печи[]и[accent]тигли[]теперь имеют максимальная емкость элемента.\n-[accent]Тигли[]теперь требует угля в качестве топлива. -text.link.discord.description = официальный discord-сервер Mindustry -text.link.github.description = Исходный код игры -text.link.dev-builds.description = Нестабильные разработки -text.link.trello.description = Официальная доска trello для запланированных функций -text.link.itch.io.description = itch.io страница с загрузкой ПК и веб-версией -text.link.google-play.description = Google Play список магазинов -text.link.wiki.description = официальная вики Mindustry -text.linkfail = Не удалось открыть ссылку!\nURL-адрес был скопирован в буфер обмена. -text.editor.web = Веб-версия не поддерживает редактор!\nЗагрузите игру, чтобы использовать ее. -text.multiplayer.web = Эта версия игры не поддерживает многопользовательскую игру! \n Чтобы играть в мультиплеер из своего браузера, используйте ссылку «Многопользовательская веб-версия» на странице itch.io. -text.gameover = Ядро было уничтожено. -text.highscore = [YELLOW]Новый рекорд! -text.lasted = Вы продержались до волны -text.level.highscore = Рекорд: [accent]{0} -text.level.delete.title = Подтвердите удаление -text.level.delete = Вы уверены,что хотите удалить эту карту \"[orange]\"{0}? -text.level.select = Выбор уровня -text.level.mode = Режим игры: -text.savegame = Сохранить игру -text.loadgame = Загрузить игру -text.joingame = Присоединиться -text.newgame= Новая игра -text.quit = Выход -text.about.button = Об игре -text.name = Название: -text.public = Общие -text.players = Игроков на сервере: {0} +text.about=Создатель [ROYAL] Anuken. [] \nИзначально игра была создана для участия в [orange] GDL [] MM Jam. \n\nАвторы: \n- Звуковые эффекты, сделаны с помощью [YELLOW] bfxr [] \n- Музыка, создана [GREEN] RoccoW [] / найденная на [lime] FreeMusicArchive.org [] \n\nОсобая благодарность: \n- [coral] MitchellFJN []: в тестировании и отзывах \n- [sky] Luxray5474 []: работа в вики, помощь в разработке \n- Все бета-тестеры на itch.io и Google Play\n\nИгра переведена полностью на русский язык [GREEN]krocotavus[] и [GREEN]lexa1549. Дополнил перевод [GREEN]Prosta4ok_ua[]\n +text.credits=Авторы +text.discord=Присоединяйтесь к нашему Discord чату! +text.link.discord.description=официальный discord-сервер Mindustry +text.link.github.description=Исходный код игры +text.link.dev-builds.description=Нестабильные разработки +text.link.trello.description=Официальная доска trello для запланированных функций +text.link.itch.io.description=itch.io страница с загрузкой ПК и веб-версией +text.link.google-play.description=Google Play список магазинов +text.link.wiki.description=официальная вики Mindustry +text.linkfail=Не удалось открыть ссылку!\nURL-адрес был скопирован в буфер обмена. +text.editor.web=Веб-версия не поддерживает редактор!\nЗагрузите игру, чтобы использовать ее. +text.multiplayer.web=Эта версия игры не поддерживает многопользовательскую игру! \n Чтобы играть в мультиплеер из своего браузера, используйте ссылку «Многопользовательская веб-версия» на странице itch.io. +text.gameover=Ядро было уничтожено. +text.highscore=[YELLOW]Новый рекорд! +text.lasted=Вы продержались до волны +text.level.highscore=Рекорд: [accent]{0} +text.level.delete.title=Подтвердите удаление +text.level.select=Выбор уровня +text.level.mode=Режим игры: +text.savegame=Сохранить игру +text.loadgame=Загрузить игру +text.joingame=Присоединиться +text.newgame=Новая игра +text.quit=Выход +text.about.button=Об игре +text.name=Название: +text.public=Общие +text.players=Игроков на сервере: {0} text.server.player.host={0} (хост) -text.players.single = {0} игрок на сервере -text.server.mismatch = Ошибка пакета: возможное несоответствие версии клиента / сервера. Убедитесь, что у вас и у создателя сервера установлена ​​последняя версия Mindustry! -text.server.closing = [accent]Закрытие сервера... -text.server.kicked.kick = Вас выгнали с сервера! -text.server.kicked.fastShoot = Вы стреляете слишком быстро. -text.server.kicked.invalidPassword = Неверный пароль. -text.server.kicked.clientOutdated = Устаревший клиент! Обновите игру! -text.server.kicked.serverOutdated = Устаревший сервер! Попросите хост обновить! -text.server.kicked.banned = Вы заблокированы на этом сервере. +text.players.single={0} игрок на сервере +text.server.mismatch=Ошибка пакета: возможное несоответствие версии клиента / сервера. Убедитесь, что у вас и у создателя сервера установлена ​​последняя версия Mindustry! +text.server.closing=[accent]Закрытие сервера... +text.server.kicked.kick=Вас выгнали с сервера! +text.server.kicked.fastShoot=Вы стреляете слишком быстро. +text.server.kicked.invalidPassword=Неверный пароль. +text.server.kicked.clientOutdated=Устаревший клиент! Обновите игру! +text.server.kicked.serverOutdated=Устаревший сервер! Попросите хост обновить! +text.server.kicked.banned=Вы заблокированы на этом сервере. text.server.kicked.recentKick=Вы недавно были кикнуты.\n Подождите немного перед следующим подключением -text.server.connected = {0} присоединился -text.server.disconnected = {0} отключился. -text.nohost = Не удается запустить сервер на пользовательской карте! +text.server.connected={0} присоединился +text.server.disconnected={0} отключился. +text.nohost=Не удается запустить сервер на пользовательской карте! text.host.info=The [accent]host[] button hosts a server on ports [scarlet]6567[] and [scarlet]6568.[]\nAnybody on the same [LIGHT_GRAY]wifi or local network[] should be able to see your server in their server list.\n\nIf you want people to be able to connect from anywhere by IP, [accent]port forwarding[] is required.\n\n[LIGHT_GRAY]Note: If someone is experiencing trouble connecting to your LAN game, make sure you have allowed Mindustry access to your local network in your firewall settings. text.join.info=Here, you can enter a [accent]server IP[] to connect to, or discover [accent]local network[] servers to connect to.\nBoth LAN and WAN multiplayer is supported.\n\n[LIGHT_GRAY]Note: There is no automatic global server list; if you want to connect to someone by IP, you would need to ask the host for their IP. -text.hostserver = Запустить сервер -text.host = Сервер -text.hosting = [accent]Открытие сервера... -text.hosts.refresh = Обновить -text.hosts.discovering = Поиск локальных игр -text.server.refreshing = Обновление сервера -text.hosts.none = [lightgray]Локальных игр не обнаружено! -text.host.invalid = [scarlet] Не удается подключиться к хосту. -text.server.friendlyfire = Дружественный огонь -text.trace = Слежка за игроком -text.trace.playername = Имя игрока: [accent]{0} -text.trace.ip = IP: [accent]{0} -text.trace.id = Уникальный идентификатор: [accent]{0} -text.trace.android = Клиент Android: [accent]{0} -text.trace.modclient = Пользовательский клиент: [accent]{0} -text.trace.totalblocksbroken = Всего разбитых блоков: [accent]{0} - -e.structureblocksbroken = Структурных блоков сломанных: [accent]{0} -text.trace.lastblockbroken = Последний сломанный блок:[accent]{0} -text.trace.totalblocksplaced = Всего размещено блоков: [accent]{0} -text.trace.lastblockplaced = Последний размещенный блок: [accent]{0} -text.invalidid = Недопустимый идентификатор клиента! Отправьте отчет об ошибке. -text.server.bans = Блокировки -text.server.bans.none = Никаких заблокированных игроков не найдено! -text.server.admins = Администраторы -text.server.admins.none = Администраторов не найдено! -text.server.add = Добавить сервер -text.server.delete = Вы действительно хотите удалить этот сервер? -text.server.hostname = Хост: {0} -text.server.edit = Редактировать сервер -text.server.outdated = [crimson]Устаревший сервер![] -text.server.outdated.client = [crimson]Устаревший клиент![] -text.server.version = [lightgray]Версия: {0} +text.hostserver=Запустить сервер +text.host=Сервер +text.hosting=[accent]Открытие сервера... +text.hosts.refresh=Обновить +text.hosts.discovering=Поиск локальных игр +text.server.refreshing=Обновление сервера +text.hosts.none=[lightgray]Локальных игр не обнаружено! +text.host.invalid=[scarlet] Не удается подключиться к хосту. +text.server.friendlyfire=Дружественный огонь +text.trace=Слежка за игроком +text.trace.playername=Имя игрока: [accent]{0} +text.trace.ip=IP: [accent]{0} +text.trace.id=Уникальный идентификатор: [accent]{0} +text.trace.android=Клиент Android: [accent]{0} +text.trace.modclient=Пользовательский клиент: [accent]{0} +text.trace.totalblocksbroken=Всего разбитых блоков: [accent]{0} +text.trace.lastblockbroken=Последний сломанный блок:[accent]{0} +text.trace.totalblocksplaced=Всего размещено блоков: [accent]{0} +text.trace.lastblockplaced=Последний размещенный блок: [accent]{0} +text.invalidid=Недопустимый идентификатор клиента! Отправьте отчет об ошибке. +text.server.bans=Блокировки +text.server.bans.none=Никаких заблокированных игроков не найдено! +text.server.admins=Администраторы +text.server.admins.none=Администраторов не найдено! +text.server.add=Добавить сервер +text.server.delete=Вы действительно хотите удалить этот сервер? +text.server.hostname=Хост: {0} +text.server.edit=Редактировать сервер +text.server.outdated=[crimson]Устаревший сервер![] +text.server.outdated.client=[crimson]Устаревший клиент![] +text.server.version=[lightgray]Версия: {0} text.server.custombuild=[yellow]Пользовательская сборка -text.confirmban = Вы действительно хотите заблокировать этого игрока? -text.confirmunban = Вы действительно хотите разблокировать этого игрока? -text.confirmadmin = Вы уверены, что хотите сделать этого игрока администратором? -text.confirmunadmin = Вы действительно хотите удалить статус администратора с этого игрока? -text.joingame.byip = Присоединиться по IP ... -text.joingame.title = Присоединиться к игре -text.joingame.ip = IP: -text.disconnect = Отключён\n -text.disconnect.data = Не удалось загрузить данные мира! -text.connecting = [accent]Подключение... -text.connecting.data = [accent]Загрузка данных мира... -text.connectfail = [crimson]Не удалось подключиться к серверу: [orange] {0} -text.server.port = Порт: +text.confirmban=Вы действительно хотите заблокировать этого игрока? +text.confirmunban=Вы действительно хотите разблокировать этого игрока? +text.confirmadmin=Вы уверены, что хотите сделать этого игрока администратором? +text.confirmunadmin=Вы действительно хотите удалить статус администратора с этого игрока? +text.joingame.byip=Присоединиться по IP ... +text.joingame.title=Присоединиться к игре +text.joingame.ip=IP: +text.disconnect=Отключён\n +text.disconnect.data=Не удалось загрузить данные мира! +text.connecting=[accent]Подключение... +text.connecting.data=[accent]Загрузка данных мира... +text.connectfail=[crimson]Не удалось подключиться к серверу: [orange] {0} +text.server.port=Порт: text.server.addressinuse=Адрес уже используется! -text.server.invalidport = Неверный номер порта! -text.server.error = [crimson]Ошибка создания сервера: [orange] {0} -text.tutorial.back = <назад -text.tutorial.next = далее> -text.save.new = Новое сохранение -text.save.overwrite = Вы уверены,что хотите перезаписать этот слот для сохранения? -text.overwrite = Перезаписать -text.save.none = Сохранения не найдены! -text.saveload = [accent]Сохранение... -text.savefail = Не удалось сохранить игру! -text.save.delete.confirm = Вы уверены,что хотите удалить это сохранение? -text.save.delete = Удалить -text.save.export = Отправить сохранение -text.save.import.invalid = [orange]Это сохранение недействительно! -text.save.import.fail = [crimson]Не удалось импортировать сохранение: [orange] {0} -text.save.export.fail = [crimson]Не удалось отправить сохранение: [orange] {0} -text.save.import = Импортировать сохранение -text.save.newslot = Имя сохранения: -text.save.rename = Переименовывать -text.save.rename.text = Новое название: -text.selectslot = Выберите сохранение. -text.slot = [accent]Слот {0} -text.save.corrupted = [orange]Файл сохранения поврежден или имеет недействительный формат! -text.empty = <Пусто> -text.on = Вкл -text.off = Выкл -text.save.autosave = Автосохранение: {0} -text.save.map = Карта: {0} -text.save.wave = Волна: {0} -text.save.difficulty = Сложность: {0} -text.save.date = Последнее сохранение: {0} -text.confirm = Подтвердить -text.delete = Удалить -text.ok = ОК -text.open = Открыть -text.cancel = Отмена -text.openlink = Открыть ссылку -text.copylink = Скопировать ссылку -text.back = Назад -text.quit.confirm = Вы уверены, что хотите выйти? -text.changelog.title = Список изменений -text.changelog.loading = Получение изменений ... -text.changelog.error.android = [orange]Обратите внимание, что журнал изменений иногда не работает на Android 4.4 и ниже!\nЭто связано с внутренней ошибкой Android. -text.changelog.error.ios = [orange]В настоящее время журнал изменений не поддерживается iOS. -text.changelog.error = [scarlet]Ошибка при получении изменений!\nПроверьте подключение к Интернету. +text.server.invalidport=Неверный номер порта! +text.server.error=[crimson]Ошибка создания сервера: [orange] {0} +text.tutorial.back=<назад +text.tutorial.next=далее> +text.save.new=Новое сохранение +text.save.overwrite=Вы уверены,что хотите перезаписать этот слот для сохранения? +text.overwrite=Перезаписать +text.save.none=Сохранения не найдены! +text.saveload=[accent]Сохранение... +text.savefail=Не удалось сохранить игру! +text.save.delete.confirm=Вы уверены,что хотите удалить это сохранение? +text.save.delete=Удалить +text.save.export=Отправить сохранение +text.save.import.invalid=[orange]Это сохранение недействительно! +text.save.import.fail=[crimson]Не удалось импортировать сохранение: [orange] {0} +text.save.export.fail=[crimson]Не удалось отправить сохранение: [orange] {0} +text.save.import=Импортировать сохранение +text.save.newslot=Имя сохранения: +text.save.rename=Переименовывать +text.save.rename.text=Новое название: +text.selectslot=Выберите сохранение. +text.slot=[accent]Слот {0} +text.save.corrupted=[orange]Файл сохранения поврежден или имеет недействительный формат! +text.empty=<Пусто> +text.on=Вкл +text.off=Выкл +text.save.autosave=Автосохранение: {0} +text.save.map=Карта: {0} +text.save.wave=Волна: {0} +text.save.difficulty=Сложность: {0} +text.save.date=Последнее сохранение: {0} +text.confirm=Подтвердить +text.delete=Удалить +text.ok=ОК +text.open=Открыть +text.cancel=Отмена +text.openlink=Открыть ссылку +text.copylink=Скопировать ссылку +text.back=Назад +text.quit.confirm=Вы уверены, что хотите выйти? +text.changelog.title=Список изменений +text.changelog.loading=Получение изменений ... +text.changelog.error.android=[orange]Обратите внимание, что журнал изменений иногда не работает на Android 4.4 и ниже!\nЭто связано с внутренней ошибкой Android. +text.changelog.error.ios=[orange]В настоящее время журнал изменений не поддерживается iOS. +text.changelog.error=[scarlet]Ошибка при получении изменений!\nПроверьте подключение к Интернету. text.changelog.current=[yellow][[Текущая версия] text.changelog.latest=[orange][[Последняя версия] -text.loading = [accent] Загрузка... -text.wave = [orange]Волна {0} -text.wave.waiting = Волна через {0} -text.waiting = Ожидание... -text.enemies = {0} Противников -text.enemies.single = {0} Противник -text.loadimage = Загрузить изображение -text.saveimage = Сохранить изображение -text.oregen = Генерация руд -text.editor.badsize = [orange]Недопустимый формат изображения! [] \nДопустимый формат карты: {0} -text.editor.errorimageload = Ошибка загрузки изображения: [orange] {0} -text.editor.errorimagesave = Ошибка сохранения изображения: [orange] {0} -text.editor.generate = Создать -text.editor.resize = Изменить \nразмер -text.editor.loadmap = Загрузить\nкарту -text.editor.savemap = Сохранить\nкарту -text.editor.loadimage = Загрузить \nизображение -text.editor.saveimage = Сохранить \nизображение -text.editor.unsaved = [scarlet]У вас есть не сохраненные изменения![] \nВы уверены,что хотите выйти? -text.editor.brushsize = Размер кисти: {0} -text.editor.noplayerspawn = На этой карте нет точки появления игрока! -text.editor.manyplayerspawns = На карте не может быть больше одной точки появления игрока! -text.editor.manyenemyspawns = Не может быть больше {0} вражеских точек появления! -text.editor.resizemap = Изменить размер карты -text.editor.resizebig = [scarlet]Внимание! \n[]Карты размером больше 256 единиц могут быть не стабильны и тормозить. -text.editor.mapname = Название карты: -text.editor.overwrite = [accent]Внимание! \nЭто перезапишет уже существующую карту. -text.editor.failoverwrite = [crimson]Невозможно перезаписать стандартную карту! -text.editor.selectmap = Выберите карту для загрузки: -text.width = Ширина: -text.height = Высота: -text.randomize = Рандомизировать -text.apply = Применить -text.update = Обновить -text.menu = Меню -text.play = Играть -text.load = Загрузить -text.save = Сохранить -text.language.restart = Перезагрузите игру, чтобы настройки языка вступили в силу. -text.settings.language = Язык -text.settings = Настройки -text.tutorial = Обучение -text.editor = Редактор -text.mapeditor = Редактор карт -text.donate = Донат -text.settings.reset = Сбросить по умолчанию -text.settings.controls = Управление -text.settings.game = Игра -text.settings.sound = Звук -text.settings.graphics = Графика -text.upgrades = Улучшения -text.purchased = [LIME]Создан! -text.weapons = Оружие -text.paused = Пауза -text.respawn = Возрождение через -text.info.title = [accent]Информация -text.error.title = [crimson]Произошла ошибка -text.error.crashmessage = [SCARLET]Произошла непредвиденная ошибка,которая могла вызвать сбой.[]Пожалуйста, сообщите точные обстоятельства разработчику,при которых эта ошибка возникла : [ORANGE]anukendev@gmail.com[] -text.error.crashtitle = Произошла ошибка -text.mode.break = Режим сноса: {0} -text.mode.place = Режим размещения: {0} -placemode.hold.name = линия -placemode.areadelete.name = зона -placemode.touchdelete.name = касание -placemode.holddelete.name = удержание -placemode.none.name = ничего -placemode.touch.name = Касание -placemode.cursor.name = курсор -text.blocks.extrainfo = [accent]дополнительная информация о блоке: -text.blocks.blockinfo = Информация о блоке -text.blocks.powercapacity = Вместимость энергии -text.blocks.powershot = Энергия / выстрел -text.blocks.powersecond = Энергия / в секунду -text.blocks.powerdraindamage = Поглощение энергии / урон -text.blocks.shieldradius = Радиус щита -text.blocks.itemspeedsecond = Скорость предметов / в секунду -text.blocks.range = Дальность -text.blocks.size = Размер -text.blocks.powerliquid = Энергия / Жидкость -text.blocks.maxliquidsecond = Макс. Жидкость / в секунду -text.blocks.liquidcapacity = Вместимость жидкости -text.blocks.liquidsecond = Жидкость / в секунду -text.blocks.damageshot = Урон / выстрел -text.blocks.ammocapacity = Вместимость боеприпасов -text.blocks.ammo = боеприпасы -text.blocks.ammoitem = боеприпасы / предмет -text.blocks.maxitemssecond = Макс. Количество предметов / в секунду -text.blocks.powerrange = Диапазон мощности энергии -text.blocks.lasertilerange = Дальность лазера -text.blocks.capacity = Вместимость -text.blocks.itemcapacity = Вместимость предметов -text.blocks.maxpowergenerationsecond = Макс. выработка энергии / в секунду -text.blocks.powergenerationsecond = Выработка энергии / в секунду -text.blocks.generationsecondsitem = Выработка секунд / предмет -text.blocks.input = Прием -text.blocks.inputliquid = Прием жидкости -text.blocks.inputitem = Прием предмета -text.blocks.output = Вывод -text.blocks.secondsitem = Секунды / предмет -text.blocks.maxpowertransfersecond = Максимальная передача энергии / секунда -text.blocks.explosive = Взрывоопасно! -text.blocks.repairssecond = Ремонт / в секунду -text.blocks.health = Здоровье -text.blocks.inaccuracy = Разброс -text.blocks.shots = Выстрелы -text.blocks.shotssecond = Выстрелов / в секунду -text.blocks.fuel = топливо -text.blocks.fuelduration = Продолжительность действия топлива -text.blocks.maxoutputsecond = Макс. Вывод / в секунду -text.blocks.inputcapacity = Вместимость ввода -text.blocks.outputcapacity = Вместимость вывода -text.blocks.poweritem = Энергия / предмет -text.placemode = Режим размещения -text.breakmode = Режим сноса -text.health = здоровье -setting.difficulty.easy = легко -setting.difficulty.normal = нормально -setting.difficulty.hard = тяжело -setting.difficulty.insane = нереально -setting.difficulty.purge = зачистка -setting.difficulty.name = Сложность -setting.screenshake.name = Дрожание экрана -setting.smoothcam.name = Плавная камера -setting.indicators.name = Индикаторы противников -setting.effects.name = Эффекты на экране -setting.sensitivity.name = Чувствительность контроллера -setting.saveinterval.name = Интервал автосохранения -setting.seconds = {0} Секунд -setting.fullscreen.name = Полноэкранный -setting.multithread.name = Многопоточность -setting.fps.name = Показать FPS -setting.vsync.name = Верт. синхронизация -setting.lasers.name = Показывать энергетические лазеры -setting.previewopacity.name = Прозрачность объкта при предв. просм. -setting.healthbars.name = Показать полоски здоровья объекта -setting.pixelate.name = Пикселизация экрана -setting.musicvol.name = Громкость музыки -setting.mutemusic.name = Заглушить музыку -setting.sfxvol.name = Громкость звуковых эффектов -setting.mutesound.name = Заглушить звук -map.maze.name = лабиринт -map.fortress.name = крепость -map.sinkhole.name = раковина -map.caves.name = пещеры -map.volcano.name = вулкан -map.caldera.name = кальдера -map.scorch.name = горение -map.desert.name = пустыня -map.island.name = остров -map.grassland.name = луг -map.tundra.name = тундра -map.spiral.name = спираль -map.tutorial.name = обучение -tutorial.intro.text = [yellow]Добро пожаловать в обучение.[] Чтобы начать нажмите «далее». -tutorial.moveDesktop.text = Для перемещения используйте [orange][[WASD][] клавиши. Удерживайте [orange]shift[] для ускорения. Удерживайте [orange]CTRL[] при использовании [orange]​​колесика мыши[] для увеличения или уменьшения масштаба. -tutorial.shoot.text = Используйте мышь для прицеливания, удерживайте [orange]левую кнопку мыши[],чтобы выстрелить. Попробуйте на [yellow]цели[]. -tutorial.moveAndroid.text = Чтобы изменить вид, перетащите палец по экрану. Зажмите и разведите двумя пальцами,чтобы увеличить или уменьшить степень приближения. -tutorial.placeSelect.text = Попробуйте выбрать [yellow]конвейер[] из меню блоков внизу справа. -tutorial.placeConveyorDesktop.text = Используйте [orange][[колёсико мыши][],чтобы повернуть конвейер лицом [orange]вперед[], поместите его в [yellow]отмеченное место[],используя [orange][[левую кнопку мыши]]. -tutorial.placeConveyorAndroid.text = Используйте [orange][[кнопку поворота][], чтобы повернуть конвейер лицом [orange]вперед[], перетащите его на место одним пальцем, затем поместите его в [yellow]отмеченное место[],используя [orange][[галочку][]. -tutorial.placeConveyorAndroidInfo.text = Кроме того, вы можете нажать на значок перекрестия в левом нижнем углу, чтобы перейти в [orange][[режим касания][] и поместить блоки, нажав на экран. В режиме касания блоки можно поворачивать стрелкой в ​​левом нижнем углу. Нажмите [yellow]далее[],чтобы попробовать. -tutorial.placeDrill.text = Теперь, выберите и поместите [yellow]каменный бур[] в отмеченное место. -tutorial.blockInfo.text = Если вы хотите узнать больше о блоке, вы можете нажать на [orange]знак вопроса[] в правом верхнем углу, чтобы прочитать его описание. -tutorial.deselectDesktop.text = Вы можете отменить выбор блока, используя [orange][[правую кнопку мыши][]. -tutorial.deselectAndroid.text = Вы можете отменить выбор блока, нажав кнопку [orange]X[]. -tutorial.drillPlaced.text = Бур теперь производит [yellow]камень,[] выведет его на конвейер, а затем переместит его в [yellow]ядро​[] -tutorial.drillInfo.text = Разным рудам нужны разные буры. Камень требует каменный бур, железо требует железный бур и т.д. -tutorial.drillPlaced2.text = Перемещение предметов в ядро ​​помещает их в ваш [yellow]инвентарь для предметов[], в левом верхнем углу. Размещение блоков использует предметы из вашего инвентаря. -tutorial.moreDrills.text = Вы можете соединить много буров и конвейеров вместе, вот так. -tutorial.deleteBlock.text = Вы можете удалить блоки, щелкнув [orange]правой кнопкой мыши[] на блоке, который вы хотите удалить. Попробуйте удалить этот конвейер. -tutorial.deleteBlockAndroid.text = Вы можете удалить блоки [orange]выбрав перекрестие []в меню режима сноса[orange][] в левом нижнем углу и нажать на блок. Попробуйте удалить этот конвейер. -tutorial.placeTurret.text = Теперь выберите и поместите [yellow]турель[] в [yellow]отмеченную позицию[]. -tutorial.placedTurretAmmo.text = Эта турель теперь принимает [yellow]боеприпасы[] от конвейера. Вы можете видеть, сколько патронов у него есть, посмотрев на зеленую полосу над ней [green][]. -tutorial.turretExplanation.text = Турели автоматически стреляют в ближайшего противника в радиусе действия, если у них достаточно боеприпасов. -tutorial.waves.text = Каждые [yellow]60[] секунд, волна [coral]противников[] будет появляться в определенных местах и будет ​​пытаться уничтожить ядро. -tutorial.coreDestruction.text = Ваша цель - [yellow]защитить ядро​​[]. Если ядро будет ​​уничтожено, то вы [cotal]проиграете[] -tutorial.pausingDesktop.text = Если вам нужен будет перерыв, нажмите кнопку [orange]пауза[] в верхнем левом углу или [orange]пробел[],чтобы приостановить игру. Вы можете выбирать и размещать блоки во время паузы, но не можете перемещаться или стрелять. -tutorial.pausingAndroid.text = Если вам нужен будет перерыв, нажмите кнопку [orange]пауза[] в левом верхнем углу, чтобы приостановить игру. Вы можете по-прежнему размещать выбранные блоки во время паузы. -tutorial.purchaseWeapons.text = Вы можете приобрести новое [yellow]оружие[] для своего меха, открыв меню обновлений в левом нижнем углу. -tutorial.switchWeapons.text = Переключаться между оружием можно, щелкнув на его значок в левом нижнем углу или используя цифры [orange][[1-9][]. -tutorial.spawnWave.text = Сейчас прибудет волна. Уничтожьте их. -tutorial.pumpDesc.text = В более поздних волнах, вы должны использовать [yellow]насосы[] для распределения жидкостей для генераторов или экстракторов. -tutorial.pumpPlace.text = Насосы работают аналогично бурам, за исключением того, что они производят жидкости вместо предметов. Попробуйте поместить насос на [yellow]обозначенную нефть[]. -tutorial.conduitUse.text = Теперь поместите [orange]трубопровод[], ведущий от насоса. -tutorial.conduitUse2.text = И еще немного ... -tutorial.conduitUse3.text = И еще немного ... -tutorial.generator.text = Теперь поместите блок[orange]генератор сжигания[] в конец трубопровода. -tutorial.generatorExplain.text = Этот генератор теперь создаст [yellow]энергию[] из нефти. -tutorial.lasers.text = Энергия распределяется с использованием [yellow]электрических лазеров[]. Поверните и поместите его здесь. -tutorial.laserExplain.text = Теперь генератор передаст энергию в лазерный блок. [yellow]Непрозрачный[] луч означает, что он в настоящее время передает электричество, а [yellow]прозрачный[] луч означает, что он не передает электричество. -tutorial.laserMore.text = Вы можете проверить,какой заряд у блока, наблюдая за [yellow]желтой полосой[] над ним. -tutorial.healingTurret.text = Этот лазер можно использовать для питания [lime]ремонтной турели[]. Поместите её сюда. -tutorial.healingTurretExplain.text = Пока она имеет заряд, эта турель будет [lime]ремонтировать соседние блоки.[] Когда вы играете, убедитесь, что вы имеете такую на своей базе как можно быстрее. -tutorial.smeltery.text = Для многих блоков требуется [orange]сталь[], для этого требуется [orange]плавильный завод[]. Поместите его сюда. -tutorial.smelterySetup.text = Этот завод теперь производит [orange]сталь[] из поступающего железа, используя уголь в качестве топлива. -tutorial.tunnelExplain.text = Также обратите внимание, что предметы проходят через [orange] туннельный блок [] и появляются на другой стороне, проходя через каменный блок. Имейте в виду, что туннели могут проходить только до двух блоков. -tutorial.end.text = На этом обучение закончено! Удачи! -text.keybind.title = Переназначить клавиши -keybind.move_x.name = движение_x -keybind.move_y.name = движение_y -keybind.select.name = выбрать -keybind.break.name = Разрушить -keybind.shoot.name = стрельба -keybind.zoom_hold.name = удержание_зума -keybind.zoom.name = Приблизить -keybind.block_info.name = инфо_о_блоке -keybind.menu.name = Меню -keybind.pause.name = Пауза -keybind.dash.name = Рывок -keybind.chat.name = Чат -keybind.player_list.name = список_игроков -keybind.console.name = консоль -keybind.rotate_alt.name = вращать_alt -keybind.rotate.name = вращать -keybind.weapon_1.name = Оружие_1 -keybind.weapon_2.name = Оружие_2 -keybind.weapon_3.name = Оружие_3 -keybind.weapon_4.name = Оружие_4 -keybind.weapon_5.name = Оружие_5 -keybind.weapon_6.name = Оружие_6 +text.loading=[accent] Загрузка... +text.wave=[orange]Волна {0} +text.wave.waiting=Волна через {0} +text.waiting=Ожидание... +text.enemies={0} Противников +text.enemies.single={0} Противник +text.loadimage=Загрузить изображение +text.saveimage=Сохранить изображение +text.editor.badsize=[orange]Недопустимый формат изображения! [] \nДопустимый формат карты: {0} +text.editor.errorimageload=Ошибка загрузки изображения: [orange] {0} +text.editor.errorimagesave=Ошибка сохранения изображения: [orange] {0} +text.editor.generate=Создать +text.editor.resize=Изменить \nразмер +text.editor.loadmap=Загрузить\nкарту +text.editor.savemap=Сохранить\nкарту +text.editor.loadimage=Загрузить \nизображение +text.editor.saveimage=Сохранить \nизображение +text.editor.unsaved=[scarlet]У вас есть не сохраненные изменения![] \nВы уверены,что хотите выйти? +text.editor.brushsize=Размер кисти: {0} +text.editor.noplayerspawn=На этой карте нет точки появления игрока! +text.editor.manyplayerspawns=На карте не может быть больше одной точки появления игрока! +text.editor.manyenemyspawns=Не может быть больше {0} вражеских точек появления! +text.editor.resizemap=Изменить размер карты +text.editor.resizebig=[scarlet]Внимание! \n[]Карты размером больше 256 единиц могут быть не стабильны и тормозить. +text.editor.mapname=Название карты: +text.editor.overwrite=[accent]Внимание! \nЭто перезапишет уже существующую карту. +text.editor.selectmap=Выберите карту для загрузки: +text.width=Ширина: +text.height=Высота: +text.randomize=Рандомизировать +text.apply=Применить +text.update=Обновить +text.menu=Меню +text.play=Играть +text.load=Загрузить +text.save=Сохранить +text.language.restart=Перезагрузите игру, чтобы настройки языка вступили в силу. +text.settings.language=Язык +text.settings=Настройки +text.tutorial=Обучение +text.editor=Редактор +text.mapeditor=Редактор карт +text.donate=Донат +text.settings.reset=Сбросить по умолчанию +text.settings.controls=Управление +text.settings.game=Игра +text.settings.sound=Звук +text.settings.graphics=Графика +text.upgrades=Улучшения +text.purchased=[LIME]Создан! +text.weapons=Оружие +text.paused=Пауза +text.info.title=[accent]Информация +text.error.title=[crimson]Произошла ошибка +text.error.crashmessage=[SCARLET]Произошла непредвиденная ошибка,которая могла вызвать сбой.[]Пожалуйста, сообщите точные обстоятельства разработчику,при которых эта ошибка возникла : [ORANGE]anukendev@gmail.com[] +text.error.crashtitle=Произошла ошибка +text.blocks.blockinfo=Информация о блоке +text.blocks.powercapacity=Вместимость энергии +text.blocks.powershot=Энергия / выстрел +text.blocks.size=Размер +text.blocks.liquidcapacity=Вместимость жидкости +text.blocks.maxitemssecond=Макс. Количество предметов / в секунду +text.blocks.powerrange=Диапазон мощности энергии +text.blocks.itemcapacity=Вместимость предметов +text.blocks.inputliquid=Прием жидкости +text.blocks.inputitem=Прием предмета +text.blocks.explosive=Взрывоопасно! +text.blocks.health=Здоровье +text.blocks.inaccuracy=Разброс +text.blocks.shots=Выстрелы +text.blocks.inputcapacity=Вместимость ввода +text.blocks.outputcapacity=Вместимость вывода +setting.difficulty.easy=легко +setting.difficulty.normal=нормально +setting.difficulty.hard=тяжело +setting.difficulty.insane=нереально +setting.difficulty.purge=зачистка +setting.difficulty.name=Сложность +setting.screenshake.name=Дрожание экрана +setting.smoothcam.name=Плавная камера +setting.indicators.name=Индикаторы противников +setting.effects.name=Эффекты на экране +setting.sensitivity.name=Чувствительность контроллера +setting.saveinterval.name=Интервал автосохранения +setting.seconds={0} Секунд +setting.fullscreen.name=Полноэкранный +setting.multithread.name=Многопоточность +setting.fps.name=Показать FPS +setting.vsync.name=Верт. синхронизация +setting.lasers.name=Показывать энергетические лазеры +setting.previewopacity.name=Прозрачность объкта при предв. просм. +setting.healthbars.name=Показать полоски здоровья объекта +setting.pixelate.name=Пикселизация экрана +setting.musicvol.name=Громкость музыки +setting.mutemusic.name=Заглушить музыку +setting.sfxvol.name=Громкость звуковых эффектов +setting.mutesound.name=Заглушить звук +map.maze.name=лабиринт +map.fortress.name=крепость +map.sinkhole.name=раковина +map.caves.name=пещеры +map.volcano.name=вулкан +map.caldera.name=кальдера +map.scorch.name=горение +map.desert.name=пустыня +map.island.name=остров +map.grassland.name=луг +map.tundra.name=тундра +map.spiral.name=спираль +map.tutorial.name=обучение +text.keybind.title=Переназначить клавиши +keybind.move_x.name=движение_x +keybind.move_y.name=движение_y +keybind.select.name=выбрать +keybind.break.name=Разрушить +keybind.shoot.name=стрельба +keybind.zoom_hold.name=удержание_зума +keybind.zoom.name=Приблизить +keybind.block_info.name=инфо_о_блоке +keybind.menu.name=Меню +keybind.pause.name=Пауза +keybind.dash.name=Рывок +keybind.chat.name=Чат +keybind.player_list.name=список_игроков +keybind.console.name=консоль +keybind.rotate_alt.name=вращать_alt +keybind.rotate.name=вращать mode.text.help.title=Описание режимов -mode.waves.name = волны -mode.waves.description = в нормальном режиме. ограниченные ресурсы и автоматические наступающие волны. -mode.sandbox.name = песочница -mode.sandbox.description = бесконечные ресурсы и нет таймера для волн. -mode.freebuild.name = свободная\nстройка +mode.waves.name=волны +mode.waves.description=в нормальном режиме. ограниченные ресурсы и автоматические наступающие волны. +mode.sandbox.name=песочница +mode.sandbox.description=бесконечные ресурсы и нет таймера для волн. +mode.freebuild.name=свободная\nстройка mode.freebuild.description=ограниченные ресурсы и нет таймера для волн. -upgrade.standard.name = стандарт -upgrade.standard.description = Стандартный мех. -upgrade.blaster.name = Бластер -upgrade.blaster.description = Стреляет медленной, слабой пулей. -upgrade.triblaster.name = трибластер -upgrade.triblaster.description = Стреляет 3 пулями в разброс. -upgrade.clustergun.name = Кластерная пушка -upgrade.clustergun.description = Стреляет неточным распространением взрывных гранат. -upgrade.beam.name = Лучевая пушка -upgrade.beam.description = Стреляет пробивающим лазерным луч высокой дальности. -upgrade.vulcan.name = вулкан -upgrade.vulcan.description = Стреляет шквалом быстрых пуль. -upgrade.shockgun.name = шоковая пушка -upgrade.shockgun.description = Стреляет взрывным зарядом заряженной шрапнели. -item.stone.name = камень -item.iron.name = железо -item.coal.name = Уголь -item.steel.name = сталь -item.titanium.name = титан -item.dirium.name = дириум -item.thorium.name = уран -item.sand.name = песок -liquid.water.name = Вода -liquid.plasma.name = Плазма -liquid.lava.name = лава -liquid.oil.name = Нефть -block.weaponfactory.name = оружейный завод -block.weaponfactory.fulldescription=Используется для создания оружия для игрока. Нажмите для использования. Автоматически извлекает ресурсы из ядра. -block.air.name = воздух -block.blockpart.name = часть блока -block.deepwater.name = глубоководье -block.water.name = вода -block.lava.name = лава -block.oil.name = нефть -block.stone.name = Камень -block.blackstone.name = черный камень -block.iron.name = железо -block.coal.name = уголь -block.titanium.name = титан -block.thorium.name = уран -block.dirt.name = земля -block.sand.name = песок -block.ice.name = лед -block.snow.name = снег -block.grass.name = трава -block.sandblock.name = блок песка -block.snowblock.name = блок снега -block.stoneblock.name = блок камня -block.blackstoneblock.name = блок черного камня -block.grassblock.name = блок травы -block.mossblock.name = блок мха -block.shrub.name = кустарник -block.rock.name = валун -block.icerock.name = замерзший валун -block.blackrock.name = черный валун -block.dirtblock.name = блок земли -block.stonewall.name = каменная стена -block.stonewall.fulldescription = Дешевый оборонительный блок. Полезен для защиты ядра и турелей в первых волнах. -block.ironwall.name = железная стена -block.ironwall.fulldescription = Основной защитный блок. Обеспечивает защиту от противников. -block.steelwall.name = стальная стена -block.steelwall.fulldescription = Стандартный защитный блок. адекватная защита от противников. -block.titaniumwall.name = титановая стена -block.titaniumwall.fulldescription = Сильный защитный блок. Обеспечивает защиту от противников. -block.duriumwall.name = стена из дириума -block.duriumwall.fulldescription = Очень прочный защитный блок. Обеспечивает защиту от противников. -block.compositewall.name = композитная стена -block.steelwall-large.name = большая стальная стена -block.steelwall-large.fulldescription = Стандартный защитный блок. Охватывает несколько клеток. -block.titaniumwall-large.name = большая титановая стена -block.titaniumwall-large.fulldescription = Сильный защитный блок. Охватывает несколько клеток. -block.duriumwall-large.name = большая стена из дириума -block.duriumwall-large.fulldescription = Очень сильный защитный блок. Охватывает несколько клеток. -block.titaniumshieldwall.name = экранированная стена -block.titaniumshieldwall.fulldescription = Прочный защитный блок с дополнительным встроенным щитом. Требует энергию. Использует энергию для поглощения вражеских пуль. Для обеспечения энергией этого блока рекомендуется использовать усилители энергии. -block.repairturret.name = ремонтная турель -block.repairturret.fulldescription = Ремонтирует близлежащие поврежденные блоки в радиусе действия. Использует небольшое количество энергии. -block.megarepairturret.name = ремонтная турель II -block.megarepairturret.fulldescription = Ремонтирует близлежащие поврежденные блоки в радиусе действия с приличной скоростью. Использует энергию. -block.shieldgenerator.name = Генератор щита -block.shieldgenerator.fulldescription = Передовой защитный блок. Защищает все блоки в радиусе от атаки. Использует мало энергии когда бездействует, но быстро разряжается при контакте с пулями. -block.door.name = дверь -block.door.fulldescription = Блок, который можно открыть и закрыть, нажав на него. -block.door-large.name = большая дверь -block.door-large.fulldescription = Блок, который можно открыть и закрыть, нажав на него. -block.conduit.name = трубопровод -block.conduit.fulldescription = Основной блок транспортировки жидкости. Работает как конвейер, но с жидкостями. Лучше всего использовать насосы или другие трубопроводы. Может использоваться как мост через жидкости для противников и игроков. -block.pulseconduit.name = импульсный трубопровод -block.pulseconduit.fulldescription = Передовой блок транспортировки жидкости. Перемещает жидкости быстрее и хранит больше, чем стандартные трубопроводы. -block.liquidrouter.name = Маршрутизатор житкостей -block.liquidrouter.fulldescription = Работает аналогично маршрутизатору. Принимает жидкость с одной стороны и выводит ее на другие стороны. Полезно для разделения жидкости из одного трубопровода на несколько других трубопроводов. -block.conveyor.name = конвейер -block.conveyor.fulldescription = Основной транспортный блок. Перемещает предметы вперед и автоматически перекладывает их в турели или в крафтеры. Могут вращаться . Может использоваться как мост через жидкости для противников и игроков. -block.steelconveyor.name = стальной конвейер -block.steelconveyor.fulldescription = Передовой транспортный блок предметов. Перемещает предметы быстрее, чем стандартные конвейеры. -block.poweredconveyor.name = импульсный конвейер -block.poweredconveyor.fulldescription = Лучший транспортный блок. Перемещает предметы быстрее, чем стальные конвейеры. -block.router.name = Маршрутизатор -block.router.fulldescription = Принимает предметы с одного направления и выводит их в 3 других направлениях. Может также хранить определенное количество предметов. Используется для разделения материалов с одного бура на несколько турелей -block.junction.name = Перекресток -block.junction.fulldescription = Действует как мост для двух конвейерных лент. Полезно в ситуациях с двумя различными конвейерами, несущими разные материалы в разные места. -block.conveyortunnel.name = конвейерный туннель -block.conveyortunnel.fulldescription = Перемещает предмет под блоками. Чтобы использовать, поместите один туннель, ведущий в блок, который должен быть туннелирован, и один на другой стороне. Убедитесь, что оба туннеля обращены к противоположным направлениям, которые относятся к блокам, которые они принимают или выводят. -block.liquidjunction.name = Перекресток для жидкостей -block.liquidjunction.fulldescription = Действует как мост для двух пересекающихся трубопроводов. Полезно в ситуациях с двумя различными каналами, перемещающими различные жидкости в разные места. -block.liquiditemjunction.name = Распределитель жидкостей и предметов -block.liquiditemjunction.fulldescription = Действует как мост для пересекающихся трубопроводов и конвейеров. -block.powerbooster.name = усилитель энергии -block.powerbooster.fulldescription = Распределяет электричество всем блокам в пределах своего радиуса. -block.powerlaser.name = Энергетический лазер -block.powerlaser.fulldescription = Создает лазер, который передает питание блоку перед ним. Не генерирует никакой энергии. Лучше всего использовать с генераторами или другими лазерами. -block.powerlaserrouter.name = лазерный маршрутизатор -block.powerlaserrouter.fulldescription = Лазер, который одновременно передает электричество в три направления. Полезно в тех ситуациях, когда требуется питание нескольким блокам от одного генератора. -block.powerlasercorner.name = лазерный угол -block.powerlasercorner.fulldescription = Лазер, распределяющий энергию сразу на два направления. Полезно в тех ситуациях, когда требуется питание нескольким блокам от одного генератора, а маршрутизатор не годится. -block.teleporter.name = телепорт -block.teleporter.fulldescription = Улучшенный транспортный блок предметов. Телепортеры передают предметы в другие телепорты одного цвета. Ничего не происходит, если нет телепортеров одного цвета. Если несколько телепортеров имеют один и тот же цвет, выбирается случайный. Использует энергия. Нажмите, чтобы изменить цвет. -block.sorter.name = сортировщик -block.sorter.fulldescription = Сортирует предмет по типу материала. Материал для приема указывается цветом в блоке. Все предметы, соответствующие материалу сортировки, выводятся вперед, все остальное выводится влево и вправо. -block.core.name = Ядро -block.pump.name = Насос -block.pump.fulldescription = Качают жидкости из блока источнка - обычно вода, лава или нефть. Выводит жидкость в соседние трубопроводы. -block.fluxpump.name = Флюсовый насос -block.fluxpump.fulldescription = Передовая версия насоса. Хранит больше жидкости и качает быстрее. -block.smelter.name = Плавильный завод -block.smelter.fulldescription = Основной блок крафтер. При вводе 1 х железа и 1 х угля выдается одна сталь. -block.crucible.name = Тигель -block.crucible.fulldescription = Продвинутый блок крафтер. При вводе 1х титана и 1х стали выдается один дириум. -block.coalpurifier.name = Экстрактор угля -block.coalpurifier.fulldescription = Стандартный экстрактор. Выдает уголь, когда подается большое количество воды и камня. -block.titaniumpurifier.name = Экстрактор титана -block.titaniumpurifier.fulldescription = Стандартный экстрактор. Выдает титан при подаче большого количества воды и железа. -block.oilrefinery.name = Нефтеперерабатывающий Завод -block.oilrefinery.fulldescription = Перерабатывает большое количество нефти в уголь. Полезно для заправки турелей использующих уголь, когда на карте дефицит угля. -block.stoneformer.name = Формировщик камня -block.stoneformer.fulldescription = Охлаждает жидкую лаву, делая из него камень. Полезен для производства большого количества камней для ​угольного очистителя -block.lavasmelter.name = лавовый плавильный завод -block.lavasmelter.fulldescription = Использует лаву для переработки железа в сталь. Альтернатива плавильным заводам. Полезно в ситуациях, когда угля мало. -block.stonedrill.name = каменный бур -block.stonedrill.fulldescription = Важный бур. Когда он помещается на каменную клетку, медленно, бесконечно добывает камень. -block.irondrill.name = Железный бур -block.irondrill.fulldescription = Основной бур. При размещении на клетке с железной рудой, выдает железо медленным темпом на неопределенный срок. -block.coaldrill.name = угольный бур -block.coaldrill.fulldescription = Основной бур. При размещении на клетке с угольной рудой происходит медленный темп добычи угля на неопределенный срок. -block.thoriumdrill.name = урановый бур -block.thoriumdrill.fulldescription = Передовой бур. При размещении на клетке с урановой рудой выдает уран медленным темпом на неопределенный срок. -block.titaniumdrill.name = титановый бур -block.titaniumdrill.fulldescription = Продвинутый бур. При размещении на клетках с титановой рудой выводится титан медленным темпом на неопределенный срок. -block.omnidrill.name = Адаптивный бур -block.omnidrill.fulldescription = Идеальный бур. Будет добывать любую руду на которой стоит с безумным темпом\n -block.coalgenerator.name = угольный генератор -block.coalgenerator.fulldescription = Важный генератор. Генерирует энергию из угля. Выводит энергию в качестве лазеров на 4 стороны. -block.thermalgenerator.name = термальный генератор -block.thermalgenerator.fulldescription = Генерирует энергию из лавы. Выводит электричество в качестве лазеров на 4 стороны. -block.combustiongenerator.name = генератор внутреннего сгорания -block.combustiongenerator.fulldescription = Генерирует энергию из нефти. Выводит энергию в качестве лазеров на 4 стороны. -block.rtgenerator.name = Генератор RTG -block.rtgenerator.fulldescription = Генерирует небольшое количество энергии из распада радиоактивного урана. Выводит энергию в качестве лазеров на 4 стороны. -block.nuclearreactor.name = ядерный реактор -block.nuclearreactor.fulldescription = Передовая версия генератора RTG и идеальный источник энергии. Генерирует энергию из урана. Требуется постоянное водяное охлаждение.Крайне взрывоопасен; сильно взорвётся при подаче недостаточного количества хладагента. -block.turret.name = Турель -block.turret.fulldescription = Базовая, дешевая турель. Использует камень для боеприпасов. Имеет немного больше диапазон, чем двойная турель. -block.doubleturret.name = двойная турель -block.doubleturret.fulldescription = Немного более мощная версия турели. Использует камень для боеприпасов. Значительно больший урон, но имеет более низкий диапазон. Выстреливает двумя пулями. -block.machineturret.name = Турель Гатлинга -block.machineturret.fulldescription = Стандартная универсальная турель. Использует железо для боеприпасов. Обладает быстрой скоростью выстрелов с приличным уроном. -block.shotgunturret.name = разветвленная турель\n -block.shotgunturret.fulldescription = Стандартная турель. Использует железо для боеприпасов. Стреляет в разброс 7 пулями. Маленький диапазон, но более высокий уровень урона по сравнению с турелью Гатлинга. -block.flameturret.name = Огнемётная турель\n -block.flameturret.fulldescription = Продвинутая турель для защиты на близком расстоянии. Использует уголь для боеприпасов. Имеет очень маленький диапазон, но очень высокий урон. Хорошо подходит на близких расстояниях. Рекомендуется использовать со стенами. -block.sniperturret.name = Турель-рельсотрон -block.sniperturret.fulldescription = Продвинутая дальнобойная турель. Использует сталь для боеприпасов. Очень высокий урон, но низкая скорость стрельбы. Дорога в использовании, но может быть помещена далеко от вражеских линий из-за её дальности. -block.mortarturret.name = Зенитная турель -block.mortarturret.fulldescription = Продвинутая турель с уроном по зоне. Использует уголь для боеприпасов. Очень низкая скорость стрельбы и пуль, но очень высокий урон по одной цели и зоне. Полезен для больших толп врагов. -block.laserturret.name = лазерная турель -block.laserturret.fulldescription = Продвинутая турель. Использует энергию. Хорошая , универсальная турель средней дальности. Атакует только одну цель. Никогда не промахивается. -block.waveturret.name = Тесла-турель -block.waveturret.fulldescription = Продвинутая многоцелевая турель. Использует энергию. Средняя дальность. Никогда не промахивается. В среднем, может нанести небольшой урон, но он может поразить нескольких противников одновременно с помощью цепной молнии. -block.plasmaturret.name = плазменная турель -block.plasmaturret.fulldescription = Высокотехнологичная версия огнеметной турели. Использует уголь в качестве боеприпасов. Очень высокий урон, дальность между маленькой и средней. -block.chainturret.name = Пулемётная турель -block.chainturret.fulldescription = Самая лучшая, скорострельная турель. Использует уран для боеприпасов. Стреляет большими снарядами с высокой скорострельностью. Средняя дальность. Охватывает несколько клеток. Чрезвычайно прочная. -block.titancannon.name = Пушка-титан -block.titancannon.fulldescription = Самая лучшая, дальнобойная турель. Использует уран как боеприпасы. Стреляет большими снарядами с уроном по зоне со средней скоростью стрельбы. Большая дальность. Охватывает несколько клеток. Чрезвычайно прочная. -block.playerspawn.name = Точка появления игрока -block.enemyspawn.name = Точка появления врага +item.stone.name=камень +item.coal.name=Уголь +item.titanium.name=титан +item.thorium.name=уран +item.sand.name=песок +liquid.water.name=Вода +liquid.lava.name=лава +liquid.oil.name=Нефть +block.door.name=дверь +block.door-large.name=большая дверь +block.conduit.name=трубопровод +block.pulseconduit.name=импульсный трубопровод +block.liquidrouter.name=Маршрутизатор житкостей +block.conveyor.name=конвейер +block.router.name=Маршрутизатор +block.junction.name=Перекресток +block.liquidjunction.name=Перекресток для жидкостей +block.sorter.name=сортировщик +block.smelter.name=Плавильный завод +text.web.unsupported=The web version does not support this feature! Download the game to use it. +text.host.web=The web version does not support hosting games! Download the game to use this feature. +text.map.delete=Are you sure you want to delete the map "[orange]{0}[]"? +text.construction.title=Block Construction Guide +text.construction=You've just selected [accent]block construction mode[].\n\nTo begin placing, simply tap a valid location near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Shift the selection[] by holding and dragging any block in the selection.\n- [accent]Place blocks in a line[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel construction or selection[] by pressing the X at the bottom left. +text.deconstruction.title=Block Deconstruction Guide +text.deconstruction=You've just selected [accent]block deconstruction mode[].\n\nTo begin breaking, simply tap a block near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin de-constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Remove blocks in an area[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel deconstruction or selection[] by pressing the X at the bottom left. +text.showagain=Don't show again next session +text.unlocks=Unlocks +text.addplayers=Add/Remove Players +text.maps=Maps +text.maps.none=[LIGHT_GRAY]No maps found! +text.unlocked=New Block Unlocked! +text.unlocked.plural=New Blocks Unlocked! +text.server.rollback=Rollback +text.server.rollback.numberfield=Rollback Amount: +text.blocks.editlogs=Edit Logs +text.block.editlogsnotfound=[red]There are no edit logs for this location. +text.server.kicked.nameInUse=There is someone with that name\nalready on this server. +text.server.kicked.nameEmpty=Your name must contain at least one character or number. +text.server.kicked.idInUse=You are already on this server! Connecting with two accounts is not permitted. +text.server.kicked.customClient=This server does not support custom builds. Download an official version. +text.trace.structureblocksbroken=Structure blocks broken: [accent]{0} +text.saving=[accent]Saving... +text.unknown=Unknown +text.custom=Custom +text.builtin=Built-In +text.map.delete.confirm=Are you sure you want to delete this map? This action cannot be undone! +text.map.random=[accent]Random Map +text.map.nospawn=This map does not have any cores for the player to spawn in! Add a [ROYAL]blue[] core to this map in the editor. +text.editor.slope=\\ +text.editor.openin=Open In Editor +text.editor.oregen=Ore Generation +text.editor.oregen.info=Ore Generation: +text.editor.mapinfo=Map Info +text.editor.author=Author: +text.editor.description=Description: +text.editor.name=Name: +text.editor.teams=Teams +text.editor.elevation=Elevation +text.editor.saved=Saved! +text.editor.save.noname=Your map does not have a name! Set one in the 'map info' menu. +text.editor.save.overwrite=Your map overwrites a built-in map! Pick a different name in the 'map info' menu. +text.editor.import.exists=[scarlet]Unable to import:[] a built-in map named '{0}' already exists! +text.editor.import=Import... +text.editor.importmap=Import Map +text.editor.importmap.description=Import an already existing map +text.editor.importfile=Import File +text.editor.importfile.description=Import an external map file +text.editor.importimage=Import Terrain Image +text.editor.importimage.description=Import an external map image file +text.editor.export=Export... +text.editor.exportfile=Export File +text.editor.exportfile.description=Export a map file +text.editor.exportimage=Export Terrain Image +text.editor.exportimage.description=Export a map image file +text.editor.overwrite.confirm=[scarlet]Warning![] A map with this name already exists. Are you sure you want to overwrite it? +text.fps=FPS: {0} +text.tps=TPS: {0} +text.ping=Ping: {0}ms +text.settings.rebind=Rebind +text.yes=Yes +text.no=No +text.blocks.targetsair=Targets Air +text.blocks.itemspeed=Units Moved +text.blocks.shootrange=Range +text.blocks.poweruse=Power Use +text.blocks.inputitemcapacity=Input Item Capacity +text.blocks.outputitemcapacity=Input Item Capacity +text.blocks.maxpowergeneration=Max Power Generation +text.blocks.powertransferspeed=Power Transfer +text.blocks.craftspeed=Production Speed +text.blocks.inputliquidaux=Aux Liquid +text.blocks.inputitems=Input Items +text.blocks.outputitem=Output Item +text.blocks.drilltier=Drillables +text.blocks.drillspeed=Base Drill Speed +text.blocks.liquidoutput=Liquid Output +text.blocks.liquiduse=Liquid Use +text.blocks.coolant=Coolant +text.blocks.coolantuse=Coolant Use +text.blocks.inputliquidfuel=Fuel Liquid +text.blocks.liquidfueluse=Liquid Fuel Use +text.blocks.reload=Reload +text.blocks.inputfuel=Fuel +text.blocks.fuelburntime=Fuel Burn Time +text.unit.blocks=blocks +text.unit.powersecond=power units/second +text.unit.liquidsecond=liquid units/second +text.unit.itemssecond=items/second +text.unit.pixelssecond=pixels/second +text.unit.liquidunits=liquid units +text.unit.powerunits=power units +text.unit.degrees=degrees +text.unit.seconds=seconds +text.unit.none= +text.unit.items=items +text.category.general=General +text.category.power=Power +text.category.liquids=Liquids +text.category.items=Items +text.category.crafting=Crafting +text.category.shooting=Shooting +setting.minimap.name=Show Minimap +content.item.name=Items +content.liquid.name=Liquids +content.unit-type.name=Units +content.recipe.name=Blocks +item.stone.description=A common raw material. Used for separating and refining into other materials, or melting into lava. +item.tungsten.name=Tungsten +item.tungsten.description=A common, but very useful structure material. Used in drills and heat-resistant blocks such as generators and smelteries. +item.lead.name=Lead +item.lead.description=A basic starter material. Used extensively in electronics and liquid transportation blocks. +item.coal.description=A common and readily available fuel. +item.carbide.name=Carbide +item.carbide.description=A tough alloy made with tungsten and carbon. Used in advanced transportation blocks and high-tier drills. +item.titanium.description=A rare super-light metal used extensively in liquid transportation, drills and aircraft. +item.thorium.description=A dense, radioactive metal used as structural support and nuclear fuel. +item.silicon.name=Silicon +item.silcion.description=An extremely useful semiconductor, with applications in solar panels and many complex electronics. +item.plastanium.name=Plastanium +item.plastanium.description=A light, ductile material used in advanced aircraft and fragmentation ammunition. +item.phase-matter.name=Phase Matter +item.surge-alloy.name=Surge Alloy +item.biomatter.name=Biomatter +item.biomatter.description=A clump of organic mush; used for conversion into oil or as a basic fuel. +item.sand.description=A common material that is used extensively in smelting, both in alloying and as a flux. +item.blast-compound.name=Blast Compound +item.blast-compound.description=A volatile compound used in bombs and explosives. While it can burned as fuel, this is not advised. +item.pyratite.name=Pyratite +item.pyratite.description=An extremely flammable substance used in incendiary weapons. +liquid.cryofluid.name=Cryofluid +text.item.explosiveness=[LIGHT_GRAY]Explosiveness: {0} +text.item.flammability=[LIGHT_GRAY]Flammability: {0} +text.item.radioactivity=[LIGHT_GRAY]Radioactivity: {0} +text.item.fluxiness=[LIGHT_GRAY]Flux Power: {0} +text.item.hardness=[LIGHT_GRAY]Hardness: {0} +text.liquid.heatcapacity=[LIGHT_GRAY]Heat Capacity: {0} +text.liquid.viscosity=[LIGHT_GRAY]Viscosity: {0} +text.liquid.temperature=[LIGHT_GRAY]Temperature: {0} +block.tungsten-wall.name=Tungsten Wall +block.tungsten-wall-large.name=Large Tungsten Wall +block.carbide-wall.name=Carbide Wall +block.carbide-wall-large.name=Large Carbide Wall +block.thorium-wall.name=Thorium Wall +block.thorium-wall-large.name=Large Thorium Wall +block.duo.name=Duo +block.scorch.name=Scorch +block.hail.name=Hail +block.lancer.name=Lancer +block.titanium-conveyor.name=Titanium Conveyor +block.splitter.name=Splitter +block.splitter.description=Outputs items into two opposite directions immediately after they are recieved. +block.router.description=Splits items into all 4 directions. Can store items as a buffer. +block.distributor.name=Distributor +block.distributor.description=A splitter that can split items into 8 directions. +block.sorter.description=Sorts items. If an item matches the selection, it is allowed to pass. Otherwise, the item is outputted to the left and right. +block.overflow-gate.name=Overflow Gate +block.overflow-gate.description=A combination splitter and router that only outputs to the left and right if the front path is blocked. +block.bridgeconveyor.name=Bridge Conveyor +block.bridgeconveyor.description=A conveyor that can go over other blocks, for up to two total blocks. +block.arc-smelter.name=Arc Smelter +block.silicon-smelter.name=Silicon Smelter +block.phase-weaver.name=Phase Weaver +block.pulverizer.name=Pulverizer +block.cryofluidmixer.name=Cryofluid Mixer +block.melter.name=Melter +block.incinerator.name=Incinerator +block.biomattercompressor.name=Biomatter Compressor +block.separator.name=Separator +block.centrifuge.name=Centrifuge +block.power-node.name=Power Node +block.power-node-large.name=Large Power Node +block.battery.name=Battery +block.battery-large.name=Large Battery +block.combustion-generator.name=Combustion Generator +block.turbine-generator.name=Turbine Generator +block.tungsten-drill.name=Tungsten Drill +block.carbide-drill.name=Carbide Drill +block.laser-drill.name=Laser Drill +block.water-extractor.name=Water Extractor +block.cultivator.name=Cultivator +block.dart-ship-factory.name=Dart Ship Factory +block.delta-mech-factory.name=Delta Mech Factory +block.dronefactory.name=Drone Factory +block.repairpoint.name=Repair Point +block.resupplypoint.name=Resupply Point +block.liquidtank.name=Liquid Tank +block.bridgeconduit.name=Bridge Conduit +block.mechanical-pump.name=Mechanical Pump +block.itemsource.name=Item Source +block.itemvoid.name=Item Void +block.liquidsource.name=Liquid Source +block.powervoid.name=Power Void +block.powerinfinite.name=Power Infinite +block.unloader.name=Unloader +block.sortedunloader.name=Sorted Unloader +block.vault.name=Vault +block.wave.name=Wave +block.swarmer.name=Swarmer +block.salvo.name=Salvo +block.ripple.name=Ripple +block.phase-conveyor.name=Phase Conveyor +block.bridge-conveyor.name=Bridge Conveyor +block.plastanium-compressor.name=Plastanium Compressor +block.pyratite-mixer.name=Pyratite Mixer +block.blast-mixer.name=Blast Mixer +block.solidifer.name=Solidifer +block.solar-panel.name=Solar Panel +block.solar-panel-large.name=Large Solar Panel +block.oil-extractor.name=Oil Extractor +block.javelin-ship-factory.name=Javelin Ship factory +block.drone-factory.name=Drone Factory +block.fabricator-factory.name=Fabricator Factory +block.repair-point.name=Repair Point +block.resupply-point.name=Resupply Point +block.pulse-conduit.name=Pulse Conduit +block.phase-conduit.name=Phase Conduit +block.liquid-router.name=Liquid Router +block.liquid-tank.name=Liquid Tank +block.liquid-junction.name=Liquid Junction +block.bridge-conduit.name=Bridge Conduit +block.rotary-pump.name=Rotary Pump +block.nuclear-reactor.name=Nuclear Reactor diff --git a/core/assets/bundles/bundle_tk.properties b/core/assets/bundles/bundle_tk.properties index 814f36c6e0..493db23aa0 100644 --- a/core/assets/bundles/bundle_tk.properties +++ b/core/assets/bundles/bundle_tk.properties @@ -1,551 +1,515 @@ -text.about = [ROYAL] Anuken tarafından oluşturuldu [] - [SKY] anukendev@gmail.com [] Aslen [turuncu] GDL [] Metal Monstrosity Jam. Kredi: - [SARI] ile yapılan SFX bfxr [] - [YEŞİL] RoccoW tarafından yapılan müzik [] / [kireç] bulunan FreeMusicArchive.org [] Özel teşekkürler: - [mercan] MitchellFJN []: Kapsamlı oyun testi ve geri bildirim - [sky] Luxray5474 []: wiki çalışması, kod katkıları - [kireç] Epowerj []: kod sistemi yapılandırması, icon - itch.io ve Google Play'deki tüm beta test kullanıcıları\n -text.credits = Yapımcılar -text.discord = Mindustry Discord'una katılın! -text.changes = [SCARLET] Dikkat! [] Bazı önemli oyun mekanikleri değişti. - [Aksanın] teletaşıyıcı [] şimdi artık gücü kullanıyor. - [accent] dökümcü [] ve [accent] pota [] artık bir maksimum ürün kapasitesine sahip. - [Aksan] pota [] artık yakıt olarak kömür gerektiriyor. -text.link.discord.description = Resmi Mindustry Discord iletişim kanalı -text.link.github.description = Oyunun kaynak kodu -text.link.dev-builds.description = Geliştirme altında olan sürüm -text.link.trello.description = Planlanan özellikler için resmi Trello Bülteni -text.link.itch.io.description = PC yüklemeleri ve web sürümü ile itch.io sayfası -text.link.google-play.description = Google Play mağaza sayfası -text.link.wiki.description = Resmi Mindustry Wikipedi'si -text.linkfail = Bağlantı açılamadı! URL, yazı tahtanıza kopyalandı. -text.editor.web = Web sürümü editörü desteklemiyor! Editörü kullanmak için oyunu indirin. -text.multiplayer.web = Oyunun bu sürümü çok oyunculuyu desteklemiyor! Tarayıcınızdan çok oyunculu oynamak için, itch.io sayfasındaki \"çok oyunculu web sürümü\" bağlantısını kullanın. -text.gameover = Çekirdek yok edildi. -text.highscore = [SARI] Yeni yüksek puan! -text.lasted = Dalgaya kadar sürdün -text.level.highscore = Yüksek Puan: [accent] {0} -text.level.delete.title = Silmeyi onaylayın -text.level.delete = \"[Orange] {0} \" Haritayı silmek istediğinizden emin misiniz? -text.level.select = Seviye Seç -text.level.mode = Oyun Modu -text.savegame = Oyunu Kaydet -text.loadgame = Oyunu yükle -text.joingame = Oyuna katıl -text.newgame = Yeni Oyun -text.quit = Çık -text.about.button = Hakkında -text.name = Adı: -text.public = Herkese açık -text.players = 1090 oyuncu çevrimiçi -text.server.player.host = Sunucu -text.players.single = {0} Oyuncu Çevrimiçi -text.server.mismatch = Paket hatası: olası istemci / sunucu sürümü uyuşmazlığı. Siz ve ev sahibi Mindustry'nin en son sürümüne sahip olduğunuzdan emin olun! -text.server.closing = [accent] Sunucu kapatılıyor ... -text.server.kicked.kick = Sunucudan kovuldun! -text.server.kicked.invalidPassword = Geçersiz şifre! -text.server.kicked.clientOutdated = Oyun sürümünüz geçerli değil. Oyununu güncelleyin! -text.server.kicked.serverOutdated = Eski sunucu! Ev sahibinden güncellemesini isteyin! -text.server.kicked.banned = Bu sunucudan yasaklandınız. -text.server.kicked.recentKick = Son zamanlarda tekmelendin. Tekrar bağlanmadan önce bekleyin. -text.server.connected = {0} katıldı. -text.server.disconnected = {0} bağlantısı kesildi. -text.nohost = Özel bir haritada sunucuyu barındıramıyor! -text.host.info = [Vurgu] ana bilgisayarı [] düğmesi, [657] [65] [65] ve [65] [6568] bağlantı noktalarında bir sunucuyu barındırır. [] Aynı [LIGHT_GRAY] wifi veya yerel ağ [] üzerindeki herkes sunucunuzu sunucularında görebilir. liste. Kişilerin IP tarafından herhangi bir yerden bağlanabilmesini istiyorsanız [vurgu] bağlantı noktası iletme [] gereklidir. [LIGHT_GRAY] Not: Birisi LAN oyununuza bağlanırken sorun yaşıyorsa, güvenlik duvarı ayarlarınızda Mindustry'e yerel ağınıza erişebildiğinizden emin olun. -text.join.info = Burada, bağlanmak için yerel ağ [] sunucularına bağlanmak ya da [aksan] sunucularını bulmak için bir [vurgu] sunucunun IP [] girebilirsiniz. Hem LAN hem de WAN çok oyunculu desteklenir. [LIGHT_GRAY] Not: Otomatik bir global sunucu listesi yoktur; Birisine IP ile bağlanmak isterseniz, ana bilgisayardan kendi IP adreslerini sormanız gerekir. -text.hostserver = Oyunu Sun -text.host = evsahibi -text.hosting = [accent] Sunucu açılıyor ... -text.hosts.refresh = Yenile -text.hosts.discovering = LAN oyunlarını keşfetme -text.server.refreshing = Canlandırıcı sunucu -text.hosts.none = [lightgray] Hayır LAN oyunları bulundu! -text.host.invalid = [scarlet] Ana bilgisayara bağlanılamıyor. -text.server.friendlyfire = Dost ateşi -text.trace = Oyuncuyu Takip Et -text.trace.playername = Oyuncu adı: [accent] {0} -text.trace.ip = IP: [vurgu] {0} -text.trace.id = Benzersiz kimlik: [accent] {0} -text.trace.android = Android : [accent] {0} -text.trace.modclient = Özel Alıcı: [accent] {0} -text.trace.totalblocksbroken = Toplam kırık blok: [accent] {0} -text.trace.structureblocksbroken = Kırılan yapı blokları: [accent] {0} -text.trace.lastblockbroken = Kırılan son blok: [accent] {0} -text.trace.totalblocksplaced = Toplam blok yerleştirildi: [accent] {0} -text.trace.lastblockplaced = Konulan son blok: [accent] {0} -text.invalidid = Geçersiz alıcı kimliği! Bir hata raporu gönderin. -text.server.bans = yasaklar -text.server.bans.none = Yasaklanmış oyuncu bulunamadı! -text.server.admins = Yöneticiler -text.server.admins.none = Yönetici bulunamadı! -text.server.add = Sunucu ekle -text.server.delete = Bu sunucuyu silmek istediğinizden emin misiniz? -text.server.hostname = Sun -text.server.edit = Sunucuyu Düzenle -text.server.outdated = [crimson] Eski Sunucu! -text.server.outdated.client = [crimson] Eski Alıcı! -text.server.version = [lightgray] Sürüm: {0} -text.server.custombuild = [sarı] Özel Yapım -text.confirmban = Bu oyuncuyu yasaklamak istediğinizden emin misiniz? -text.confirmunban = Bu oyuncunun yasağını kaldırmak istediğinden emin misin? -text.confirmadmin = Bu oyuncunun yönetici yapmak istediğinden emin misin? -text.confirmunadmin = Bu oyuncudan yönetici durumunu kaldırmak istediğinizden emin misiniz? -text.joingame.byip = IP ile Katılın ... -text.joingame.title = Oyuna katılmak -text.joingame.ip = IP: -text.disconnect = Bağlantı Kesildi -text.disconnect.data = Dünya verileri yüklenemedi! -text.connecting = [Vurgu] bağlanıyor ... -text.connecting.data = [accent] Dünya verileri yükleniyor ... -text.connectfail = [crimson] Sunucuya bağlanılamadı: [orange] {0} -text.server.port = Liman -text.server.addressinuse = Adres çoktan kullanımda! -text.server.invalidport = Bağlantı noktası numarası geçersiz. -text.server.error = [crimson] Sunucu barındırma hatası: [orange] {0} -text.tutorial.back = <Önceki -text.tutorial.next = İleri > -text.save.new = 6349,Yeni Kayıt -text.save.overwrite = Bu kayıt yuvasının üzerine yazmak istediğinizden emin misiniz? -text.overwrite = Üzerine Yaz -text.save.none = Hiçbir kayıt bulunamadı! -text.saveload = [Vurgu] Kaydediliyor ... -text.savefail = Oyun kaydedilemedi! -text.save.delete.confirm = Bu kaydı silmek istediğinizden emin misiniz? -text.save.delete = Sil -text.save.export = Dışa Aktar -text.save.import.invalid = [turuncu] Bu kayıt geçersiz! -text.save.import.fail = [crimson] Kayıt oyuna aktarılamadı : [orange] {0} -text.save.export.fail = [crimson] Kayıt dışa aktarılamadı: [orange] {0} -text.save.import = İçe Aktar -text.save.newslot = İsmi kaydet: -text.save.rename = Yeniden Adlandır -text.save.rename.text = Yeni İsim: -text.selectslot = Bir kayıt seçin. -text.slot = [accent] Yuva {0} -text.save.corrupted = [orange] Kayıt dosyası bozuk veya geçersiz! -text.empty = -text.on = Açık -text.off = Kapalı -text.save.autosave = Otomatik kaydetme: {0} -text.save.map = harita -text.save.wave = Dalga -text.save.difficulty = zorluk -text.save.date = Son Kaydedilen: {0} -text.confirm = Onayla -text.delete = Sil -text.ok = Tamam -text.open = Açık -text.cancel = İptal -text.openlink = Linki aç -text.copylink = Bağlantıyı kopyala -text.back = Geri -text.quit.confirm = Çıkmak istediğinden emin misin? -text.changelog.title = Değişiklik listesi -text.changelog.loading = Değişiklik listesi yükleniyor -text.changelog.error.android = [turuncu] Android'da olan hata nedeniyle değişiklik listesi görüntülenemiyor. -text.changelog.error = [scarlet] Değişiklik listesi alma hatası! İnternet bağlantınızı kontrol edin. -text.changelog.current = [sarı] [[Güncel versiyon] -text.changelog.latest = [turuncu] [[Son sürüm] -text.loading = [Vurgu] Yükleniyor ... -text.wave = [turuncu] Dalga {0} -text.wave.waiting = {0} içinde dalga -text.waiting = Bekleniyor -text.enemies = {0} Düşmanlar -text.enemies.single = {0} Düşman -text.loadimage = Resmi yükle -text.saveimage = Resmi Kaydet -text.oregen = Maden Üretimi -text.editor.badsize = [orange] Resim boyutları geçersiz! [] Geçerli harita boyutları: {0} -text.editor.errorimageload = Resim dosyası yüklenirken hata oluştu: [orange] {0} -text.editor.errorimagesave = Resim dosyası kaydedilirken hata oluştu: [orange] {0} -text.editor.generate = Üretmek -text.editor.resize = Yeniden Boyutlandırma -text.editor.loadmap = Harita Yükle -text.editor.savemap = Harita Kaydet -text.editor.loadimage = Resmi yükle -text.editor.saveimage = Resmi Kaydet -text.editor.unsaved = [scarlet] Kaydedilmemiş değişiklikleriniz var! [] Çıkmak istediğinizden emin misiniz? -text.editor.brushsize = Fırça boyutu: {0} -text.editor.noplayerspawn = Bu haritanın oyuncu spawnpoint'i yok! -text.editor.manyplayerspawns = Haritalar, birden fazla oyuncu spawnpoint'e sahip olamaz! -text.editor.manyenemyspawns = {0} düşman spawnpoint {0}'den daha fazlası olamaz! -text.editor.resizemap = Haritayı Yeniden Boyutlandır -text.editor.resizebig = [Kızıl] Uyarı! [] 256'dan büyük haritalar yavaş ve dengesiz olabilir. -text.editor.mapname = Harita Adı -text.editor.overwrite = [Vurgu] Uyarı! Bu mevcut bir haritanın üzerine yazar. -text.editor.failoverwrite = [crimson] Varsayılan haritanın üzerine yazılamıyor! -text.editor.selectmap = Yüklenecek bir harita seçin: -text.width = Genişliği: -text.height = Boy: -text.randomize = Rasgele seçmek -text.apply = Uygula -text.update = Güncelle -text.menu = Menü -text.play = Oyna -text.load = Yükle -text.save = Kaydet -text.language.restart = Lütfen dil ayarlarının etkili olması için oyununuzu yeniden başlatın. -text.settings.language = Dil -text.settings = Ayarlar -text.tutorial = Eğitim -text.editor = Editör -text.mapeditor = Harita Editörü -text.donate = Bağışlamak -text.settings.reset = Varsayılanlara Dön -text.settings.controls = kontroller -text.settings.game = Oyun -text.settings.sound = Ses -text.settings.graphics = Grafik -text.upgrades = Geliştirmeler -text.purchased = [KİREÇ] Yap၊ld၊ -text.weapons = Silahlar -text.paused = Duraklatıldı -text.respawn = Saniye içinde yeniden doğacaksınız. -text.info.title = [Vurgu] Bilgi -text.error.title = [crimson] Bir hata oluştu -text.error.crashmessage = [SCARLET] Bir kilitlenme meydana getiren beklenmeyen bir hata oluştu. [] Lütfen geliştiriciye bu hatanın gerçekleştiği koşulları bildirin: [ORANGE] anukendev@gmail.com [] -text.error.crashtitle = Bir hata oluştu -text.mode.break = Ara verme modu: {0} -text.mode.place = Döşeme modu: {0} -placemode.hold.name = hat -placemode.areadelete.name = alan -placemode.touchdelete.name = dokun -placemode.holddelete.name = tut -placemode.none.name = Yok -placemode.touch.name = dokun -placemode.cursor.name = İmleç -text.blocks.extrainfo = [accent] fazladan blok bilgisi: -text.blocks.blockinfo = Blok Bilgisi -text.blocks.powercapacity = Güç kapasitesi -text.blocks.powershot = Güç / atış -text.blocks.powersecond = Güç / saniye -text.blocks.powerdraindamage = Güç tahliye / hasar -text.blocks.shieldradius = Kalkan Yarıçapı -text.blocks.itemspeedsecond = Ürün Hız / saniye -text.blocks.range = Menzil -text.blocks.size = Boyut -text.blocks.powerliquid = Güç / Sıvı -text.blocks.maxliquidsecond = Maksimum sıvı / saniye -text.blocks.liquidcapacity = Sıvı kapasitesi -text.blocks.liquidsecond = Sıvı / saniye -text.blocks.damageshot = Zarar / atış -text.blocks.ammocapacity = Mermi kapasitesi -text.blocks.ammo = Cephane: -text.blocks.ammoitem = Cephane / öğe -text.blocks.maxitemssecond = Maksimum öğe / saniye -text.blocks.powerrange = Güç aralığı -text.blocks.lasertilerange = Lazer karo aralığı -text.blocks.capacity = Kapasite -text.blocks.itemcapacity = Ürün kapasitesi -text.blocks.maxpowergenerationsecond = Maksimum Güç Üretimi / saniye -text.blocks.powergenerationsecond = Güç Üretimi / saniye -text.blocks.generationsecondsitem = Nesil Saniye / öğe -text.blocks.input = giriş -text.blocks.inputliquid = Giriş sıvı -text.blocks.inputitem = Giriş öğesi -text.blocks.output = Çıktı -text.blocks.secondsitem = Saniye / öğe -text.blocks.maxpowertransfersecond = Maksimum güç aktarımı / saniye -text.blocks.explosive = Çok patlayıcı! -text.blocks.repairssecond = Tamir / saniye -text.blocks.health = Can -text.blocks.inaccuracy = yanlışlık -text.blocks.shots = atışlar -text.blocks.shotssecond = Çekim / saniye -text.blocks.fuel = Yakıt -text.blocks.fuelduration = Yakıt Süresi -text.blocks.maxoutputsecond = Maksimum çıkış / saniye -text.blocks.inputcapacity = Giriş kapasitesi -text.blocks.outputcapacity = Çıkış kapasitesi -text.blocks.poweritem = Güç / Ürün -text.placemode = Yer Modu -text.breakmode = Mola modu -text.health = sağlık -setting.difficulty.easy = kolay -setting.difficulty.normal = orta -setting.difficulty.hard = zor -setting.difficulty.insane = deli -setting.difficulty.purge = tasfiye -setting.difficulty.name = Zorluk: -setting.screenshake.name = Ekran Sallamak -setting.smoothcam.name = Pürüzsüz kamera -setting.indicators.name = Düşman Göstergeleri -setting.effects.name = Görüntü Efektleri -setting.sensitivity.name = Denetleyici hassasiyeti -setting.saveinterval.name = Otomatik Kaydetme Aralığı -setting.seconds = saniye -setting.fullscreen.name = Tam ekran -setting.multithread.name = Çok iş parçacığı -setting.fps.name = Saniyede ... Kare göstermek -setting.vsync.name = VSync -setting.lasers.name = Güç Lazerleri Göster -setting.healthbars.name = Varlık Sağlık çubuklarını göster -setting.pixelate.name = Piksel Ekran -setting.musicvol.name = Müzik sesi -setting.mutemusic.name = Müziği Kapat -setting.sfxvol.name = SFX Hacmi -setting.mutesound.name = Sesi kapat -map.maze.name = Labirent -map.fortress.name = Kale -map.sinkhole.name = düden -map.caves.name = mağaralar -map.volcano.name = volkan -map.caldera.name = kaldera -map.scorch.name = alazlamak -map.desert.name = çöl -map.island.name = ada -map.grassland.name = Çayır -map.tundra.name = tundra -map.spiral.name = sarmal -map.tutorial.name = Eğitim -tutorial.intro.text = [sarı] Eğiticiye hoşgeldiniz. [] Başlamak için 'ileri' ye basın. -tutorial.moveDesktop.text = Taşımak için [turuncu] [[WASD] [] tuşlarını kullanın. Destek için [turuncu] shift [] tuşunu basılı tutun. Yakınlaştırmak veya uzaklaştırmak için [turuncu] kaydırma tekerini [] kullanırken [turuncu] CTRL [] tuşunu basılı tutun. -tutorial.shoot.text = Hedeflemek için farenizi kullanın, [turuncu] sol fare tuşunu [] vurun. [Sarı] hedef [] üzerinde çalışmayı deneyin. -tutorial.moveAndroid.text = Görünümü kaydırmak için, bir parmağınızı ekran boyunca sürükleyin. Yakınlaştırmak veya uzaklaştırmak için sıkıştırın ve sürükleyin. -tutorial.placeSelect.text = Sağ alttaki blok menüsünden [sarı] bir konveyör [] seçmeyi deneyin. -tutorial.placeConveyorDesktop.text = [Turuncu] [[scrollwheel] [] tuşunu kullanarak konveyörü [turuncu] ileriye [] getirin ve [turuncu] [[sol fare tuşu] [] düğmesini kullanarak [sarı] işaretli konuma [] yerleştirin. -tutorial.placeConveyorAndroid.text = [Turuncu] [[döndürme düğmesi] [] düğmesini kullanarak konveyörü [turuncu] ileriye [] doğru döndürün, bir parmağınızla konumuna sürükleyin, ardından [turuncu] kullanarak [sarı] işaretli konuma [] yerleştirin [[onay işareti][]. -tutorial.placeConveyorAndroidInfo.text = Alternatif olarak, [turuncu] [[dokunma modu] [] moduna geçmek için sol alt taraftaki artı simgesini ve ekrana dokunarak blokları yerleştirebilirsiniz. Dokunmatik modda, bloklar soldaki ok ile döndürülebilir. Denemek için [sarı] sonraki [] tuşuna basın. -tutorial.placeDrill.text = Şimdi, işaretlenmiş konuma bir [sarı] taş matkap [] seçin ve yerleştirin. -tutorial.blockInfo.text = Bir blok hakkında daha fazla bilgi edinmek isterseniz, açıklamayı okumak için sağ üstteki [turuncu] soru işaretine [] dokunabilirsiniz. -tutorial.deselectDesktop.text = [Turuncu] [[sağ fare tuşu] [] kullanarak bir bloğu kaldırabilirsiniz. -tutorial.deselectAndroid.text = [Turuncu] X [] düğmesine basarak bir bloğun seçimini kaldırabilirsiniz. -tutorial.drillPlaced.text = Matkap şimdi [sarı] taş üretecek, [] konveyör üzerine çıkacak, daha sonra [sarı] çekirdeğe [] hareket ettirilecektir. -tutorial.drillInfo.text = Farklı cevherlerin farklı matkaplara ihtiyacı vardır. Taş taş matkaplar gerektirir, demir demir matkaplar gerektirir, vb. -tutorial.drillPlaced2.text = Öğeleri çekirdeğe taşımak, onları sol üstteki [sarı] öğe envanterinize [] yerleştirir. Yerleştirme blokları, envanterinizdeki öğeleri kullanır. -tutorial.moreDrills.text = Birçok matkap ve konveyörü birbirine bağlayabilirsiniz. -tutorial.deleteBlock.text = Silmek istediğiniz blokta [turuncu] sağ fare düğmesine [] tıklayarak blokları silebilirsiniz. Bu konveyörü silmeyi deneyin. -tutorial.deleteBlockAndroid.text = Alt soldaki [turuncu] kesme modu menüsünde [] artı işaretini [] seçerek ve bir bloka dokunarak blokları [turuncu] ile silebilirsiniz. Bu konveyörü silmeyi deneyin. -tutorial.placeTurret.text = Şimdi, [sarı] işaretli konuma [] [sarı] bir taret [] seçin ve yerleştirin. -tutorial.placedTurretAmmo.text = Bu taret artık konveyör [sarı] mermiyi [] kabul edecektir. Üzerinde gezdirerek ne kadar cephane olduğunu ve yeşil renkli [[yeşil] çubuğunu [] kontrol ederek görebilirsiniz. -tutorial.turretExplanation.text = Taretler, yeterli mermiye sahip oldukları sürece otomatik olarak en yakın düşmana ateş ederler. -tutorial.waves.text = Her [sarı] 60 [] saniyede, [mercan] düşmanlardan oluşan bir dalga [] belirli yerlerde doğacak ve çekirdeği yok etmeye çalışacaktır. -tutorial.coreDestruction.text = Hedefiniz [sarı] çekirdeği [] savunmaktır. Çekirdek yok edilirse, [mercan] oyunu kaybedersiniz []. -tutorial.pausingDesktop.text = Bir ara vermeniz gerekiyorsa, oyunu duraklatmak için sol üstteki [turuncu] duraklat [] düğmesine veya [turuncu] boşluk [] tuşuna basın. Duraklatılırken blokları seçebilir ve yerleştirebilirsiniz, ancak hareket edemez veya ateş edemezsiniz. -tutorial.pausingAndroid.text = Bir ara vermeniz gerekirse, oyunu duraklatmak için sol üstteki [turuncu] duraklatma düğmesine [] basın. Duraklatılırken hala blokları kırıp yerleştirebilirsiniz. -tutorial.purchaseWeapons.text = Alt soldaki yükseltme menüsünü açarak, makineniz için yeni [sarı] silahlar [] satın alabilirsiniz. -tutorial.switchWeapons.text = Silahları, sol alt taraftaki simgesini tıklayarak veya sayıları [turuncu] [[1-9] [] kullanarak değiştirebilirsiniz. -tutorial.spawnWave.text = İşte şimdi bir dalga geliyor. Onları yok et. -tutorial.pumpDesc.text = Daha sonraki dalgalarda, jeneratörler veya aspiratörler için sıvı dağıtmak için [sarı] pompaları [] kullanmanız gerekebilir. -tutorial.pumpPlace.text = Pompalar, matkaplar yerine benzer şekilde çalışırlar; [Sarı] belirlenmiş yağa [] bir pompa yerleştirmeyi deneyin. -tutorial.conduitUse.text = Şimdi pompadan önde giden bir [turuncu] kablo kanalı [] yerleştirin. -tutorial.conduitUse2.text = Ve birkaç tane daha ... -tutorial.conduitUse3.text = Ve birkaç tane daha ... -tutorial.generator.text = Şimdi, kanalın ucunda bir [turuncu] yanma jeneratörü [] bloğu yerleştirin. -tutorial.generatorExplain.text = Bu jeneratör şimdi yağdan [sarı] güç [] oluşturacaktır. -tutorial.lasers.text = Güç [sarı] güç lazerleri [] kullanılarak dağıtılır. Döndür ve buraya bir tane yerleştir. -tutorial.laserExplain.text = Jeneratör şimdi gücü lazer bloğuna taşıyacaktır. Bir [sarı] opak [] ışını, şu anda gücü iletmekte olduğu anlamına gelir ve [sarı] saydam [] ışını, bunun olmadığı anlamına gelir. -tutorial.laserMore.text = Bir bloğun üzerine geldiğinde ne kadar gç olduğunu ve üst taraftaki [sarı] sarı çubuğu [] kontrol ederek kontrol edebilirsiniz. -tutorial.healingTurret.text = Bu lazer bir [kireç] onarım tareti [] için kullanılabilir. Bir tane buraya yerleştirin. -tutorial.healingTurretExplain.text = Gücü olduğu sürece, bu taret yakındaki blokları tamir eder. [] en yakın zamanda bu bloku temin edin! -tutorial.smeltery.text = Pek çok blok, [turuncu] yapılabilmesi için çelik gerektirir ve bu da [turuncu] bir dökümcünün [] yapılmasını gerektirir. Bir tane buraya yerleştirin. -tutorial.smelterySetup.text = Bu dökümcü kömürü yakıt olarak kullanarak, demirden [turuncu] çelik [] üretecek. -tutorial.tunnelExplain.text = Ayrıca, eşyaların bir [turuncu] tünel bloğundan [] geçtiğini ve taş bloktan geçerek diğer tarafta ortaya çıktığını unutmayın. Tünellerin yalnızca 2 bloğa kadar gidebileceğini unutmayın. -tutorial.end.text = Ve bu dersi bitirir! İyi şanslar! -text.keybind.title = Tuşları yeniden ayarla -keybind.move_x.name = sağ / sol -keybind.move_y.name = yukarı / aşağı -keybind.select.name = seçmek -keybind.break.name = kırmak -keybind.shoot.name = ateş etme -keybind.zoom_hold.name = tut ve büyüt -keybind.zoom.name = Yakınlaştır -keybind.block_info.name = blok bilgisi -keybind.menu.name = menü -keybind.pause.name = duraklatma -keybind.dash.name = tire -keybind.chat.name = Sohbet -keybind.player_list.name = oyuncu listesi -keybind.console.name = KONTROL MASASI -keybind.rotate_alt.name = rotate_alt -keybind.rotate.name = Döndür -keybind.weapon_1.name = weapon_1 -keybind.weapon_2.name = weapon_2 -keybind.weapon_3.name = weapon_3 -keybind.weapon_4.name = weapon_4 -keybind.weapon_5.name = weapon_5 -keybind.weapon_6.name = weapon_6 -mode.text.help.title = Modların açıklaması -mode.waves.name = dalgalar -mode.waves.description = normal mod. sınırlı kaynaklar ve otomatik gelen dalgalar. -mode.sandbox.name = Limitsiz Oynama -mode.sandbox.description = sonsuz kaynaklar ve dalgalar için zamanlayıcı yok. -mode.freebuild.name = Özgür Oynama -mode.freebuild.description = sınırlı kaynaklar ve dalgalar için zamanlayıcı yok. -upgrade.standard.name = standart -upgrade.standard.description = Standart mech. -upgrade.blaster.name = blaster -upgrade.blaster.description = Yavaş, zayıf bir mermi ateş eder. -upgrade.triblaster.name = triblaster -upgrade.triblaster.description = Bir yayında 3 mermi ateş eder. -upgrade.clustergun.name = clustergun -upgrade.clustergun.description = Yayılan bombalar ateş eder. -upgrade.beam.name = lazer -upgrade.beam.description = Uzun menzilli bir delici lazer ışını atar. -upgrade.vulcan.name = Vulkan -upgrade.vulcan.description = Hızlı mermiler ateş eder. -upgrade.shockgun.name = shockgun -upgrade.shockgun.description = Yıkıcı ve patlayıcı mermiler savurarak ateş eder. -item.stone.name = taş -item.iron.name = Demir -item.coal.name = kömür -item.steel.name = çelik -item.titanium.name = titanyum -item.dirium.name = dirium -item.uranium.name = uranyum -item.sand.name = kum -liquid.water.name = su -liquid.plasma.name = plazma -liquid.lava.name = lav -liquid.oil.name = petrol -block.weaponfactory.name = silah fabrikası -block.weaponfactory.fulldescription = Oyuncu mech için silah oluşturmak için kullanılır. Kullanmak için tıklayın. Kaynaklarını otomatik olarak çekirdekten alır. -block.air.name = hava -block.blockpart.name = blokparçası -block.deepwater.name = derin su -block.water.name = su -block.lava.name = lav -block.oil.name = petrol -block.stone.name = taş -block.blackstone.name = siyah taş -block.iron.name = Demir -block.coal.name = kömür -block.titanium.name = titanyum -block.uranium.name = uranyum -block.dirt.name = toprak -block.sand.name = kum -block.ice.name = buz -block.snow.name = kar -block.grass.name = Otlar -block.sandblock.name = kumbloku -block.snowblock.name = karbloku -block.stoneblock.name = taşbloku -block.blackstoneblock.name = blackstoneblock -block.grassblock.name = grassblock -block.mossblock.name = mossblock -block.shrub.name = çalı -block.rock.name = Kaya -block.icerock.name = ICEROCK -block.blackrock.name = Siyah Kaya -block.dirtblock.name = dirtblock -block.stonewall.name = taş duvar -block.stonewall.fulldescription = Ucuz bir savunma bloğu. İlk birkaç dalgada çekirdeği ve tareti korumak için kullanışlıdır. -block.ironwall.name = Demir duvar -block.ironwall.fulldescription = Temel bir savunma bloğu. Düşmanlardan korunma sağlar. Taş duvardan daha korunaklıdır. -block.steelwall.name = Çelik duvar -block.steelwall.fulldescription = Standart bir savunma bloğu. düşmanlardan korunma sağlar -block.titaniumwall.name = titanyum duvar -block.titaniumwall.fulldescription = Güçlü bir savunma bloğu. Düşmanlardan korunma sağlar. -block.duriumwall.name = dirium duvar -block.duriumwall.fulldescription = Çok güçlü bir savunma bloğu. Düşmanlardan korunma sağlar. -block.compositewall.name = kompozit duvar -block.steelwall-large.name = büyük çelik duvar -block.steelwall-large.fulldescription = Standart bir savunma bloğu. Birden fazla fayansa yayılır. -block.titaniumwall-large.name = büyük titanyum duvar -block.titaniumwall-large.fulldescription = Güçlü bir savunma bloğu. Birden fazla fayans yayılır. -block.duriumwall-large.name = büyük dirsek duvarı -block.duriumwall-large.fulldescription = Çok güçlü bir savunma bloğu. Birden fazla fayans yayılır. -block.titaniumshieldwall.name = korumalı duvar -block.titaniumshieldwall.fulldescription = Ekstra yerleşik bir kalkan ile güçlü bir savunma bloğu. Düşman mermilerini emmek için enerji kullanır. Bu bloğa enerji sağlamak için güç arttırıcıların kullanılması tavsiye edilir. -block.repairturret.name = onarım tareti -block.repairturret.fulldescription = Yakındaki hasarlı blokları yavaş bir hızda tamir eder. Küçük menzili vardır. Az miktarlarda güç kullanır. -block.megarepairturret.name = onarım tareti II -block.megarepairturret.fulldescription = Yakındaki hasarlı blokları tamir eder. Uygun menzillidir. Gücü kullanır. -block.shieldgenerator.name = kalkan üreteci -block.shieldgenerator.fulldescription = Gelişmiş bir savunma bloğu. Bir yarıçaptaki tüm blokları saldırıya karşı korur. Boştayken gücü yavaş bir hızda kullanır, ancak mermi temasında enerjiyi hızla boşaltır. -block.door.name = kapı -block.door.fulldescription = Dokunarak açılıp kapatılabilen bir blok. -block.door-large.name = büyük kapı -block.door-large.fulldescription = Dokunarak açılıp kapatılabilen bir blok. -block.conduit.name = sıvı borusu -block.conduit.fulldescription = Temel sıvı taşıma bloğu. Bir konveyör gibi çalışır, ancak sıvılar ile. pompa veya diğer borular ile kullanılır. Düşmanlar ve oyuncular için sıvılar üzerinde bir köprü olarak kullanılabilir. -block.pulseconduit.name = hızlı sıvı borusu -block.pulseconduit.fulldescription = Gelişmiş sıvı taşıma bloku. Sıvıları daha hızlı taşır ve standart sıvı taşıma borularından daha fazla sıvı depolar. -block.liquidrouter.name = sıvı yönlendirici -block.liquidrouter.fulldescription = Bir yönlendiriciye benzer şekilde çalışır. Bir taraftan sıvı girişi kabul eder ve diğer tarafa gönderir. Tek bir borudan diğer birçok boruyla sıvı paylaşmak için kullanışlıdır. -block.conveyor.name = konveyör -block.conveyor.fulldescription = En temel madde taşıma bloğu. Öğeleri konulduğu yöne göre maddeleri ileriye taşır ve bunları otomatik olarak taretlere ya da üretici bloklara getirir. konulmadan önce Döndürülebilirler, ancak konulduktan sonra Döndürülemezler. Düşmanlar ve oyuncular için sıvılar üzerinde bir köprü olarak kullanılabilir. -block.steelconveyor.name = çelik konveyör -block.steelconveyor.fulldescription = Gelişmiş madde taşıma bloğu. Öğeleri standart konveyörlerden daha hızlı taşır. -block.poweredconveyor.name = hızlı konveyör -block.poweredconveyor.fulldescription = Nihai ürün taşıma bloğu. Öğeleri çelik konveyörlerden daha hızlı taşır. -block.router.name = yönlendirici -block.router.fulldescription = Öğeleri bir yönden kabul eder ve 3 farklı yöne gönderir. Malzemelerin belirli bir miktarını da depolayabilir. Malzemelerin bir matkaptan çoklu taretlere ayrılması için uygundur. -block.junction.name = Kavşak noktası -block.junction.fulldescription = İki çapraz şekilde geçmeye çalışan konveyör bandı için köprü görevi görür. Farklı yerlere farklı malzemeler taşıyan konveyör olduğu durumlarda kullanışlıdır. -block.conveyortunnel.name = konveyör tüneli -block.conveyortunnel.fulldescription = Maddeleri blokların altından geçirmek için kullanılır. Kullanmak için, altına tünel yapılacak bloğun bir tarafta giriş tüneli ve diğer tarafa çıkış tüneli yerleştirin. Her iki tünelin de giriş veya çıkış yapan bloklara doğru zıt yönlere baktığından emin olun. -block.liquidjunction.name = sıvı bağlantı -block.liquidjunction.fulldescription = İki çaprazdan geçen boru için köprü görevi görür. Farklı yerlere farklı sıvılar taşıyan kanalların olduğu durumlarda kullanışlıdır. -block.liquiditemjunction.name = sıvı madde kavşağı -block.liquiditemjunction.fulldescription = Kanalları ve konveyörleri yan yana geçirmek için bir köprü görevi görür. -block.powerbooster.name = güç yükseltici -block.powerbooster.fulldescription = Gücü kendi yarıçapı içindeki tüm bloklara dağıtır. -block.powerlaser.name = güç lazeri -block.powerlaser.fulldescription = Önündeki bloğa güç ileten bir lazer oluşturur. Herhangi bir güç üretmez. En iyi jeneratörler veya diğer lazerler ile kullanılır. -block.powerlaserrouter.name = lazer yönlendirici -block.powerlaserrouter.fulldescription = Bir kerede gücü üç yöne dağıtan lazer. Bir jeneratörden birçok bloka güç verilmesi gereken durumlarda kullanışlıdır. -block.powerlasercorner.name = lazer köşesi -block.powerlasercorner.fulldescription = Bir kerede gücü iki yöne dağıtan lazer. Bir jeneratörden birçok bloka güç verilmesi gereken durumlarda ve bir yönlendiricinin kesin olmadığı durumlarda kullanışlıdır. -block.teleporter.name = teletaşıyıcı -block.teleporter.fulldescription = Gelişmiş madde taşıma bloğu. tele-taşıyıcı, öğeleri aynı renkte olan bir teletaşıyıcıya yönlendirir. Aynı renkte teletaşıyıcı yoksa, hiçbir şey yapmaz. Aynı renkten birden çok tele-yazıcı varsa, rastgele biri seçilir. Gücü kullanır. Rengi değiştirmek için dokunun. Not: Sadece madde ileten teletaşıyıcılar gücü kullanır. -block.sorter.name = ayrıştırıcı -block.sorter.fulldescription = Malzemeleri türüne göre ayrıştırır. Kabul edilecek malzeme bloktaki renkle gösterilir. Doğru materyal ile eşleşen tüm öğeler ileriye doğru çıkar, diğer her şey sol ve sağ taraflardan çıkar. -block.core.name = çekirdek -block.pump.name = pompa -block.pump.fulldescription = Kaynak bloğundan su, lav veya yağ gibi sıvıları pompalar. Yakındaki kanallara sıvıyı aktarır. -block.fluxpump.name = fluxpump -block.fluxpump.fulldescription = Pompanın gelişmiş bir versiyonu. Sıvıyı daha hızlı pompalar ve daha fazla sıvı depolar. -block.smelter.name = dökümcü -block.smelter.fulldescription = Temel üretim bloğu. 1 demir ve 1 kömür yakıt olarak verildiğinde, demir çıkarır. Tıkanmayı önlemek için farklı konveyörlerden demir ve kömürün kullanılması tavsiye edilir. -block.crucible.name = pota -block.crucible.fulldescription = Gelişmiş bir üretim bloğu. 1 titanyum, 1 çelik ve 1 kömür yakıt olarak girildiğinde, dirium çıkarır. Tıkanmayı önlemek için farklı konveyörlerden kömür, çelik ve titanyum kullanılması tavsiye edilir. -block.coalpurifier.name = kömür çıkarıcı -block.coalpurifier.fulldescription = Temel bir ekstraktör bloğu. Çok miktarda su ve taş ile birlikte tedarik edildiğinde kömür çıkarır. -block.titaniumpurifier.name = titanyum çıkarıcı -block.titaniumpurifier.fulldescription = Standart bir ekstraktör bloğu. Çok miktarda su ve demir ile birlikte verildiğinde titanyum çıkarır. -block.oilrefinery.name = yağ rafinerisi -block.oilrefinery.fulldescription = Büyük miktarda yağı kömür parçalarına ayırır. Kömür damarları kıt olduğunda kömür bazlı taretlerin yakıtı için kullanışlıdır. -block.stoneformer.name = taş biçimlendiricisi -block.stoneformer.fulldescription = Lavı taş haline getirir. Muazzam miktarda taş üretmek için kullanışlıdır. -block.lavasmelter.name = lav dökümcüsü -block.lavasmelter.fulldescription = Demiri çeliğe dönüştürmek için lav kullanır. Dökümcüler için bir alternatif. Kömürün az olduğu durumlarda kullanışlıdır -block.stonedrill.name = taş matkap -block.stonedrill.fulldescription = Temel bir matkap. Taş karolara yerleştirildiğinde, süresiz olarak yavaş bir hızda taş çıkarırç -block.irondrill.name = demir matkap -block.irondrill.fulldescription = Temel bir matkap. Demir cevheri çinileri üzerine yerleştirildiğinde, süresiz olarak yavaş bir şekilde demir çıkarıTemel bir matkap. Demir cevheri çinileri üzerine yerleştirildiğinde, süresiz olarak yavaş bir şekilde demir çıkarır.\n. -block.coaldrill.name = kömür matkap -block.coaldrill.fulldescription = Temel bir matkap. Kömür madeninin üzerine yerleştirildiğinde, süresiz olarak yavaş bir şekilde kömür çıkarır. -block.uraniumdrill.name = uranyum matkap -block.uraniumdrill.fulldescription = Gelişmiş bir matkap. Uranyum cevheri üzerine yerleştirildiğinde, uranyumu süresiz olarak yavaş bir hızda çıkarır. -block.titaniumdrill.name = titanyum matkap -block.titaniumdrill.fulldescription = Gelişmiş bir matkap. Titanyum cevherinin üzerine yerleştirildiğinde, sonsuza yavaş bir tempoda titanyum çıkar. -block.omnidrill.name = omnidrill -block.omnidrill.fulldescription = En büyük matkap. Herhangi bir cevherin uzerine yerlestitldiginde hızlı bir hızda cevher çıkarır -block.coalgenerator.name = kömür jeneratörü -block.coalgenerator.fulldescription = Gerekli jeneratör. Kömürden güç üretir. 4 tarafına lazer olarak güç verir. -block.thermalgenerator.name = termik jeneratör -block.thermalgenerator.fulldescription = Lavdan güç üretir. 4 tarafına lazer olarak güç verir. -block.combustiongenerator.name = yanma jeneratörü -block.combustiongenerator.fulldescription = Yağdan güç üretir. 4 tarafına lazer olarak güç verir. -block.rtgenerator.name = RTG jeneratörü -block.rtgenerator.fulldescription = Uranyumun radyoaktif bozunmasından az miktarda güç üretir. 4 tarafına lazer olarak güç verir. -block.nuclearreactor.name = nükleer reaktör -block.nuclearreactor.fulldescription = RTG Jeneratörünün gelişmiş bir versiyonu ve en iyi güç jeneratörüdür. Uranyumdan güç üretir. Su ile soğutulması gerekir. Son derece tehlikelidir; yetersiz miktarda su ile beslenmediğinde şiddetli patlayabilir. -block.turret.name = taret -block.turret.fulldescription = Basit, ucuz bir kule. Cephane için taş kullanır. Çift taretten biraz daha büyük menzillidir. -block.doubleturret.name = çift ​​taret -block.doubleturret.fulldescription = Taretin biraz daha güçlü bir versiyonu. Cephane için taş kullanır. standart tarete nazaran daha fazla hasar verir, ancak daha düşük bir menzile sahiptir. İki mermi ile ateş eder. -block.machineturret.name = gattling tareti -block.machineturret.fulldescription = Demir atan bir taret. Cephane için demir kullanır. İyi bir hasar ile ateş oranına sahiptir. -block.shotgunturret.name = splitter tareti -block.shotgunturret.fulldescription = Standart bir kule. Cephane için demir kullanır. tek atışta 7 mermi yayılır. Düşük menzillidir, ancak Gatling taretinden daha yüksek hasar verir. -block.flameturret.name = alev tareti -block.flameturret.fulldescription = Gelişmiş yakın menzilli taret. Cephane için kömür kullanır. Çok düşük bir menzile sahiptir, ancak çok yüksek hasar verir. Duvarların arkasında kullanılması tavsiye edilir. -block.sniperturret.name = çelik tareti -block.sniperturret.fulldescription = Gelişmiş uzun menzilli taret. Cephane için çelik kullanır. Yüksek hasar verir, ancak düşük ateş hızı vardır. Kullanımı pahalı, ancak yüksek menzili nedeniyle düşmanı uzak mesafelerden vurabilir. -block.mortarturret.name = flak tareti -block.mortarturret.fulldescription = Gelişmiş sıçrama hasarlı tareti. Cephane için kömür kullanır. Patlayan mermi şarapnel saysinde birden fazla düşman vurabilir. büyük düşman topluluklarını yok etmek için kullanışlıdır. -block.laserturret.name = lazer tareti -block.laserturret.fulldescription = Gelişmiş tek hedefli taret. Gücü kullanır. orta menzilli taret. Sadece tek hedefli. Asla ıskalamaz. -block.waveturret.name = tesla tareti -block.waveturret.fulldescription = Gelişmiş birçok hedefli taret. Gücü kullanır. Orta menzilli. Asla ıskalamaz. Düşük ile orta seviyede hasar verir, ancak aynı anda birden fazla düşmana vurabilir. -block.plasmaturret.name = plazma tareti -block.plasmaturret.fulldescription = Alev taretinin gelişmiş versiyonu. kömürü cephane olarak kullanır. Çok yüksek hasar, düşük ila orta menzil. -block.chainturret.name = zincir tareti -block.chainturret.fulldescription = En iyi hızlı ateş tareti. Uranyumu cephane olarak kullanır. Yüksek ateş hızı vardır. Orta menzillidir. Birden fazla blok boyunca yayılır. Son derece dayanıklıdır. -block.titancannon.name = titan topu -block.titancannon.fulldescription = En iyi uzak menzilli taret. Uranyumu cephane olarak kullanır. Orta ateş hızındadır ve top ateşinin sıçrama hasarı büyüktür. Uzun mesafe. Birden fazla blok boyunca yayılır. Son derece dayanıklıdır. -block.playerspawn.name = oyuncudoğuşu -block.enemyspawn.name = dϋşmandoğuşu +text.about=[ROYAL] Anuken tarafından oluşturuldu [] - [SKY] anukendev@gmail.com [] Aslen [turuncu] GDL [] Metal Monstrosity Jam. Kredi: - [SARI] ile yapılan SFX bfxr [] - [YEŞİL] RoccoW tarafından yapılan müzik [] / [kireç] bulunan FreeMusicArchive.org [] Özel teşekkürler: - [mercan] MitchellFJN []: Kapsamlı oyun testi ve geri bildirim - [sky] Luxray5474 []: wiki çalışması, kod katkıları - [kireç] Epowerj []: kod sistemi yapılandırması, icon - itch.io ve Google Play'deki tüm beta test kullanıcıları\n +text.credits=Yapımcılar +text.discord=Mindustry Discord'una katılın! +text.link.discord.description=Resmi Mindustry Discord iletişim kanalı +text.link.github.description=Oyunun kaynak kodu +text.link.dev-builds.description=Geliştirme altında olan sürüm +text.link.trello.description=Planlanan özellikler için resmi Trello Bülteni +text.link.itch.io.description=PC yüklemeleri ve web sürümü ile itch.io sayfası +text.link.google-play.description=Google Play mağaza sayfası +text.link.wiki.description=Resmi Mindustry Wikipedi'si +text.linkfail=Bağlantı açılamadı! URL, yazı tahtanıza kopyalandı. +text.editor.web=Web sürümü editörü desteklemiyor! Editörü kullanmak için oyunu indirin. +text.multiplayer.web=Oyunun bu sürümü çok oyunculuyu desteklemiyor! Tarayıcınızdan çok oyunculu oynamak için, itch.io sayfasındaki "çok oyunculu web sürümü" bağlantısını kullanın. +text.gameover=Çekirdek yok edildi. +text.highscore=[SARI] Yeni yüksek puan! +text.lasted=Dalgaya kadar sürdün +text.level.highscore=Yüksek Puan: [accent] {0} +text.level.delete.title=Silmeyi onaylayın +text.level.select=Seviye Seç +text.level.mode=Oyun Modu +text.savegame=Oyunu Kaydet +text.loadgame=Oyunu yükle +text.joingame=Oyuna katıl +text.newgame=Yeni Oyun +text.quit=Çık +text.about.button=Hakkında +text.name=Adı: +text.public=Herkese açık +text.players=1090 oyuncu çevrimiçi +text.server.player.host=Sunucu +text.players.single={0} Oyuncu Çevrimiçi +text.server.mismatch=Paket hatası: olası istemci / sunucu sürümü uyuşmazlığı. Siz ve ev sahibi Mindustry'nin en son sürümüne sahip olduğunuzdan emin olun! +text.server.closing=[accent] Sunucu kapatılıyor ... +text.server.kicked.kick=Sunucudan kovuldun! +text.server.kicked.invalidPassword=Geçersiz şifre! +text.server.kicked.clientOutdated=Oyun sürümünüz geçerli değil. Oyununu güncelleyin! +text.server.kicked.serverOutdated=Eski sunucu! Ev sahibinden güncellemesini isteyin! +text.server.kicked.banned=Bu sunucudan yasaklandınız. +text.server.kicked.recentKick=Son zamanlarda tekmelendin. Tekrar bağlanmadan önce bekleyin. +text.server.connected={0} katıldı. +text.server.disconnected={0} bağlantısı kesildi. +text.nohost=Özel bir haritada sunucuyu barındıramıyor! +text.host.info=[Vurgu] ana bilgisayarı [] düğmesi, [657] [65] [65] ve [65] [6568] bağlantı noktalarında bir sunucuyu barındırır. [] Aynı [LIGHT_GRAY] wifi veya yerel ağ [] üzerindeki herkes sunucunuzu sunucularında görebilir. liste. Kişilerin IP tarafından herhangi bir yerden bağlanabilmesini istiyorsanız [vurgu] bağlantı noktası iletme [] gereklidir. [LIGHT_GRAY] Not: Birisi LAN oyununuza bağlanırken sorun yaşıyorsa, güvenlik duvarı ayarlarınızda Mindustry'e yerel ağınıza erişebildiğinizden emin olun. +text.join.info=Burada, bağlanmak için yerel ağ [] sunucularına bağlanmak ya da [aksan] sunucularını bulmak için bir [vurgu] sunucunun IP [] girebilirsiniz. Hem LAN hem de WAN çok oyunculu desteklenir. [LIGHT_GRAY] Not: Otomatik bir global sunucu listesi yoktur; Birisine IP ile bağlanmak isterseniz, ana bilgisayardan kendi IP adreslerini sormanız gerekir. +text.hostserver=Oyunu Sun +text.host=evsahibi +text.hosting=[accent] Sunucu açılıyor ... +text.hosts.refresh=Yenile +text.hosts.discovering=LAN oyunlarını keşfetme +text.server.refreshing=Canlandırıcı sunucu +text.hosts.none=[lightgray] Hayır LAN oyunları bulundu! +text.host.invalid=[scarlet] Ana bilgisayara bağlanılamıyor. +text.server.friendlyfire=Dost ateşi +text.trace=Oyuncuyu Takip Et +text.trace.playername=Oyuncu adı: [accent] {0} +text.trace.ip=IP: [vurgu] {0} +text.trace.id=Benzersiz kimlik: [accent] {0} +text.trace.android=Android : [accent] {0} +text.trace.modclient=Özel Alıcı: [accent] {0} +text.trace.totalblocksbroken=Toplam kırık blok: [accent] {0} +text.trace.structureblocksbroken=Kırılan yapı blokları: [accent] {0} +text.trace.lastblockbroken=Kırılan son blok: [accent] {0} +text.trace.totalblocksplaced=Toplam blok yerleştirildi: [accent] {0} +text.trace.lastblockplaced=Konulan son blok: [accent] {0} +text.invalidid=Geçersiz alıcı kimliği! Bir hata raporu gönderin. +text.server.bans=yasaklar +text.server.bans.none=Yasaklanmış oyuncu bulunamadı! +text.server.admins=Yöneticiler +text.server.admins.none=Yönetici bulunamadı! +text.server.add=Sunucu ekle +text.server.delete=Bu sunucuyu silmek istediğinizden emin misiniz? +text.server.hostname=Sun +text.server.edit=Sunucuyu Düzenle +text.server.outdated=[crimson] Eski Sunucu! +text.server.outdated.client=[crimson] Eski Alıcı! +text.server.version=[lightgray] Sürüm: {0} +text.server.custombuild=[sarı] Özel Yapım +text.confirmban=Bu oyuncuyu yasaklamak istediğinizden emin misiniz? +text.confirmunban=Bu oyuncunun yasağını kaldırmak istediğinden emin misin? +text.confirmadmin=Bu oyuncunun yönetici yapmak istediğinden emin misin? +text.confirmunadmin=Bu oyuncudan yönetici durumunu kaldırmak istediğinizden emin misiniz? +text.joingame.byip=IP ile Katılın ... +text.joingame.title=Oyuna katılmak +text.joingame.ip=IP: +text.disconnect=Bağlantı Kesildi +text.disconnect.data=Dünya verileri yüklenemedi! +text.connecting=[Vurgu] bağlanıyor ... +text.connecting.data=[accent] Dünya verileri yükleniyor ... +text.connectfail=[crimson] Sunucuya bağlanılamadı: [orange] {0} +text.server.port=Liman +text.server.addressinuse=Adres çoktan kullanımda! +text.server.invalidport=Bağlantı noktası numarası geçersiz. +text.server.error=[crimson] Sunucu barındırma hatası: [orange] {0} +text.tutorial.back=<Önceki +text.tutorial.next=İleri > +text.save.new=6349,Yeni Kayıt +text.save.overwrite=Bu kayıt yuvasının üzerine yazmak istediğinizden emin misiniz? +text.overwrite=Üzerine Yaz +text.save.none=Hiçbir kayıt bulunamadı! +text.saveload=[Vurgu] Kaydediliyor ... +text.savefail=Oyun kaydedilemedi! +text.save.delete.confirm=Bu kaydı silmek istediğinizden emin misiniz? +text.save.delete=Sil +text.save.export=Dışa Aktar +text.save.import.invalid=[turuncu] Bu kayıt geçersiz! +text.save.import.fail=[crimson] Kayıt oyuna aktarılamadı : [orange] {0} +text.save.export.fail=[crimson] Kayıt dışa aktarılamadı: [orange] {0} +text.save.import=İçe Aktar +text.save.newslot=İsmi kaydet: +text.save.rename=Yeniden Adlandır +text.save.rename.text=Yeni İsim: +text.selectslot=Bir kayıt seçin. +text.slot=[accent] Yuva {0} +text.save.corrupted=[orange] Kayıt dosyası bozuk veya geçersiz! +text.empty= +text.on=Açık +text.off=Kapalı +text.save.autosave=Otomatik kaydetme: {0} +text.save.map=harita +text.save.wave=Dalga +text.save.difficulty=zorluk +text.save.date=Son Kaydedilen: {0} +text.confirm=Onayla +text.delete=Sil +text.ok=Tamam +text.open=Açık +text.cancel=İptal +text.openlink=Linki aç +text.copylink=Bağlantıyı kopyala +text.back=Geri +text.quit.confirm=Çıkmak istediğinden emin misin? +text.changelog.title=Değişiklik listesi +text.changelog.loading=Değişiklik listesi yükleniyor +text.changelog.error.android=[turuncu] Android'da olan hata nedeniyle değişiklik listesi görüntülenemiyor. +text.changelog.error=[scarlet] Değişiklik listesi alma hatası! İnternet bağlantınızı kontrol edin. +text.changelog.current=[sarı] [[Güncel versiyon] +text.changelog.latest=[turuncu] [[Son sürüm] +text.loading=[Vurgu] Yükleniyor ... +text.wave=[turuncu] Dalga {0} +text.wave.waiting={0} içinde dalga +text.waiting=Bekleniyor +text.enemies={0} Düşmanlar +text.enemies.single={0} Düşman +text.loadimage=Resmi yükle +text.saveimage=Resmi Kaydet +text.editor.badsize=[orange] Resim boyutları geçersiz! [] Geçerli harita boyutları: {0} +text.editor.errorimageload=Resim dosyası yüklenirken hata oluştu: [orange] {0} +text.editor.errorimagesave=Resim dosyası kaydedilirken hata oluştu: [orange] {0} +text.editor.generate=Üretmek +text.editor.resize=Yeniden Boyutlandırma +text.editor.loadmap=Harita Yükle +text.editor.savemap=Harita Kaydet +text.editor.loadimage=Resmi yükle +text.editor.saveimage=Resmi Kaydet +text.editor.unsaved=[scarlet] Kaydedilmemiş değişiklikleriniz var! [] Çıkmak istediğinizden emin misiniz? +text.editor.brushsize=Fırça boyutu: {0} +text.editor.noplayerspawn=Bu haritanın oyuncu spawnpoint'i yok! +text.editor.manyplayerspawns=Haritalar, birden fazla oyuncu spawnpoint'e sahip olamaz! +text.editor.manyenemyspawns={0} düşman spawnpoint {0}'den daha fazlası olamaz! +text.editor.resizemap=Haritayı Yeniden Boyutlandır +text.editor.resizebig=[Kızıl] Uyarı! [] 256'dan büyük haritalar yavaş ve dengesiz olabilir. +text.editor.mapname=Harita Adı +text.editor.overwrite=[Vurgu] Uyarı! Bu mevcut bir haritanın üzerine yazar. +text.editor.selectmap=Yüklenecek bir harita seçin: +text.width=Genişliği: +text.height=Boy: +text.randomize=Rasgele seçmek +text.apply=Uygula +text.update=Güncelle +text.menu=Menü +text.play=Oyna +text.load=Yükle +text.save=Kaydet +text.language.restart=Lütfen dil ayarlarının etkili olması için oyununuzu yeniden başlatın. +text.settings.language=Dil +text.settings=Ayarlar +text.tutorial=Eğitim +text.editor=Editör +text.mapeditor=Harita Editörü +text.donate=Bağışlamak +text.settings.reset=Varsayılanlara Dön +text.settings.controls=kontroller +text.settings.game=Oyun +text.settings.sound=Ses +text.settings.graphics=Grafik +text.upgrades=Geliştirmeler +text.purchased=[KİREÇ] Yap၊ld၊ +text.weapons=Silahlar +text.paused=Duraklatıldı +text.info.title=[Vurgu] Bilgi +text.error.title=[crimson] Bir hata oluştu +text.error.crashmessage=[SCARLET] Bir kilitlenme meydana getiren beklenmeyen bir hata oluştu. [] Lütfen geliştiriciye bu hatanın gerçekleştiği koşulları bildirin: [ORANGE] anukendev@gmail.com [] +text.error.crashtitle=Bir hata oluştu +text.blocks.blockinfo=Blok Bilgisi +text.blocks.powercapacity=Güç kapasitesi +text.blocks.powershot=Güç / atış +text.blocks.size=Boyut +text.blocks.liquidcapacity=Sıvı kapasitesi +text.blocks.maxitemssecond=Maksimum öğe / saniye +text.blocks.powerrange=Güç aralığı +text.blocks.itemcapacity=Ürün kapasitesi +text.blocks.inputliquid=Giriş sıvı +text.blocks.inputitem=Giriş öğesi +text.blocks.explosive=Çok patlayıcı! +text.blocks.health=Can +text.blocks.inaccuracy=yanlışlık +text.blocks.shots=atışlar +text.blocks.inputcapacity=Giriş kapasitesi +text.blocks.outputcapacity=Çıkış kapasitesi +setting.difficulty.easy=kolay +setting.difficulty.normal=orta +setting.difficulty.hard=zor +setting.difficulty.insane=deli +setting.difficulty.purge=tasfiye +setting.difficulty.name=Zorluk: +setting.screenshake.name=Ekran Sallamak +setting.smoothcam.name=Pürüzsüz kamera +setting.indicators.name=Düşman Göstergeleri +setting.effects.name=Görüntü Efektleri +setting.sensitivity.name=Denetleyici hassasiyeti +setting.saveinterval.name=Otomatik Kaydetme Aralığı +setting.seconds=saniye +setting.fullscreen.name=Tam ekran +setting.multithread.name=Çok iş parçacığı +setting.fps.name=Saniyede ... Kare göstermek +setting.vsync.name=VSync +setting.lasers.name=Güç Lazerleri Göster +setting.healthbars.name=Varlık Sağlık çubuklarını göster +setting.pixelate.name=Piksel Ekran +setting.musicvol.name=Müzik sesi +setting.mutemusic.name=Müziği Kapat +setting.sfxvol.name=SFX Hacmi +setting.mutesound.name=Sesi kapat +map.maze.name=Labirent +map.fortress.name=Kale +map.sinkhole.name=düden +map.caves.name=mağaralar +map.volcano.name=volkan +map.caldera.name=kaldera +map.scorch.name=alazlamak +map.desert.name=çöl +map.island.name=ada +map.grassland.name=Çayır +map.tundra.name=tundra +map.spiral.name=sarmal +map.tutorial.name=Eğitim +text.keybind.title=Tuşları yeniden ayarla +keybind.move_x.name=sağ / sol +keybind.move_y.name=yukarı / aşağı +keybind.select.name=seçmek +keybind.break.name=kırmak +keybind.shoot.name=ateş etme +keybind.zoom_hold.name=tut ve büyüt +keybind.zoom.name=Yakınlaştır +keybind.block_info.name=blok bilgisi +keybind.menu.name=menü +keybind.pause.name=duraklatma +keybind.dash.name=tire +keybind.chat.name=Sohbet +keybind.player_list.name=oyuncu listesi +keybind.console.name=KONTROL MASASI +keybind.rotate_alt.name=rotate_alt +keybind.rotate.name=Döndür +mode.text.help.title=Modların açıklaması +mode.waves.name=dalgalar +mode.waves.description=normal mod. sınırlı kaynaklar ve otomatik gelen dalgalar. +mode.sandbox.name=Limitsiz Oynama +mode.sandbox.description=sonsuz kaynaklar ve dalgalar için zamanlayıcı yok. +mode.freebuild.name=Özgür Oynama +mode.freebuild.description=sınırlı kaynaklar ve dalgalar için zamanlayıcı yok. +item.stone.name=taş +item.coal.name=kömür +item.titanium.name=titanyum +item.sand.name=kum +liquid.water.name=su +liquid.lava.name=lav +liquid.oil.name=petrol +block.door.name=kapı +block.door-large.name=büyük kapı +block.conduit.name=sıvı borusu +block.pulseconduit.name=hızlı sıvı borusu +block.liquidrouter.name=sıvı yönlendirici +block.conveyor.name=konveyör +block.router.name=yönlendirici +block.junction.name=Kavşak noktası +block.liquidjunction.name=sıvı bağlantı +block.sorter.name=ayrıştırıcı +block.smelter.name=dökümcü +text.web.unsupported=The web version does not support this feature! Download the game to use it. +text.host.web=The web version does not support hosting games! Download the game to use this feature. +text.map.delete=Are you sure you want to delete the map "[orange]{0}[]"? +text.construction.title=Block Construction Guide +text.construction=You've just selected [accent]block construction mode[].\n\nTo begin placing, simply tap a valid location near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Shift the selection[] by holding and dragging any block in the selection.\n- [accent]Place blocks in a line[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel construction or selection[] by pressing the X at the bottom left. +text.deconstruction.title=Block Deconstruction Guide +text.deconstruction=You've just selected [accent]block deconstruction mode[].\n\nTo begin breaking, simply tap a block near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin de-constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Remove blocks in an area[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel deconstruction or selection[] by pressing the X at the bottom left. +text.showagain=Don't show again next session +text.unlocks=Unlocks +text.addplayers=Add/Remove Players +text.maps=Maps +text.maps.none=[LIGHT_GRAY]No maps found! +text.unlocked=New Block Unlocked! +text.unlocked.plural=New Blocks Unlocked! +text.server.rollback=Rollback +text.server.rollback.numberfield=Rollback Amount: +text.blocks.editlogs=Edit Logs +text.block.editlogsnotfound=[red]There are no edit logs for this location. +text.server.kicked.fastShoot=You are shooting too quickly. +text.server.kicked.nameInUse=There is someone with that name\nalready on this server. +text.server.kicked.nameEmpty=Your name must contain at least one character or number. +text.server.kicked.idInUse=You are already on this server! Connecting with two accounts is not permitted. +text.server.kicked.customClient=This server does not support custom builds. Download an official version. +text.changelog.error.ios=[orange]The changelog is currently not supported in iOS. +text.saving=[accent]Saving... +text.unknown=Unknown +text.custom=Custom +text.builtin=Built-In +text.map.delete.confirm=Are you sure you want to delete this map? This action cannot be undone! +text.map.random=[accent]Random Map +text.map.nospawn=This map does not have any cores for the player to spawn in! Add a [ROYAL]blue[] core to this map in the editor. +text.editor.slope=\\ +text.editor.openin=Open In Editor +text.editor.oregen=Ore Generation +text.editor.oregen.info=Ore Generation: +text.editor.mapinfo=Map Info +text.editor.author=Author: +text.editor.description=Description: +text.editor.name=Name: +text.editor.teams=Teams +text.editor.elevation=Elevation +text.editor.saved=Saved! +text.editor.save.noname=Your map does not have a name! Set one in the 'map info' menu. +text.editor.save.overwrite=Your map overwrites a built-in map! Pick a different name in the 'map info' menu. +text.editor.import.exists=[scarlet]Unable to import:[] a built-in map named '{0}' already exists! +text.editor.import=Import... +text.editor.importmap=Import Map +text.editor.importmap.description=Import an already existing map +text.editor.importfile=Import File +text.editor.importfile.description=Import an external map file +text.editor.importimage=Import Terrain Image +text.editor.importimage.description=Import an external map image file +text.editor.export=Export... +text.editor.exportfile=Export File +text.editor.exportfile.description=Export a map file +text.editor.exportimage=Export Terrain Image +text.editor.exportimage.description=Export a map image file +text.editor.overwrite.confirm=[scarlet]Warning![] A map with this name already exists. Are you sure you want to overwrite it? +text.fps=FPS: {0} +text.tps=TPS: {0} +text.ping=Ping: {0}ms +text.settings.rebind=Rebind +text.yes=Yes +text.no=No +text.blocks.targetsair=Targets Air +text.blocks.itemspeed=Units Moved +text.blocks.shootrange=Range +text.blocks.poweruse=Power Use +text.blocks.inputitemcapacity=Input Item Capacity +text.blocks.outputitemcapacity=Input Item Capacity +text.blocks.maxpowergeneration=Max Power Generation +text.blocks.powertransferspeed=Power Transfer +text.blocks.craftspeed=Production Speed +text.blocks.inputliquidaux=Aux Liquid +text.blocks.inputitems=Input Items +text.blocks.outputitem=Output Item +text.blocks.drilltier=Drillables +text.blocks.drillspeed=Base Drill Speed +text.blocks.liquidoutput=Liquid Output +text.blocks.liquiduse=Liquid Use +text.blocks.coolant=Coolant +text.blocks.coolantuse=Coolant Use +text.blocks.inputliquidfuel=Fuel Liquid +text.blocks.liquidfueluse=Liquid Fuel Use +text.blocks.reload=Reload +text.blocks.inputfuel=Fuel +text.blocks.fuelburntime=Fuel Burn Time +text.unit.blocks=blocks +text.unit.powersecond=power units/second +text.unit.liquidsecond=liquid units/second +text.unit.itemssecond=items/second +text.unit.pixelssecond=pixels/second +text.unit.liquidunits=liquid units +text.unit.powerunits=power units +text.unit.degrees=degrees +text.unit.seconds=seconds +text.unit.none= +text.unit.items=items +text.category.general=General +text.category.power=Power +text.category.liquids=Liquids +text.category.items=Items +text.category.crafting=Crafting +text.category.shooting=Shooting +setting.previewopacity.name=Placing Preview Opacity +setting.minimap.name=Show Minimap +content.item.name=Items +content.liquid.name=Liquids +content.unit-type.name=Units +content.recipe.name=Blocks +item.stone.description=A common raw material. Used for separating and refining into other materials, or melting into lava. +item.tungsten.name=Tungsten +item.tungsten.description=A common, but very useful structure material. Used in drills and heat-resistant blocks such as generators and smelteries. +item.lead.name=Lead +item.lead.description=A basic starter material. Used extensively in electronics and liquid transportation blocks. +item.coal.description=A common and readily available fuel. +item.carbide.name=Carbide +item.carbide.description=A tough alloy made with tungsten and carbon. Used in advanced transportation blocks and high-tier drills. +item.titanium.description=A rare super-light metal used extensively in liquid transportation, drills and aircraft. +item.thorium.name=Thorium +item.thorium.description=A dense, radioactive metal used as structural support and nuclear fuel. +item.silicon.name=Silicon +item.silcion.description=An extremely useful semiconductor, with applications in solar panels and many complex electronics. +item.plastanium.name=Plastanium +item.plastanium.description=A light, ductile material used in advanced aircraft and fragmentation ammunition. +item.phase-matter.name=Phase Matter +item.surge-alloy.name=Surge Alloy +item.biomatter.name=Biomatter +item.biomatter.description=A clump of organic mush; used for conversion into oil or as a basic fuel. +item.sand.description=A common material that is used extensively in smelting, both in alloying and as a flux. +item.blast-compound.name=Blast Compound +item.blast-compound.description=A volatile compound used in bombs and explosives. While it can burned as fuel, this is not advised. +item.pyratite.name=Pyratite +item.pyratite.description=An extremely flammable substance used in incendiary weapons. +liquid.cryofluid.name=Cryofluid +text.item.explosiveness=[LIGHT_GRAY]Explosiveness: {0} +text.item.flammability=[LIGHT_GRAY]Flammability: {0} +text.item.radioactivity=[LIGHT_GRAY]Radioactivity: {0} +text.item.fluxiness=[LIGHT_GRAY]Flux Power: {0} +text.item.hardness=[LIGHT_GRAY]Hardness: {0} +text.liquid.heatcapacity=[LIGHT_GRAY]Heat Capacity: {0} +text.liquid.viscosity=[LIGHT_GRAY]Viscosity: {0} +text.liquid.temperature=[LIGHT_GRAY]Temperature: {0} +block.tungsten-wall.name=Tungsten Wall +block.tungsten-wall-large.name=Large Tungsten Wall +block.carbide-wall.name=Carbide Wall +block.carbide-wall-large.name=Large Carbide Wall +block.thorium-wall.name=Thorium Wall +block.thorium-wall-large.name=Large Thorium Wall +block.duo.name=Duo +block.scorch.name=Scorch +block.hail.name=Hail +block.lancer.name=Lancer +block.titanium-conveyor.name=Titanium Conveyor +block.splitter.name=Splitter +block.splitter.description=Outputs items into two opposite directions immediately after they are recieved. +block.router.description=Splits items into all 4 directions. Can store items as a buffer. +block.distributor.name=Distributor +block.distributor.description=A splitter that can split items into 8 directions. +block.sorter.description=Sorts items. If an item matches the selection, it is allowed to pass. Otherwise, the item is outputted to the left and right. +block.overflow-gate.name=Overflow Gate +block.overflow-gate.description=A combination splitter and router that only outputs to the left and right if the front path is blocked. +block.bridgeconveyor.name=Bridge Conveyor +block.bridgeconveyor.description=A conveyor that can go over other blocks, for up to two total blocks. +block.arc-smelter.name=Arc Smelter +block.silicon-smelter.name=Silicon Smelter +block.phase-weaver.name=Phase Weaver +block.pulverizer.name=Pulverizer +block.cryofluidmixer.name=Cryofluid Mixer +block.melter.name=Melter +block.incinerator.name=Incinerator +block.biomattercompressor.name=Biomatter Compressor +block.separator.name=Separator +block.centrifuge.name=Centrifuge +block.power-node.name=Power Node +block.power-node-large.name=Large Power Node +block.battery.name=Battery +block.battery-large.name=Large Battery +block.combustion-generator.name=Combustion Generator +block.turbine-generator.name=Turbine Generator +block.tungsten-drill.name=Tungsten Drill +block.carbide-drill.name=Carbide Drill +block.laser-drill.name=Laser Drill +block.water-extractor.name=Water Extractor +block.cultivator.name=Cultivator +block.dart-ship-factory.name=Dart Ship Factory +block.delta-mech-factory.name=Delta Mech Factory +block.dronefactory.name=Drone Factory +block.repairpoint.name=Repair Point +block.resupplypoint.name=Resupply Point +block.liquidtank.name=Liquid Tank +block.bridgeconduit.name=Bridge Conduit +block.mechanical-pump.name=Mechanical Pump +block.itemsource.name=Item Source +block.itemvoid.name=Item Void +block.liquidsource.name=Liquid Source +block.powervoid.name=Power Void +block.powerinfinite.name=Power Infinite +block.unloader.name=Unloader +block.sortedunloader.name=Sorted Unloader +block.vault.name=Vault +block.wave.name=Wave +block.swarmer.name=Swarmer +block.salvo.name=Salvo +block.ripple.name=Ripple +block.phase-conveyor.name=Phase Conveyor +block.bridge-conveyor.name=Bridge Conveyor +block.plastanium-compressor.name=Plastanium Compressor +block.pyratite-mixer.name=Pyratite Mixer +block.blast-mixer.name=Blast Mixer +block.solidifer.name=Solidifer +block.solar-panel.name=Solar Panel +block.solar-panel-large.name=Large Solar Panel +block.oil-extractor.name=Oil Extractor +block.javelin-ship-factory.name=Javelin Ship factory +block.drone-factory.name=Drone Factory +block.fabricator-factory.name=Fabricator Factory +block.repair-point.name=Repair Point +block.resupply-point.name=Resupply Point +block.pulse-conduit.name=Pulse Conduit +block.phase-conduit.name=Phase Conduit +block.liquid-router.name=Liquid Router +block.liquid-tank.name=Liquid Tank +block.liquid-junction.name=Liquid Junction +block.bridge-conduit.name=Bridge Conduit +block.rotary-pump.name=Rotary Pump +block.nuclear-reactor.name=Nuclear Reactor diff --git a/core/assets/bundles/bundle_uk_UA.properties b/core/assets/bundles/bundle_uk_UA.properties index 58caee33e5..8764b65c68 100644 --- a/core/assets/bundles/bundle_uk_UA.properties +++ b/core/assets/bundles/bundle_uk_UA.properties @@ -1,500 +1,515 @@ -text.about = Створено [ROYAL] Anuken. []\nСпочатку запис у [orange] GDL [] MM Jam.\nТворці:\n- SFX зроблено з [YELLOW] bfxr []\n- Музика зроблена [GREEN] RoccoW [] / Знайдено на [lime] FreeMusicArchive.org [] \nОсоблива подяка:\n- [coral] MitchellFJN []: екстенсивне тестування та відгуки\n- [sky] Luxray5474 []: робота з вікі, вклади коду\n- Всі бета-тестери на itch.io та Google Play\n -text.discord = Приєднуйтесь до нашого Discord! -text.changes = [SCARLET] Увага! \n[] Деякі важливі механіки гри були змінені.\n- [accent] Телепорти [] тепер використовують електроенергію. \n- [accent] Домінна піч [] та [accent] Тиглі [] тепер мають ліміт. \n- [accent] Тиглі [] зараз вимагають вугілля як паливо. -text.gameover = Ядро було зруйновано. -text.highscore = [YELLOW] Новий рекорд! -text.lasted = Ви тримались до хвилі -text.level.highscore = Рекорд: [accent] {0} -text.level.delete.title = Підтвердьте видалення -text.level.delete = Ви впевнені, що хочете видалити карту \"[orange] {0} \"? -text.level.select = Вибір рівня -text.level.mode = Ігровий режим -text.savegame = Зберегти гру -text.loadgame = Завантажити гру -text.joingame = Приєднатися\nдо гри +text.about=Створено [ROYAL] Anuken. []\nСпочатку запис у [orange] GDL [] MM Jam.\nТворці:\n- SFX зроблено з [YELLOW] bfxr []\n- Музика зроблена [GREEN] RoccoW [] / Знайдено на [lime] FreeMusicArchive.org [] \nОсоблива подяка:\n- [coral] MitchellFJN []: екстенсивне тестування та відгуки\n- [sky] Luxray5474 []: робота з вікі, вклади коду\n- Всі бета-тестери на itch.io та Google Play\n +text.discord=Приєднуйтесь до нашого Discord! +text.gameover=Ядро було зруйновано. +text.highscore=[YELLOW] Новий рекорд! +text.lasted=Ви тримались до хвилі +text.level.highscore=Рекорд: [accent] {0} +text.level.delete.title=Підтвердьте видалення +text.level.select=Вибір рівня +text.level.mode=Ігровий режим +text.savegame=Зберегти гру +text.loadgame=Завантажити гру +text.joingame=Приєднатися\nдо гри text.newgame=Нова гра -text.quit = Вийти -text.about.button = Про -text.name = Назва: -text.public = Публічний -text.players = {0} гравців онлайн -text.server.player.host = {0} (host) -text.players.single = {0} гравців онлайн -text.server.mismatch = Пакетна помилка: невідповідність версії версії клієнта / сервера. Переконайтеся, що ви та хост мають останню версію Mindustry! -text.server.closing = [accent] Закриття сервера ... -text.server.kicked.kick = Ви були вигнані з сервера! -text.server.kicked.invalidPassword = Невірний пароль! -text.server.kicked.clientOutdated = Застарілий клієнт! Оновіть свою гру! -text.server.kicked.serverOutdated = Застарілий сервер! Попросіть хост оновити! -text.server.connected = {0} приєднався. -text.server.disconnected = {0} від'єднано. -text.nohost = Неможливо розмістити сервер на власній карті! -text.hostserver = Хост-сервер -text.host = Хост -text.hosting = [accent] Відкриття сервера ... -text.hosts.refresh = Оновити -text.hosts.discovering = Знайомство з мережевими іграми -text.server.refreshing = Оновити сервери -text.hosts.none = [lightgray] Ніяких ігор у мережі не знайдено! -text.host.invalid = [scarlet] Неможливо підключитися до хосту. -text.server.friendlyfire = Дружній вогонь -text.server.add = Додати сервер -text.server.delete = Ви впевнені, що хочете видалити цей сервер? -text.server.hostname = Хост: {0} -text.server.edit = Редагувати сервер -text.joingame.byip = [] Приєднатися по IP ...[] -text.joingame.title = Приєднатися до гри -text.joingame.ip = IP -text.disconnect = Роз'єднано -text.connecting = [accent] Підключення ... -text.connecting.data = [accent] Завантаження світових даних ... -text.connectfail = [crimson] Не вдалося підключитися до сервера: [orange] {0} -text.server.port = Порт -text.server.addressinuse = Адреса вже використовується! -text.server.invalidport = Недійсний номер порту. -text.server.error = [crimson] Помилка хостингу сервера: [orange] {0} -text.tutorial.back = < Попер. -text.tutorial.next = Далі > -text.save.new = Нове збереження -text.save.overwrite = Ви впевнені, що хочете перезаписати цей слот для збереження? -text.overwrite = Перезаписати -text.save.none = Не знайдено жодних збережень! -text.saveload = [accent] Збереження ... -text.savefail = Не вдалося зберегти гру! -text.save.delete.confirm = Ви впевнені, що хочете видалити це збереження? -text.save.delete = Видалити -text.save.export = Експорт збереження -text.save.import.invalid = [orange] Це збереження недійсне! -text.save.import.fail = [crimson] Не вдалося імпортувати збереження: [orange] {0} -text.save.export.fail = [crimson] Не вдалося експортувати збереження: [orange] {0} -text.save.import = Імпортувати збереження -text.save.newslot = Назва збереження: -text.save.rename = Переіменувати -text.save.rename.text = Нова назва: -text.selectslot = Виберіть збереження. -text.slot = [accent] слот {0} -text.save.corrupted = [orange] Збережений файл пошкоджений або він невірний! -text.empty = <порожньо> -text.on = Увімкнути -text.off = Вимкнути -text.save.autosave = Автозбереження: {0} -text.save.map = Карта -text.save.wave = Хвиля {0} -text.save.difficulty = Складність -text.save.date = Останнє збережено: {0} -text.confirm = Підтвердити -text.delete = Видалити -text.ok = ОК -text.open = Відкрити -text.cancel = Скасувати -text.openlink = Відкрити посилання -text.back = Назад -text.quit.confirm = Ти впевнений що хочеш піти? -text.loading = [accent] Завантаження ... -text.wave = [orange] хвиля {0} -text.wave.waiting = Хвиля через {0} -text.waiting = Очікування… -text.enemies = {0} Вороги -text.enemies.single = Противник -text.loadimage = Завантажити зображення -text.saveimage = Зберегти зображення -text.oregen = Генерація руд -text.editor.badsize = [orange] Недійсні розміри зображення! [] Дійсні розміри карти: {0} -text.editor.errorimageload = Помилка завантаження файлу зображень: [orange] {0} -text.editor.errorimagesave = Помилка збереження файлу зображення: [orange] {0} -text.editor.generate = Генератор -text.editor.resize = Змінити розмір -text.editor.loadmap = // Завантажити карту -text.editor.savemap = Зберегти карту -text.editor.loadimage = Завантажити зображення -text.editor.saveimage = Зберегти зображення -text.editor.unsaved = [scarlet] У вас є незбережені зміни! [] Ви впевнені, що хочете вийти? -text.editor.brushsize = Розмір пензля: {0} -text.editor.noplayerspawn = Ця карта не має ігрового поля для гравця! -text.editor.manyplayerspawns = Карти не можуть мати більше одного ігрового поля для гравців! -text.editor.manyenemyspawns = Не може бути більше ніж {0} ворожих точок! -text.editor.resizemap = Змінити розмір карти -text.editor.resizebig = [scarlet] Попередження! [] Карти, розмір яких перевищує 256 одиниць, можуть виснути і можуть бути нестабільними. -text.editor.mapname = Назва карти: -text.editor.overwrite = [accent] Попередження! Це перезаписує існуючу карту. -text.editor.failoverwrite = [crimson] Неможливо перезаписати карту за замовчуванням! -text.editor.selectmap = Виберіть карту для завантаження: -text.width = Ширина -text.height = Висота -text.randomize = Рандомізувати -text.apply = Застосувати -text.update = Оновити -text.menu = Меню -text.play = Відтворити -text.load = Завантаження -text.save = Зберегти -text.language.restart = Будь ласка, перезапустіть свою гру, щоб налаштування мови набули чинності. -text.settings.language = Мова -text.settings = Налаштування -text.tutorial = Навчальний\nпосібник -text.editor = Редактор -text.mapeditor = Редактор карт -text.donate = Підтримати проект -text.settings.reset = Скинути до стандартних -text.settings.controls = Елементи управління -text.settings.game = Гра -text.settings.sound = Звук -text.settings.graphics = Графіка -text.upgrades = Оновлення -text.purchased = [LIME] Створено! -text.weapons = Зброя -text.paused = Пауза -text.respawn = Відновлення за -text.info.title = [accent] інформація -text.error.title = [crimson] Виникла помилка -text.error.crashmessage = [SCARLET] Виникла несподівана помилка, що призвела до збою. [] Будь ласка, повідомте про конкретні обставини, розробнику: [ORANGE] anukendev@gmail.com [] -text.error.crashtitle = Виникла помилка -text.mode.break = Режим зносу: {0} -text.mode.place = Режим будівництва: {0} -placemode.hold.name = Лінія -placemode.areadelete.name = Площа -placemode.touchdelete.name = Дотик -placemode.holddelete.name = Утримування. -placemode.none.name = (None) -placemode.touch.name = Дотик -placemode.cursor.name = курсор -text.blocks.extrainfo = [accent] додатковий інформаційний блок: -text.blocks.blockinfo = Блокування інформації -text.blocks.powercapacity = Потужність -text.blocks.powershot = Потужність / постріл -text.blocks.powersecond = Потужність / секунда -text.blocks.powerdraindamage = Потужність дренажу / пошкодження -text.blocks.shieldradius = Радіус щита -text.blocks.itemspeedsecond = Швидкість / секунда -text.blocks.range = Радіус -text.blocks.size = Розмір -text.blocks.powerliquid = Потужність / Рідина -text.blocks.maxliquidsecond = Макс. Рідина / секунда -text.blocks.liquidcapacity = Ємкість рідини -text.blocks.liquidsecond = Рідина / секунда -text.blocks.damageshot = Пошкодження / постріл -text.blocks.ammocapacity = Місткість боєприпасів -text.blocks.ammo = Набої -text.blocks.ammoitem = Боєприпаси / предмет -text.blocks.maxitemssecond = Макс. Елементи / секунду -text.blocks.powerrange = Радіус потужності -text.blocks.lasertilerange = Радіус лазерних плиток -text.blocks.capacity = Ємкість -text.blocks.itemcapacity = Ємкість предмету -text.blocks.maxpowergenerationsecond = Максимальна потужність / секунда -text.blocks.powergenerationsecond = Потужність / секунда -text.blocks.generationsecondsitem = Генерація за секунду / предмет -text.blocks.input = Ввід -text.blocks.inputliquid = Ввід речовини -text.blocks.inputitem = Вхідний матеріал -text.blocks.output = Вивід -text.blocks.secondsitem = Секунда / предмет -text.blocks.maxpowertransfersecond = Максимальна передача потужності / секунда -text.blocks.explosive = Вибухонебезпечний! -text.blocks.repairssecond = Ремонт / секунда -text.blocks.health = Здоров'я -text.blocks.inaccuracy = Неточність -text.blocks.shots = Постріли -text.blocks.shotssecond = Постріли / секунду -text.blocks.fuel = Паливо: -text.blocks.fuelduration = Тривалість палива -text.blocks.maxoutputsecond = Макс. Вихід / секунду -text.blocks.inputcapacity = Вхідна ємність -text.blocks.outputcapacity = Випускна ємність -text.blocks.poweritem = Потужність / виріб -text.placemode = Місцевий режим -text.breakmode = Перерваний режим -text.health = Здоров'я -setting.difficulty.easy = Легкий -setting.difficulty.normal = Нормальний -setting.difficulty.hard = Важкий -setting.difficulty.insane = Божевільний -setting.difficulty.purge = Очистити -setting.difficulty.name = Складність -setting.screenshake.name = Тряска екрана -setting.smoothcam.name = Гладка камера -setting.indicators.name = Індикатори ворога -setting.effects.name = Ефекти відображення -setting.sensitivity.name = Чутливість контролера -setting.saveinterval.name = Інтервал автозбереження -setting.seconds = {0} секунд -setting.fullscreen.name = Повноекранний -setting.multithread.name = Багатопотоковий [scarlet] (нестабільний!) -setting.fps.name = Показати FPS -setting.vsync.name = VSunc -setting.lasers.name = Показати енергетичні лазери -setting.healthbars.name = Показати здоров'я -setting.pixelate.name = Пікселяція екрану -setting.musicvol.name = Гучність музики -setting.mutemusic.name = Вимкнути музику -setting.sfxvol.name = Гучність ефектів -setting.mutesound.name = Вимкнути звук -map.maze.name = Лабіринт -map.fortress.name = Фортеця -map.sinkhole.name = Свердловина -map.caves.name = Печери -map.volcano.name = Вулкан -map.caldera.name = Кальдера -map.scorch.name = Мертва земля -map.desert.name = Пустеля -map.island.name = Острів -map.grassland.name = Пасовища -map.tundra.name = Тундра -map.spiral.name = Спіраль -map.tutorial.name = Навчання -tutorial.intro.text = [yellow] Ласкаво просимо до підручника. [] Для початку натисніть \"далі\". -tutorial.moveDesktop.text = Для переміщення використовуйте клавіші [orange] ​​[[WASD] []. Утримуйте [orange] SHIFT[], для прискорення. Утримуйте [orange] CTRL [], використовуючи [orange] колесо прокручування [] для збільшення або зменшення. -tutorial.shoot.text = Використовуйте мишу, щоб націлитись, утримуйте [orange] ліву кнопку миші [], щоб стріляти. Попрактикуйтесь на [yellow] мішені []. -tutorial.moveAndroid.text = Щоб перетягнути панораму, перетягніть один палець по екрану. Використовуйте два пальця, щоб збільшити чи зменшити маштаб. -tutorial.placeSelect.text = Спробуйте вибрати [yellow] конвеєр [] у меню блоку внизу справа. -tutorial.placeConveyorDesktop.text = Використовуйте [orange] [[колесико миші] [], щоб повернути конвеєр [orange] вперед [], а потім помістіть його в [yellow] позначене місце [], використовуючи [orange] [[ліву кнопку миші] []. -tutorial.placeConveyorAndroid.text = Використовуйте [orange] [[кнопку оберту] [], щоб обернути конвеєр [оранжевий] вперед [], перетягуйте його одним пальцем, а потім помістіть його в [yellow] позначене місце [], використовуючи [orange] [[галочка][]. -tutorial.placeConveyorAndroidInfo.text = Крім того, ви можете натиснути піктограму перехрестя внизу ліворуч, щоб переключитися на [orange] [[сенсорний режим]] [], і помістити блоки, натиснувши на екран. У сенсорному режимі блоки можна повертати зі стрілкою внизу ліворуч. Натисніть [yellow] наступний [], щоб спробувати. -tutorial.placeDrill.text = Тепер виберіть та розмістіть [yellow] кам'яне свердло [] у зазначеному місці. -tutorial.blockInfo.text = Якщо ви хочете дізнатись більше про блок, ви можете торкнутися [orange] знак питання [] у верхньому правому куті, щоб прочитати його опис. -tutorial.deselectDesktop.text = Ви можете вимкнути блок, використовуючи [orange] [[клацання правою кнопкою миші] []. -tutorial.deselectAndroid.text = Ви можете скасувати вибір блоку, натиснувши кнопку [orange] X []. -tutorial.drillPlaced.text = Дриль тепер видобуває [yellow] камінь, [] та виведе його на конвеєр, а потім переміщає його в [yellow] ядро []. -tutorial.drillInfo.text = Різні руди потребують різних дрилі. Камінь вимагає кам'яні свердла, залізо вимагає залізні свердла та ін -tutorial.drillPlaced2.text = Переміщення елементів у ядро ​​вказує їх у ваш [yellow] предметний інвентар [] у верхньому лівому куті. Розміщення блоків використовує предмети з вашого інвентарю. -tutorial.moreDrills.text = Ви можете пов'язати багато свердлів і конвеєрів разом в одну гілку конвеєра. -tutorial.deleteBlock.text = Ви можете видалити блоки, натиснувши правою клавішею [orange] правою кнопкою миші [] по блоці, який ви хочете видалити. Спробуйте видалити цей конвеєр. -tutorial.deleteBlockAndroid.text = Ви можете видалити блоки за допомогою [orange], перехрестя [] в меню [mode] зламу [orange] у нижньому лівому куті та натиснувши на блок. Спробуйте видалити цей конвеєр. -tutorial.placeTurret.text = Тепер виділіть та розмістіть [yellow] турель [] у [yellow] позначеному місці []. -tutorial.placedTurretAmmo.text = Ця турель тепер приймає [yellow] боєприпас [] з конвеєра. Ви можете побачити, скільки боєприпасів вона має, натискаючи на неї і перевіряючи [green] зелену полоску []. -tutorial.turretExplanation.text = Турелі будуть автоматично стріляти у найближчого ворога, якщо вони мають достатню кількість боєприпасів. -tutorial.waves.text = Кожні [yellow] 60 [] секунд, хвиля [coral] ворогів [] буде виникати в певних місцях і намагатися знищити ядро. -tutorial.coreDestruction.text = Ваша мета полягає в тому, щоб [yellow] захищати ядро []. Якщо ядро ​​знищено, ви [coral] програєте[]. -tutorial.pausingDesktop.text = Якщо вам коли-небудь потрібно зробити перерву, натисніть кнопку [orange] паузи [] у верхньому лівому куті або на кнопку [orange] пропуск [], щоб призупинити гру. Ви можете вибрати і розмістити блоки під час призупинення, але не можете переміщатися чи стріляти. -tutorial.pausingAndroid.text = Якщо вам коли-небудь потрібно зробити перерву, натисніть кнопку [orange] пауза [] у верхньому лівому куті, щоб призупинити гру. Ти можеш ще знищувати та будувати блоки під час призупинення. -tutorial.purchaseWeapons.text = Ви можете придбати нову [yellow] зброю [] для вашого механізму, відкривши меню оновлення в лівому нижньому кутку. -tutorial.switchWeapons.text = Перемикати зброю будь-яким натисканням його піктограми внизу ліворуч або за допомогою цифр [orange] [[1-9] []. -tutorial.spawnWave.text = Ось хвиля зараз. Знищи їх -tutorial.pumpDesc.text = У пізніших хвилях, можливо, доведеться використовувати [yellow] насоси [] для розподілу рідин для генераторів або екстракторів. -tutorial.pumpPlace.text = Насоси працюють аналогічно свердлам, за винятком того, що вони виробляють рідини замість предметів. Спробуйте встановити насос на [yellow] призначене мастило []. -tutorial.conduitUse.text = Тепер покладіть [orange] трубопровід [], віддаляючись від насоса. -tutorial.conduitUse2.text = І ще кілька ... -tutorial.conduitUse3.text = І ще кілька ... -tutorial.generator.text = Тепер, помістіть блок [orange] ​​базовий генератор енергії [] в кінці каналу. -tutorial.generatorExplain.text = Цей генератор тепер створить [yellow] енергію [] від масла. -tutorial.lasers.text = Потужність розподіляється за допомогою [yellow] лазерів потужності []. Поверніть і помістіть його тут. -tutorial.laserExplain.text = Тепер генератор переведе енергію в лазерний блок. Промінь [yellow] непрозорий [] означає, що в даний час він передає потужність, а промінь [yellow] прозорий [] означає, що це не так. -tutorial.laserMore.text = Ви можете перевірити, скільки енергії в блоку, наведіть курсор миші на нього і перевірте [yellow] жовту стрічку [] у верхній частині екрана. -tutorial.healingTurret.text = Цей лазер може бути використаний для живлення турелі для ремонту [lime] []. Помістіть одну тут. -tutorial.healingTurretExplain.text = Поки вона має енергію, ця турель може [lime] відремонтувати блоки. [] Під час гри постарайтеся збудувати одну таку чим швидше! -tutorial.smeltery.text = Для багатьох блоків потрібна [orange] сталь [], для цього потрібна[orange] доминна піч [] . Місце тут. -tutorial.smelterySetup.text = Ця піч буде тепер виробляти [orange] сталь [] із вхідного заліза, використовуючи вугілля як паливо. -tutorial.tunnelExplain.text = Також зауважте, що елементи проходять через [yellow] тунельний блок [] і з'являються з іншого боку, проходячи через кам'яний блок. Майте на увазі, що тунелі можуть проходити лише до 2 блоків. -tutorial.end.text = Ви завершили підручник! Удачі! -text.keybind.title = Ключ перемотки -keybind.move_x.name = move_x -keybind.move_y.name = move_y -keybind.select.name = Вибрати -keybind.break.name = {0}break{/0}{1}; {/1} -keybind.shoot.name = Постріл -keybind.zoom_hold.name = zoom_hold -keybind.zoom.name = Збільшити -keybind.block_info.name = Інформація про блок -keybind.menu.name = Меню -keybind.pause.name = Пауза -keybind.dash.name = Тире -keybind.chat.name = Чат -keybind.player_list.name = Список гравців -keybind.console.name = // Консоль 1 -keybind.rotate_alt.name = rotate_alt -keybind.rotate.name = Повернути -keybind.weapon_1.name = Зброя! -keybind.weapon_2.name = Зброя! -keybind.weapon_3.name = Зброя! -keybind.weapon_4.name = Зброя! -keybind.weapon_5.name = Зброя! -keybind.weapon_6.name = Зброя! -mode.waves.name = Хвилі -mode.sandbox.name = Пісочниця -mode.freebuild.name = Вільний режим -upgrade.standard.name = Стандартний -upgrade.standard.description = Стандартний механ. -upgrade.blaster.name = Бластер -upgrade.blaster.description = Стріляє повільно, слабкі кулі. -upgrade.triblaster.name = Трипластер -upgrade.triblaster.description = Вистрілює 3 кулі в розповсюдженні. -upgrade.clustergun.name = Касетна гармата -upgrade.clustergun.description = Вистрілює неточними вибуховими гранатами. -upgrade.beam.name = Пушечна гармата -upgrade.beam.description = Вистрілює далекобійним,пробірний лазерний промінь. -upgrade.vulcan.name = Вулкан -upgrade.vulcan.description = Вистрілює шквал швидких куль. -upgrade.shockgun.name = Шок-пушка -upgrade.shockgun.description = Стріляє руйнівним вибухом заряженої шрапнелі. -item.stone.name = Камінь -item.iron.name = Залізо -item.coal.name = Вугівалля -item.steel.name = Сталь -item.titanium.name = Титан -item.dirium.name = Дириум -item.thorium.name = Уран -item.sand.name = Пісок -liquid.water.name = Вода -liquid.plasma.name = Плазма -liquid.lava.name = Лава -liquid.oil.name = Нафта -block.weaponfactory.name = Фабрика зброї -block.weaponfactory.fulldescription = Використовується для створення зброї для гравця mech. Натисніть, щоб використати. Автоматично приймає ресурси з основного ядра. -block.air.name = Повітря -block.blockpart.name = Блокчастина -block.deepwater.name = Глибока вода -block.water.name = Вода -block.lava.name = Лава -block.oil.name = Нафта -block.stone.name = Камінь -block.blackstone.name = Чорний камінь -block.iron.name = Залізо -block.coal.name = Вугілля -block.titanium.name = Титан -block.thorium.name = Уран -block.dirt.name = Бруд -block.sand.name = Пісок -block.ice.name = Лід -block.snow.name = Сніг -block.grass.name = Трава -block.sandblock.name = Блок піску -block.snowblock.name = Блок снігу -block.stoneblock.name = Блок камню -block.blackstoneblock.name = Блок чорного камню -block.grassblock.name = Блок бруду -block.mossblock.name = Моссблок -block.shrub.name = Чагарник -block.rock.name = Камень -block.icerock.name = Ледяний камень -block.blackrock.name = Чорний камінь -block.dirtblock.name = Блок землі -block.stonewall.name = Кам'яна стіна -block.stonewall.fulldescription = Недорогий захисний блок. Корисно для захисту ядра та турелі в перші кілька хвиль. -block.ironwall.name = Залізна стіна -block.ironwall.fulldescription = Основний захисний блок. Забезпечує захист від ворогів. -block.steelwall.name = Сталева стіна -block.steelwall.fulldescription = Стандартний захисний блок. адекватний захист від ворогів. -block.titaniumwall.name = Титанова стіна -block.titaniumwall.fulldescription = Сильний захисний блок. Забезпечує захист від ворогів. -block.duriumwall.name = Діріумова стіна -block.duriumwall.fulldescription = Дуже сильний захисний блок. Забезпечує захист від ворогів. -block.compositewall.name = Композитна стіна -block.steelwall-large.name = Велика сталева стіна -block.steelwall-large.fulldescription = Стандартний захисний блок. Поєднує в собі кілька блоків. -block.titaniumwall-large.name = Велика титанова стіна -block.titaniumwall-large.fulldescription = Сильний захисний блок. Поєднує в собі кілька блоків. -block.duriumwall-large.name = Велика дирмітова стіна -block.duriumwall-large.fulldescription = Дуже сильний захисний блок.Поєднує в собі кілька блоків. -block.titaniumshieldwall.name = Стіна з щитом -block.titaniumshieldwall.fulldescription = Сильний захисний блок з додатковим вбудованим щитом. Потрібна енергія. Використовує енергію для поглинання ворожих куль. Рекомендується використовувати силові пристосування для забезпечення енергії цього блоку. -block.repairturret.name = Ремонтна турель -block.repairturret.fulldescription = Ремонтує недалекі пошкодженні блоки.Повільний темп. Використовує невелику кількість енергії. -block.megarepairturret.name = Ремонтна турель II -block.megarepairturret.fulldescription = Ремонтує недалекі пошкодженні блоки.Збільшений радіус та швидший темп ремонту . Використовує багато енергії. -block.shieldgenerator.name = Генератор щиту -block.shieldgenerator.fulldescription = Передовий захисний блок. Захищає всі блоки в радіусі від нападу. Не вкористовує енергію при бездіяльності, але швидко витрачає енергію на захист від куль. -block.door.name = Двері -block.door.fulldescription = Блок, який можна відкрити та закрити, торкнувшись його. -block.door-large.name = Великі двері -block.door-large.fulldescription = Блок, який можна відкрити та закрити, торкнувшись його. -block.conduit.name = Трубопровід -block.conduit.fulldescription = Основний транспортний блок. Працює як конвеєр, але з рідинами. Найкраще використовується з насосами або іншими трубопроводами. Може використовуватися як міст через рідини для ворогів та гравців. -block.pulseconduit.name = Імпульсний канал -block.pulseconduit.fulldescription = Покращенний блок перевезення рідин. Транспортує рідини швидше і зберігає більше стандартних каналів. -block.liquidrouter.name = маршрутизатор для рідини -block.liquidrouter.fulldescription = Працює аналогічно маршрутизатору. Приймає рідину ввід з одного боку і виводить його на інші сторони. Корисний для розщеплення рідини з одного каналу на кілька інших трубопроводів. -block.conveyor.name = Конвеєр -block.conveyor.fulldescription = Базовий транспортний блок. Переміщує предмети вперед і автоматично вкладає їх у турелі або ремісники. Поворотний Може використовуватися як міст через рідину для ворогів та гравців. -block.steelconveyor.name = Сталевий конвеєр -block.steelconveyor.fulldescription = Розширений блок транспортування предметів. Переміщення елементів швидше, ніж стандартні конвеєри. -block.poweredconveyor.name = Імпульсний конвеєр -block.poweredconveyor.fulldescription = Кінцевий транспортний блок. Переміщення елементів швидше, ніж сталеві конвеєри. -block.router.name = Маршрутизатор -block.router.fulldescription = Приймає елементи з одного напрямку і виводить їх на 3 інших напрямках. Можна також зберігати певну кількість предметів. Використовується для розщеплення матеріалів з одного свердла на декілька башточок. -block.junction.name = Міст -block.junction.fulldescription = Виступає як міст для двох перехресних конвеєрних стрічок. Корисне у ситуаціях з двома різними конвеєрами, що несуть різні матеріали в різних місцях. -block.conveyortunnel.name = Конвеєрний тунель -block.conveyortunnel.fulldescription = Транспортує предмети під блоками. Щоб використати, помістіть один тунель, що веде у блок, щоб бути підсвіченим, а один - з іншого боку. Переконайтеся, що обидва тунелі стикаються з протилежними напрямками, тобто до блоків, які вони вводять або виводять. -block.liquidjunction.name = Міст для рідини -block.liquidjunction.fulldescription = Діє як міст для двох перехресних трубопроводів. Корисно в ситуаціях з двома різними трубами, що несуть різні рідини в різних місцях. -block.liquiditemjunction.name = Перехрестя рідкого пункту -block.liquiditemjunction.fulldescription = Виступає як міст для перетину трубопроводів і конвеєрів. -block.powerbooster.name = Підсилювач потужності -block.powerbooster.fulldescription = Поширює енергію на всі блоки в межах його радіуса. -block.powerlaser.name = Енергетичний лазер -block.powerlaser.fulldescription = Створює лазер, який передає енергію блоку перед ним. Не створює жодної сили сама. Найкраще використовується з генераторами або іншими лазерами. -block.powerlaserrouter.name = Лазерний маршрутизатор -block.powerlaserrouter.fulldescription = Лазер, який розподіляє енергію у три напрямки одночасно. Корисно в ситуаціях, коли потрібно живити кілька блоків від одного генератора. -block.powerlasercorner.name = Лазерний кут -block.powerlasercorner.fulldescription = Лазер, який розподіляє енергію одночасно на два напрямки. Корисно в ситуаціях, коли потрібно живити кілька блоків від одного генератора, а маршрутизатор неточний. -block.teleporter.name = Телепорт -block.teleporter.fulldescription = Продвинутий блок транспортування предметів.Щоб телепортувати предмети з одного місця в інше потрібно збудувати 2 телепорти і назначити на них одинаковий колір. Використовує енергію. Натисніть, щоб змінити колір. -block.sorter.name = Сортувальник -block.sorter.fulldescription = Сортує предмети за типом матеріалу. Матеріал для прийняття позначається кольором у блоці. Всі елементи, що відповідають матеріалу сортування, виводяться вперед, а все інше виводить ліворуч і праворуч. -block.core.name = Ядро -block.pump.name = Насос -block.pump.fulldescription = Насоси рідини з вихідного блоку - зазвичай вода, лава чи олія. Виводить рідину в сусідні трубопроводи. -block.fluxpump.name = Флюсовий насос -block.fluxpump.fulldescription = Розширений варіант насоса. Зберігає більше рідини та перекачує швидше. -block.smelter.name = Плавильня -block.smelter.fulldescription = Основний ремісничий блок. Коли вводиться 1 залізо та 1 вугілля в якості палива, виводить одну сталь. Рекомендується вводити залізо та вугілля на різних поясах, щоб запобігти засміченню. -block.crucible.name = Тигель -block.crucible.fulldescription = Розширений блок обробки. При введенні 1 титану, 1 сталі та 1 вугілля в якості пального, виводить один дирний. Рекомендується вводити вугілля, сталь та титан на різних поясах, щоб запобігти засміченню. -block.coalpurifier.name = вугільний екстрактор -block.coalpurifier.fulldescription = Основний екстрактор. Виходить вугілля при постачанні великої кількості води та каменю. -block.titaniumpurifier.name = Титановий екстрактор -block.titaniumpurifier.fulldescription = Стандартний блок екстрактора. Виходить титан при постачанні великої кількості води та заліза. -block.oilrefinery.name = Нафтопереробний завод -block.oilrefinery.fulldescription = Очищує велику кількість нафти і перетворює на вугілля. Корисний для заправки вугільних башточок, коли вугільні родовища є дефіцитними. -block.stoneformer.name = Кам'янний екстрактор -block.stoneformer.fulldescription = Здавлюється рідка лава в камінь. Корисно для виготовлення великої кількості каменю для очищувачів вугілля. -block.lavasmelter.name = Лавовий завод -block.lavasmelter.fulldescription = Використовує лаву для перетворення залізо на сталь. Альтернатива плавильні. Корисно в ситуаціях, коли вугілля є дефіцитним. -block.stonedrill.name = Кам'янна свердловина -block.stonedrill.fulldescription = Основна свердловина.Розміщюється на кам'яній плитці виводить камінь повільними темпами. -block.irondrill.name = Залізна свердловина -block.irondrill.fulldescription = Базова свердловина.Розміщюється на родовищі залізної руди, випускає залізо в повільному темпі. -block.coaldrill.name = Вугільна свердловина -block.coaldrill.fulldescription = Базова свердловина.Розміщюється на родовищі вугільної руди ,видобуває вугілля повільними темпами. -block.thoriumdrill.name = Уранова свердловина -block.thoriumdrill.fulldescription = Продвинута свердловина. Розміщюється на родовищі уранової руди.Видобуток урану відбувається повільними темпами. -block.titaniumdrill.name = Титанова свердловина -block.titaniumdrill.fulldescription = Продвинута свердловина.Розміщюється на родовищі титанової руди, Видобуток титану відбувається повільним темпом. -block.omnidrill.name = Убер свердловина -block.omnidrill.fulldescription = Кінцева свердловина.Дуже швидко видобуває будь-який вид руди. -block.coalgenerator.name = Вугільний генератор -block.coalgenerator.fulldescription = Основний генератор. Генерує енергію з вугілля. Виводиться потужність лазерів на 4 сторони. -block.thermalgenerator.name = Теплогенератор -block.thermalgenerator.fulldescription = Генерує енергію від лави. Виводиться потужність лазерів на 4 сторони. -block.combustiongenerator.name = Генератор горіння -block.combustiongenerator.fulldescription = Генерує енергію з нафти. Виводиться потужність лазерів на 4 сторони. -block.rtgenerator.name = RTG генератор -block.rtgenerator.fulldescription = Генерує невелику кількість енергії з радіоактивного розпаду урану. Виводиться потужність лазерів на 4 сторони. -block.nuclearreactor.name = Ядерний реактор -block.nuclearreactor.fulldescription = Розширений варіант RTG Generator і кінцевий генератор електроенергії. Генерує енергію з урану. Потребує постійного водяного охолодження.Сильно вибухне якщо не буде постачання води у великії кількості -block.turret.name = Турель -block.turret.fulldescription = Базова, дешева турель. Використовує камінь для боєприпасів. Має трохи більше діапазону, ніж подвійна турель. -block.doubleturret.name = Подвійна турель -block.doubleturret.fulldescription = Дещо потужна версія турель. Використовує камінь для боєприпасів. Значно більший урон, але менший діапазон. Вистрілює дві кулі. -block.machineturret.name = Кулеметна турель -block.machineturret.fulldescription = Стандартна всеосяжна турель. Використовує залізо для боєприпасів. Має швидку швидкість пострілу і гідну шкоду. -block.shotgunturret.name = Розріджуюча турель -block.shotgunturret.fulldescription = Стандартна турель. Використовує залізо для боєприпасів. Вистрілює 7 куль навколо себе.Наносить значних ушкоджень,звісно якщо поцілить :) -block.flameturret.name = Вогнемет -block.flameturret.fulldescription = Продвинута турель ближнього діапазону. Використовує вугілля для боєприпасів. Має дуже низький радіус, але дуже високий збиток. Добре для близьких дистанцій. Рекомендується використовувати за стінами. -block.sniperturret.name = Лазерна турель. -block.sniperturret.fulldescription = Продвинута далекобійна турель. Використовує сталь для боєприпасів. Дуже високий збиток, але низький рівень урону. Дорогі для використання, але можуть бути розташовані далеко від ліній ворога через його радіус. -block.mortarturret.name = Флак турель -block.mortarturret.fulldescription = Продвинута,неточна турель. Використовує вугілля для боєприпасів. Стріляє кулями, що вибухають у шрапнеллв. Корисне для великих натовпів ворогів. -block.laserturret.name = Лазерна турель -block.laserturret.fulldescription = Продвинута однопушечна турель. Використовує енергію. Хороша на середніх дистанціях. Ніколи не пропускає. -block.waveturret.name = Тесла -block.waveturret.fulldescription = Передова багатоцільова турель. Використовує енергію. Середній радіус. Ніколи не пропускає. Активно знижує, але може вражати декількох ворогів одночасно з ланцюговим освітленням. -block.plasmaturret.name = Плазмова турель -block.plasmaturret.fulldescription = Дуже продвинута версія Вогнеметної турелі. Використовує вугілля як боєприпаси. Дуже високий урон, від близької до середньої дистанції. -block.chainturret.name = Уранова турель -block.chainturret.fulldescription = Остаточна швидкістна вежа. Використовує уран як боєприпаси. Вистрілює великі кулі при високій швидкості вогню. Середній радіус. Промінь кілька плиток. Надзвичайно жорсткий. -block.titancannon.name = Титанова гармата -block.titancannon.fulldescription = Найбільш далекобійна турель. Використовує уран як боєприпаси. Вистрілює великі снаряди. Далекобійний. Промінь кілька плиток. Надзвичайно жорсткий. -block.playerspawn.name = Спавн Гравця -block.enemyspawn.name = Спавн ворогів +text.quit=Вийти +text.about.button=Про +text.name=Назва: +text.public=Публічний +text.players={0} гравців онлайн +text.server.player.host={0} (host) +text.players.single={0} гравців онлайн +text.server.mismatch=Пакетна помилка: невідповідність версії версії клієнта / сервера. Переконайтеся, що ви та хост мають останню версію Mindustry! +text.server.closing=[accent] Закриття сервера ... +text.server.kicked.kick=Ви були вигнані з сервера! +text.server.kicked.invalidPassword=Невірний пароль! +text.server.kicked.clientOutdated=Застарілий клієнт! Оновіть свою гру! +text.server.kicked.serverOutdated=Застарілий сервер! Попросіть хост оновити! +text.server.connected={0} приєднався. +text.server.disconnected={0} від'єднано. +text.nohost=Неможливо розмістити сервер на власній карті! +text.hostserver=Хост-сервер +text.host=Хост +text.hosting=[accent] Відкриття сервера ... +text.hosts.refresh=Оновити +text.hosts.discovering=Знайомство з мережевими іграми +text.server.refreshing=Оновити сервери +text.hosts.none=[lightgray] Ніяких ігор у мережі не знайдено! +text.host.invalid=[scarlet] Неможливо підключитися до хосту. +text.server.friendlyfire=Дружній вогонь +text.server.add=Додати сервер +text.server.delete=Ви впевнені, що хочете видалити цей сервер? +text.server.hostname=Хост: {0} +text.server.edit=Редагувати сервер +text.joingame.byip=[] Приєднатися по IP ...[] +text.joingame.title=Приєднатися до гри +text.joingame.ip=IP +text.disconnect=Роз'єднано +text.connecting=[accent] Підключення ... +text.connecting.data=[accent] Завантаження світових даних ... +text.connectfail=[crimson] Не вдалося підключитися до сервера: [orange] {0} +text.server.port=Порт +text.server.addressinuse=Адреса вже використовується! +text.server.invalidport=Недійсний номер порту. +text.server.error=[crimson] Помилка хостингу сервера: [orange] {0} +text.tutorial.back=< Попер. +text.tutorial.next=Далі > +text.save.new=Нове збереження +text.save.overwrite=Ви впевнені, що хочете перезаписати цей слот для збереження? +text.overwrite=Перезаписати +text.save.none=Не знайдено жодних збережень! +text.saveload=[accent] Збереження ... +text.savefail=Не вдалося зберегти гру! +text.save.delete.confirm=Ви впевнені, що хочете видалити це збереження? +text.save.delete=Видалити +text.save.export=Експорт збереження +text.save.import.invalid=[orange] Це збереження недійсне! +text.save.import.fail=[crimson] Не вдалося імпортувати збереження: [orange] {0} +text.save.export.fail=[crimson] Не вдалося експортувати збереження: [orange] {0} +text.save.import=Імпортувати збереження +text.save.newslot=Назва збереження: +text.save.rename=Переіменувати +text.save.rename.text=Нова назва: +text.selectslot=Виберіть збереження. +text.slot=[accent] слот {0} +text.save.corrupted=[orange] Збережений файл пошкоджений або він невірний! +text.empty=<порожньо> +text.on=Увімкнути +text.off=Вимкнути +text.save.autosave=Автозбереження: {0} +text.save.map=Карта +text.save.wave=Хвиля {0} +text.save.difficulty=Складність +text.save.date=Останнє збережено: {0} +text.confirm=Підтвердити +text.delete=Видалити +text.ok=ОК +text.open=Відкрити +text.cancel=Скасувати +text.openlink=Відкрити посилання +text.back=Назад +text.quit.confirm=Ти впевнений що хочеш піти? +text.loading=[accent] Завантаження ... +text.wave=[orange] хвиля {0} +text.wave.waiting=Хвиля через {0} +text.waiting=Очікування… +text.enemies={0} Вороги +text.enemies.single=Противник +text.loadimage=Завантажити зображення +text.saveimage=Зберегти зображення +text.editor.badsize=[orange] Недійсні розміри зображення! [] Дійсні розміри карти: {0} +text.editor.errorimageload=Помилка завантаження файлу зображень: [orange] {0} +text.editor.errorimagesave=Помилка збереження файлу зображення: [orange] {0} +text.editor.generate=Генератор +text.editor.resize=Змінити розмір +text.editor.loadmap=// Завантажити карту +text.editor.savemap=Зберегти карту +text.editor.loadimage=Завантажити зображення +text.editor.saveimage=Зберегти зображення +text.editor.unsaved=[scarlet] У вас є незбережені зміни! [] Ви впевнені, що хочете вийти? +text.editor.brushsize=Розмір пензля: {0} +text.editor.noplayerspawn=Ця карта не має ігрового поля для гравця! +text.editor.manyplayerspawns=Карти не можуть мати більше одного ігрового поля для гравців! +text.editor.manyenemyspawns=Не може бути більше ніж {0} ворожих точок! +text.editor.resizemap=Змінити розмір карти +text.editor.resizebig=[scarlet] Попередження! [] Карти, розмір яких перевищує 256 одиниць, можуть виснути і можуть бути нестабільними. +text.editor.mapname=Назва карти: +text.editor.overwrite=[accent] Попередження! Це перезаписує існуючу карту. +text.editor.selectmap=Виберіть карту для завантаження: +text.width=Ширина +text.height=Висота +text.randomize=Рандомізувати +text.apply=Застосувати +text.update=Оновити +text.menu=Меню +text.play=Відтворити +text.load=Завантаження +text.save=Зберегти +text.language.restart=Будь ласка, перезапустіть свою гру, щоб налаштування мови набули чинності. +text.settings.language=Мова +text.settings=Налаштування +text.tutorial=Навчальний\nпосібник +text.editor=Редактор +text.mapeditor=Редактор карт +text.donate=Підтримати проект +text.settings.reset=Скинути до стандартних +text.settings.controls=Елементи управління +text.settings.game=Гра +text.settings.sound=Звук +text.settings.graphics=Графіка +text.upgrades=Оновлення +text.purchased=[LIME] Створено! +text.weapons=Зброя +text.paused=Пауза +text.info.title=[accent] інформація +text.error.title=[crimson] Виникла помилка +text.error.crashmessage=[SCARLET] Виникла несподівана помилка, що призвела до збою. [] Будь ласка, повідомте про конкретні обставини, розробнику: [ORANGE] anukendev@gmail.com [] +text.error.crashtitle=Виникла помилка +text.blocks.blockinfo=Блокування інформації +text.blocks.powercapacity=Потужність +text.blocks.powershot=Потужність / постріл +text.blocks.size=Розмір +text.blocks.liquidcapacity=Ємкість рідини +text.blocks.maxitemssecond=Макс. Елементи / секунду +text.blocks.powerrange=Радіус потужності +text.blocks.itemcapacity=Ємкість предмету +text.blocks.inputliquid=Ввід речовини +text.blocks.inputitem=Вхідний матеріал +text.blocks.explosive=Вибухонебезпечний! +text.blocks.health=Здоров'я +text.blocks.inaccuracy=Неточність +text.blocks.shots=Постріли +text.blocks.inputcapacity=Вхідна ємність +text.blocks.outputcapacity=Випускна ємність +setting.difficulty.easy=Легкий +setting.difficulty.normal=Нормальний +setting.difficulty.hard=Важкий +setting.difficulty.insane=Божевільний +setting.difficulty.purge=Очистити +setting.difficulty.name=Складність +setting.screenshake.name=Тряска екрана +setting.smoothcam.name=Гладка камера +setting.indicators.name=Індикатори ворога +setting.effects.name=Ефекти відображення +setting.sensitivity.name=Чутливість контролера +setting.saveinterval.name=Інтервал автозбереження +setting.seconds={0} секунд +setting.fullscreen.name=Повноекранний +setting.multithread.name=Багатопотоковий [scarlet] (нестабільний!) +setting.fps.name=Показати FPS +setting.vsync.name=VSunc +setting.lasers.name=Показати енергетичні лазери +setting.healthbars.name=Показати здоров'я +setting.pixelate.name=Пікселяція екрану +setting.musicvol.name=Гучність музики +setting.mutemusic.name=Вимкнути музику +setting.sfxvol.name=Гучність ефектів +setting.mutesound.name=Вимкнути звук +map.maze.name=Лабіринт +map.fortress.name=Фортеця +map.sinkhole.name=Свердловина +map.caves.name=Печери +map.volcano.name=Вулкан +map.caldera.name=Кальдера +map.scorch.name=Мертва земля +map.desert.name=Пустеля +map.island.name=Острів +map.grassland.name=Пасовища +map.tundra.name=Тундра +map.spiral.name=Спіраль +map.tutorial.name=Навчання +text.keybind.title=Ключ перемотки +keybind.move_x.name=move_x +keybind.move_y.name=move_y +keybind.select.name=Вибрати +keybind.break.name={0}break{/0}{1}; {/1} +keybind.shoot.name=Постріл +keybind.zoom_hold.name=zoom_hold +keybind.zoom.name=Збільшити +keybind.block_info.name=Інформація про блок +keybind.menu.name=Меню +keybind.pause.name=Пауза +keybind.dash.name=Тире +keybind.chat.name=Чат +keybind.player_list.name=Список гравців +keybind.console.name=// Консоль 1 +keybind.rotate_alt.name=rotate_alt +keybind.rotate.name=Повернути +mode.waves.name=Хвилі +mode.sandbox.name=Пісочниця +mode.freebuild.name=Вільний режим +item.stone.name=Камінь +item.coal.name=Вугівалля +item.titanium.name=Титан +item.thorium.name=Уран +item.sand.name=Пісок +liquid.water.name=Вода +liquid.lava.name=Лава +liquid.oil.name=Нафта +block.door.name=Двері +block.door-large.name=Великі двері +block.conduit.name=Трубопровід +block.pulseconduit.name=Імпульсний канал +block.liquidrouter.name=маршрутизатор для рідини +block.conveyor.name=Конвеєр +block.router.name=Маршрутизатор +block.junction.name=Міст +block.liquidjunction.name=Міст для рідини +block.sorter.name=Сортувальник +block.smelter.name=Плавильня +text.credits=Credits +text.link.discord.description=the official Mindustry discord chatroom +text.link.github.description=Game source code +text.link.dev-builds.description=Unstable development builds +text.link.trello.description=Official trello board for planned features +text.link.itch.io.description=itch.io page with PC downloads and web version +text.link.google-play.description=Google Play store listing +text.link.wiki.description=official Mindustry wiki +text.linkfail=Failed to open link!\nThe URL has been copied to your cliboard. +text.editor.web=The web version does not support the editor!\nDownload the game to use it. +text.web.unsupported=The web version does not support this feature! Download the game to use it. +text.multiplayer.web=This version of the game does not support multiplayer!\nTo play multiplayer from your browser, use the "multiplayer web version" link at the itch.io page. +text.host.web=The web version does not support hosting games! Download the game to use this feature. +text.map.delete=Are you sure you want to delete the map "[orange]{0}[]"? +text.construction.title=Block Construction Guide +text.construction=You've just selected [accent]block construction mode[].\n\nTo begin placing, simply tap a valid location near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Shift the selection[] by holding and dragging any block in the selection.\n- [accent]Place blocks in a line[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel construction or selection[] by pressing the X at the bottom left. +text.deconstruction.title=Block Deconstruction Guide +text.deconstruction=You've just selected [accent]block deconstruction mode[].\n\nTo begin breaking, simply tap a block near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin de-constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Remove blocks in an area[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel deconstruction or selection[] by pressing the X at the bottom left. +text.showagain=Don't show again next session +text.unlocks=Unlocks +text.addplayers=Add/Remove Players +text.maps=Maps +text.maps.none=[LIGHT_GRAY]No maps found! +text.unlocked=New Block Unlocked! +text.unlocked.plural=New Blocks Unlocked! +text.server.rollback=Rollback +text.server.rollback.numberfield=Rollback Amount: +text.blocks.editlogs=Edit Logs +text.block.editlogsnotfound=[red]There are no edit logs for this location. +text.server.kicked.fastShoot=You are shooting too quickly. +text.server.kicked.banned=You are banned on this server. +text.server.kicked.recentKick=You have been kicked recently.\nWait before connecting again. +text.server.kicked.nameInUse=There is someone with that name\nalready on this server. +text.server.kicked.nameEmpty=Your name must contain at least one character or number. +text.server.kicked.idInUse=You are already on this server! Connecting with two accounts is not permitted. +text.server.kicked.customClient=This server does not support custom builds. Download an official version. +text.host.info=The [accent]host[] button hosts a server on ports [scarlet]6567[] and [scarlet]6568.[]\nAnybody on the same [LIGHT_GRAY]wifi or local network[] should be able to see your server in their server list.\n\nIf you want people to be able to connect from anywhere by IP, [accent]port forwarding[] is required.\n\n[LIGHT_GRAY]Note: If someone is experiencing trouble connecting to your LAN game, make sure you have allowed Mindustry access to your local network in your firewall settings. +text.join.info=Here, you can enter a [accent]server IP[] to connect to, or discover [accent]local network[] servers to connect to.\nBoth LAN and WAN multiplayer is supported.\n\n[LIGHT_GRAY]Note: There is no automatic global server list; if you want to connect to someone by IP, you would need to ask the host for their IP. +text.trace=Trace Player +text.trace.playername=Player name: [accent]{0} +text.trace.ip=IP: [accent]{0} +text.trace.id=Unique ID: [accent]{0} +text.trace.android=Android Client: [accent]{0} +text.trace.modclient=Custom Client: [accent]{0} +text.trace.totalblocksbroken=Total blocks broken: [accent]{0} +text.trace.structureblocksbroken=Structure blocks broken: [accent]{0} +text.trace.lastblockbroken=Last block broken: [accent]{0} +text.trace.totalblocksplaced=Total blocks placed: [accent]{0} +text.trace.lastblockplaced=Last block placed: [accent]{0} +text.invalidid=Invalid client ID! Submit a bug report. +text.server.bans=Bans +text.server.bans.none=No banned players found! +text.server.admins=Admins +text.server.admins.none=No admins found! +text.server.outdated=[crimson]Outdated Server![] +text.server.outdated.client=[crimson]Outdated Client![] +text.server.version=[lightgray]Version: {0} +text.server.custombuild=[yellow]Custom Build +text.confirmban=Are you sure you want to ban this player? +text.confirmunban=Are you sure you want to unban this player? +text.confirmadmin=Are you sure you want to make this player an admin? +text.confirmunadmin=Are you sure you want to remove admin status from this player? +text.disconnect.data=Failed to load world data! +text.copylink=Copy Link +text.changelog.title=Changelog +text.changelog.loading=Getting changelog... +text.changelog.error.android=[orange]Note that the changelog sometimes does not work on Android 4.4 and below!\nThis is due to an internal Android bug. +text.changelog.error.ios=[orange]The changelog is currently not supported in iOS. +text.changelog.error=[scarlet]Error getting changelog!\nCheck your internet connection. +text.changelog.current=[yellow][[Current version] +text.changelog.latest=[orange][[Latest version] +text.saving=[accent]Saving... +text.unknown=Unknown +text.custom=Custom +text.builtin=Built-In +text.map.delete.confirm=Are you sure you want to delete this map? This action cannot be undone! +text.map.random=[accent]Random Map +text.map.nospawn=This map does not have any cores for the player to spawn in! Add a [ROYAL]blue[] core to this map in the editor. +text.editor.slope=\\ +text.editor.openin=Open In Editor +text.editor.oregen=Ore Generation +text.editor.oregen.info=Ore Generation: +text.editor.mapinfo=Map Info +text.editor.author=Author: +text.editor.description=Description: +text.editor.name=Name: +text.editor.teams=Teams +text.editor.elevation=Elevation +text.editor.saved=Saved! +text.editor.save.noname=Your map does not have a name! Set one in the 'map info' menu. +text.editor.save.overwrite=Your map overwrites a built-in map! Pick a different name in the 'map info' menu. +text.editor.import.exists=[scarlet]Unable to import:[] a built-in map named '{0}' already exists! +text.editor.import=Import... +text.editor.importmap=Import Map +text.editor.importmap.description=Import an already existing map +text.editor.importfile=Import File +text.editor.importfile.description=Import an external map file +text.editor.importimage=Import Terrain Image +text.editor.importimage.description=Import an external map image file +text.editor.export=Export... +text.editor.exportfile=Export File +text.editor.exportfile.description=Export a map file +text.editor.exportimage=Export Terrain Image +text.editor.exportimage.description=Export a map image file +text.editor.overwrite.confirm=[scarlet]Warning![] A map with this name already exists. Are you sure you want to overwrite it? +text.fps=FPS: {0} +text.tps=TPS: {0} +text.ping=Ping: {0}ms +text.settings.rebind=Rebind +text.yes=Yes +text.no=No +text.blocks.targetsair=Targets Air +text.blocks.itemspeed=Units Moved +text.blocks.shootrange=Range +text.blocks.poweruse=Power Use +text.blocks.inputitemcapacity=Input Item Capacity +text.blocks.outputitemcapacity=Input Item Capacity +text.blocks.maxpowergeneration=Max Power Generation +text.blocks.powertransferspeed=Power Transfer +text.blocks.craftspeed=Production Speed +text.blocks.inputliquidaux=Aux Liquid +text.blocks.inputitems=Input Items +text.blocks.outputitem=Output Item +text.blocks.drilltier=Drillables +text.blocks.drillspeed=Base Drill Speed +text.blocks.liquidoutput=Liquid Output +text.blocks.liquiduse=Liquid Use +text.blocks.coolant=Coolant +text.blocks.coolantuse=Coolant Use +text.blocks.inputliquidfuel=Fuel Liquid +text.blocks.liquidfueluse=Liquid Fuel Use +text.blocks.reload=Reload +text.blocks.inputfuel=Fuel +text.blocks.fuelburntime=Fuel Burn Time +text.unit.blocks=blocks +text.unit.powersecond=power units/second +text.unit.liquidsecond=liquid units/second +text.unit.itemssecond=items/second +text.unit.pixelssecond=pixels/second +text.unit.liquidunits=liquid units +text.unit.powerunits=power units +text.unit.degrees=degrees +text.unit.seconds=seconds +text.unit.none= +text.unit.items=items +text.category.general=General +text.category.power=Power +text.category.liquids=Liquids +text.category.items=Items +text.category.crafting=Crafting +text.category.shooting=Shooting +setting.previewopacity.name=Placing Preview Opacity +setting.minimap.name=Show Minimap +mode.text.help.title=Description of modes +mode.waves.description=the normal mode. limited resources and automatic incoming waves. +mode.sandbox.description=infinite resources and no timer for waves. +mode.freebuild.description=limited resources and no timer for waves. +content.item.name=Items +content.liquid.name=Liquids +content.unit-type.name=Units +content.recipe.name=Blocks +item.stone.description=A common raw material. Used for separating and refining into other materials, or melting into lava. +item.tungsten.name=Tungsten +item.tungsten.description=A common, but very useful structure material. Used in drills and heat-resistant blocks such as generators and smelteries. +item.lead.name=Lead +item.lead.description=A basic starter material. Used extensively in electronics and liquid transportation blocks. +item.coal.description=A common and readily available fuel. +item.carbide.name=Carbide +item.carbide.description=A tough alloy made with tungsten and carbon. Used in advanced transportation blocks and high-tier drills. +item.titanium.description=A rare super-light metal used extensively in liquid transportation, drills and aircraft. +item.thorium.description=A dense, radioactive metal used as structural support and nuclear fuel. +item.silicon.name=Silicon +item.silcion.description=An extremely useful semiconductor, with applications in solar panels and many complex electronics. +item.plastanium.name=Plastanium +item.plastanium.description=A light, ductile material used in advanced aircraft and fragmentation ammunition. +item.phase-matter.name=Phase Matter +item.surge-alloy.name=Surge Alloy +item.biomatter.name=Biomatter +item.biomatter.description=A clump of organic mush; used for conversion into oil or as a basic fuel. +item.sand.description=A common material that is used extensively in smelting, both in alloying and as a flux. +item.blast-compound.name=Blast Compound +item.blast-compound.description=A volatile compound used in bombs and explosives. While it can burned as fuel, this is not advised. +item.pyratite.name=Pyratite +item.pyratite.description=An extremely flammable substance used in incendiary weapons. +liquid.cryofluid.name=Cryofluid +text.item.explosiveness=[LIGHT_GRAY]Explosiveness: {0} +text.item.flammability=[LIGHT_GRAY]Flammability: {0} +text.item.radioactivity=[LIGHT_GRAY]Radioactivity: {0} +text.item.fluxiness=[LIGHT_GRAY]Flux Power: {0} +text.item.hardness=[LIGHT_GRAY]Hardness: {0} +text.liquid.heatcapacity=[LIGHT_GRAY]Heat Capacity: {0} +text.liquid.viscosity=[LIGHT_GRAY]Viscosity: {0} +text.liquid.temperature=[LIGHT_GRAY]Temperature: {0} +block.tungsten-wall.name=Tungsten Wall +block.tungsten-wall-large.name=Large Tungsten Wall +block.carbide-wall.name=Carbide Wall +block.carbide-wall-large.name=Large Carbide Wall +block.thorium-wall.name=Thorium Wall +block.thorium-wall-large.name=Large Thorium Wall +block.duo.name=Duo +block.scorch.name=Scorch +block.hail.name=Hail +block.lancer.name=Lancer +block.titanium-conveyor.name=Titanium Conveyor +block.splitter.name=Splitter +block.splitter.description=Outputs items into two opposite directions immediately after they are recieved. +block.router.description=Splits items into all 4 directions. Can store items as a buffer. +block.distributor.name=Distributor +block.distributor.description=A splitter that can split items into 8 directions. +block.sorter.description=Sorts items. If an item matches the selection, it is allowed to pass. Otherwise, the item is outputted to the left and right. +block.overflow-gate.name=Overflow Gate +block.overflow-gate.description=A combination splitter and router that only outputs to the left and right if the front path is blocked. +block.bridgeconveyor.name=Bridge Conveyor +block.bridgeconveyor.description=A conveyor that can go over other blocks, for up to two total blocks. +block.arc-smelter.name=Arc Smelter +block.silicon-smelter.name=Silicon Smelter +block.phase-weaver.name=Phase Weaver +block.pulverizer.name=Pulverizer +block.cryofluidmixer.name=Cryofluid Mixer +block.melter.name=Melter +block.incinerator.name=Incinerator +block.biomattercompressor.name=Biomatter Compressor +block.separator.name=Separator +block.centrifuge.name=Centrifuge +block.power-node.name=Power Node +block.power-node-large.name=Large Power Node +block.battery.name=Battery +block.battery-large.name=Large Battery +block.combustion-generator.name=Combustion Generator +block.turbine-generator.name=Turbine Generator +block.tungsten-drill.name=Tungsten Drill +block.carbide-drill.name=Carbide Drill +block.laser-drill.name=Laser Drill +block.water-extractor.name=Water Extractor +block.cultivator.name=Cultivator +block.dart-ship-factory.name=Dart Ship Factory +block.delta-mech-factory.name=Delta Mech Factory +block.dronefactory.name=Drone Factory +block.repairpoint.name=Repair Point +block.resupplypoint.name=Resupply Point +block.liquidtank.name=Liquid Tank +block.bridgeconduit.name=Bridge Conduit +block.mechanical-pump.name=Mechanical Pump +block.itemsource.name=Item Source +block.itemvoid.name=Item Void +block.liquidsource.name=Liquid Source +block.powervoid.name=Power Void +block.powerinfinite.name=Power Infinite +block.unloader.name=Unloader +block.sortedunloader.name=Sorted Unloader +block.vault.name=Vault +block.wave.name=Wave +block.swarmer.name=Swarmer +block.salvo.name=Salvo +block.ripple.name=Ripple +block.phase-conveyor.name=Phase Conveyor +block.bridge-conveyor.name=Bridge Conveyor +block.plastanium-compressor.name=Plastanium Compressor +block.pyratite-mixer.name=Pyratite Mixer +block.blast-mixer.name=Blast Mixer +block.solidifer.name=Solidifer +block.solar-panel.name=Solar Panel +block.solar-panel-large.name=Large Solar Panel +block.oil-extractor.name=Oil Extractor +block.javelin-ship-factory.name=Javelin Ship factory +block.drone-factory.name=Drone Factory +block.fabricator-factory.name=Fabricator Factory +block.repair-point.name=Repair Point +block.resupply-point.name=Resupply Point +block.pulse-conduit.name=Pulse Conduit +block.phase-conduit.name=Phase Conduit +block.liquid-router.name=Liquid Router +block.liquid-tank.name=Liquid Tank +block.liquid-junction.name=Liquid Junction +block.bridge-conduit.name=Bridge Conduit +block.rotary-pump.name=Rotary Pump +block.nuclear-reactor.name=Nuclear Reactor diff --git a/core/src/io/anuke/mindustry/entities/effect/Fire.java b/core/src/io/anuke/mindustry/entities/effect/Fire.java index 37b559011b..bd2c0f0333 100644 --- a/core/src/io/anuke/mindustry/entities/effect/Fire.java +++ b/core/src/io/anuke/mindustry/entities/effect/Fire.java @@ -94,6 +94,7 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable if(time >= lifetime() || tile == null){ CallEntity.onFireRemoved(getID()); remove(); + return; } TileEntity entity = tile.target().entity; diff --git a/core/src/io/anuke/mindustry/type/Recipe.java b/core/src/io/anuke/mindustry/type/Recipe.java index 50b490e67b..ccfba98f21 100644 --- a/core/src/io/anuke/mindustry/type/Recipe.java +++ b/core/src/io/anuke/mindustry/type/Recipe.java @@ -73,6 +73,11 @@ public class Recipe implements UnlockableContent{ return this; } + @Override + public boolean isHidden() { + return debugOnly; + } + @Override public void displayInfo(Table table) { ContentDisplay.displayRecipe(table, this); diff --git a/core/src/io/anuke/mindustry/world/consumers/Consume.java b/core/src/io/anuke/mindustry/world/consumers/Consume.java index 8a43c31e55..8712ecb175 100644 --- a/core/src/io/anuke/mindustry/world/consumers/Consume.java +++ b/core/src/io/anuke/mindustry/world/consumers/Consume.java @@ -6,7 +6,7 @@ import io.anuke.mindustry.world.meta.BlockStats; public abstract class Consume { private boolean optional; - private boolean update; + private boolean update = true; public Consume optional(boolean optional) { this.optional = optional; diff --git a/core/src/io/anuke/mindustry/world/consumers/ConsumeItems.java b/core/src/io/anuke/mindustry/world/consumers/ConsumeItems.java index f662fcfebb..8680eb6403 100644 --- a/core/src/io/anuke/mindustry/world/consumers/ConsumeItems.java +++ b/core/src/io/anuke/mindustry/world/consumers/ConsumeItems.java @@ -20,9 +20,7 @@ public class ConsumeItems extends Consume { @Override public void update(Block block, TileEntity entity) { - for(ItemStack stack : items){ - entity.items.remove(stack); - } + } @Override diff --git a/core/src/io/anuke/mindustry/world/modules/InventoryModule.java b/core/src/io/anuke/mindustry/world/modules/InventoryModule.java index 9fb13a9a3c..f443a3bf3d 100644 --- a/core/src/io/anuke/mindustry/world/modules/InventoryModule.java +++ b/core/src/io/anuke/mindustry/world/modules/InventoryModule.java @@ -60,7 +60,6 @@ public class InventoryModule extends BlockModule{ return true; } - //TODO optimize! public int total(){ return total; } diff --git a/packer/src/io/anuke/mindustry/BundleLauncher.java b/packer/src/io/anuke/mindustry/BundleLauncher.java index f26ea64ad0..391310179f 100644 --- a/packer/src/io/anuke/mindustry/BundleLauncher.java +++ b/packer/src/io/anuke/mindustry/BundleLauncher.java @@ -1,16 +1,73 @@ package io.anuke.mindustry; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.ObjectMap; +import com.badlogic.gdx.utils.OrderedMap; +import com.badlogic.gdx.utils.PropertiesUtils; import io.anuke.ucore.util.Log; import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; public class BundleLauncher { - public static void main(String[] args){ + public static void main(String[] args) throws Exception{ File file = new File("bundle.properties"); - Paths.get("").forEach(child -> { - Log.info("Directory: {0}", child); + OrderedMap base = new OrderedMap<>(); + PropertiesUtils.load(base, new InputStreamReader(new FileInputStream(file))); + Array removals = new Array<>(); + + Files.walk(Paths.get("")).forEach(child -> { + try { + if (child.getFileName().toString().equals("bundle.properties") || Files.isDirectory(child) || child.toString().contains("output")) return; + + Log.info("Parsing bundle: {0}", child); + + OrderedMap other = new OrderedMap<>(); + PropertiesUtils.load(other, Files.newBufferedReader(child)); + removals.clear(); + + for(String key : other.orderedKeys()){ + if(!base.containsKey(key)){ + removals.add(key); + Log.info("&lr- Removing unused key '{0}'...", key); + } + } + Log.info("&lr{0} keys removed.", removals.size); + for(String s : removals){ + other.remove(s); + } + + int added = 0; + + for(String key : base.orderedKeys()){ + if(!other.containsKey(key)){ + other.put(key, base.get(key)); + added ++; + Log.info("&lc- Adding missing key '{0}'...", key); + } + } + + Path output = child.resolveSibling("output/" + child.getFileName()); + + Log.info("&lc{0} keys added.", added); + Log.info("Writing bundle to {0}", output); + StringBuilder result = new StringBuilder(); + for(ObjectMap.Entry e : other.entries()){ + result.append(e.toString().replace("\\", "\\\\").replace("\n", "\\n")); + result.append("\n"); + } + Files.write(output, result.toString().getBytes()); + //PropertiesUtils.store(other, Files.newBufferedWriter(output), null); + + }catch (IOException e){ + throw new RuntimeException(e); + } }); } From d6969d2c74bd12c745dd7061115975b364828b1c Mon Sep 17 00:00:00 2001 From: Anuken Date: Mon, 9 Jul 2018 11:06:15 -0400 Subject: [PATCH 14/47] Removed unused bundle keys --- core/assets/bundles/bundle.properties | 21 ------------------- core/assets/bundles/bundle_de.properties | 21 ------------------- core/assets/bundles/bundle_es.properties | 21 ------------------- core/assets/bundles/bundle_fr.properties | 21 ------------------- core/assets/bundles/bundle_in_ID.properties | 21 ------------------- core/assets/bundles/bundle_ita.properties | 21 ------------------- core/assets/bundles/bundle_ko.properties | 21 ------------------- core/assets/bundles/bundle_pl.properties | 21 ------------------- core/assets/bundles/bundle_pt_BR.properties | 21 ------------------- core/assets/bundles/bundle_ru.properties | 21 ------------------- core/assets/bundles/bundle_tk.properties | 21 ------------------- core/assets/bundles/bundle_uk_UA.properties | 21 ------------------- .../io/anuke/mindustry/BundleLauncher.java | 2 +- 13 files changed, 1 insertion(+), 253 deletions(-) diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index fc0c41f561..dc37a8a402 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -55,13 +55,7 @@ text.about.button=About text.name=Name: text.unlocked=New Block Unlocked! text.unlocked.plural=New Blocks Unlocked! -text.server.rollback=Rollback -text.server.rollback.numberfield=Rollback Amount: -text.blocks.editlogs=Edit Logs -text.block.editlogsnotfound=[red]There are no edit logs for this location. -text.public=Public text.players={0} players online -text.server.player.host={0} (host) text.players.single={0} player online text.server.mismatch=Packet error: possible client/server version mismatch.\nMake sure you and the host have the\nlatest version of Mindustry! text.server.closing=[accent]Closing server... @@ -118,7 +112,6 @@ text.confirmban=Are you sure you want to ban this player? text.confirmunban=Are you sure you want to unban this player? text.confirmadmin=Are you sure you want to make this player an admin? text.confirmunadmin=Are you sure you want to remove admin status from this player? -text.joingame.byip=Join by IP... text.joingame.title=Join Game text.joingame.ip=IP: text.disconnect=Disconnected. @@ -130,8 +123,6 @@ text.server.port=Port: text.server.addressinuse=Address already in use! text.server.invalidport=Invalid port number! text.server.error=[crimson]Error hosting server: [orange]{0} -text.tutorial.back=< Prev -text.tutorial.next=Next > text.save.new=New Save text.save.overwrite=Are you sure you want to overwrite\nthis save slot? text.overwrite=Overwrite @@ -226,21 +217,13 @@ text.editor.exportimage.description=Export a map image file text.editor.loadimage=Import Terrain text.editor.saveimage=Export Terrain text.editor.unsaved=[scarlet]You have unsaved changes![]\nAre you sure you want to exit? -text.editor.brushsize=Brush size: {0} -text.editor.noplayerspawn=This map has no player spawnpoint! -text.editor.manyplayerspawns=Maps cannot have more than one\nplayer spawnpoint! -text.editor.manyenemyspawns=Cannot have more than\n{0} enemy spawnpoints! text.editor.resizemap=Resize Map -text.editor.resizebig=[scarlet]Warning!\n[]Maps larger than 256 units may be laggy and unstable. text.editor.mapname=Map Name: text.editor.overwrite=[accent]Warning!\nThis overwrites an existing map. text.editor.overwrite.confirm=[scarlet]Warning![] A map with this name already exists. Are you sure you want to overwrite it? text.editor.selectmap=Select a map to load: text.width=Width: text.height=Height: -text.randomize=Randomize -text.apply=Apply -text.update=Update text.menu=Menu text.play=Play text.load=Load @@ -269,7 +252,6 @@ text.yes=Yes text.no=No text.info.title=[accent]Info text.error.title=[crimson]An error has occured -text.error.crashmessage=[SCARLET]An unexpected error has occured, which would have caused a crash.\n[]Please report the exact circumstances under which this error occured to the developer: \n[ORANGE]anukendev@gmail.com[] text.error.crashtitle=An error has occured text.blocks.blockinfo=Block Info text.blocks.powercapacity=Power Capacity @@ -336,7 +318,6 @@ setting.difficulty.insane=insane setting.difficulty.purge=purge setting.difficulty.name=Difficulty: setting.screenshake.name=Screen Shake -setting.smoothcam.name=Smooth Camera setting.indicators.name=Enemy Indicators setting.effects.name=Display Effects setting.sensitivity.name=Controller Sensitivity @@ -347,10 +328,8 @@ setting.multithread.name=Multithreading setting.fps.name=Show FPS setting.vsync.name=VSync setting.lasers.name=Show Power Lasers -setting.previewopacity.name=Placing Preview Opacity setting.healthbars.name=Show Entity Health bars setting.minimap.name=Show Minimap -setting.pixelate.name=Pixelate Screen setting.musicvol.name=Music Volume setting.mutemusic.name=Mute Music setting.sfxvol.name=SFX Volume diff --git a/core/assets/bundles/bundle_de.properties b/core/assets/bundles/bundle_de.properties index 72efcaad72..23c3f886eb 100644 --- a/core/assets/bundles/bundle_de.properties +++ b/core/assets/bundles/bundle_de.properties @@ -13,7 +13,6 @@ text.joingame=Spiel beitreten text.quit=Verlassen text.about.button=Info text.name=Name: -text.public=Öffentlich text.players={0} Spieler online text.players.single={0} Spieler online text.server.mismatch=Paketfehler: Mögliche Client / Server-Version stimmt nicht überein. Stell sicher, dass du und der Host die neueste Version von Mindustry haben! @@ -34,7 +33,6 @@ text.server.add=Server hinzufügen text.server.delete=Bist du dir sicher das du diesen Server löschen möchtest? text.server.hostname=Host: {0} text.server.edit=Server bearbeiten -text.joingame.byip=Über IP beitreten ... text.joingame.title=Spiel beitreten text.joingame.ip=IP: text.disconnect=Verbindung unterbrochen. @@ -44,8 +42,6 @@ text.connectfail=[crimson] Verbindung zum Server konnte nicht hergestellt werden text.server.port=Port: text.server.invalidport=Falscher Port! text.server.error=[crimson] Fehler beim Hosten des Servers: [orange] {0} -text.tutorial.back=< Zurück -text.tutorial.next=Weiter > text.save.new=Neuer Spielstand text.save.overwrite=Möchten du diesen Spielstand wirklich überschreiben? text.overwrite=Überschreiben @@ -98,20 +94,12 @@ text.editor.savemap=Karte\nspeichern text.editor.loadimage=Bild\nladen text.editor.saveimage=Bild\nspeichern text.editor.unsaved=[crimson] Du hast Änderungen nicht gespeichert [] Möchtest du wirklich aufhören? -text.editor.brushsize=Pinselgrösse: {0} -text.editor.noplayerspawn=Diese Karte hat keinen Spielerspawnpunkt! -text.editor.manyplayerspawns=Maps können nicht mehr als einen Spawnpunkt des Spielers haben! -text.editor.manyenemyspawns=Kann nicht mehr als {0} feindliche Spawnpunkte haben! text.editor.resizemap=Grösse der Karte ändern -text.editor.resizebig=[crimson] Warnung! [] Karten, die grösser als 256 Einheiten sind, können ruckeln und instabil sein. text.editor.mapname=Map Name text.editor.overwrite=[accent] Warnung! Dies überschreibt eine vorhandene Map. text.editor.selectmap=Wähle eine Map zum Laden: text.width=Breite: text.height=Höhe: -text.randomize=Zufällig -text.apply=Anwenden -text.update=Aktualisieren text.menu=Menü text.play=Spielen text.load=Laden @@ -131,7 +119,6 @@ text.purchased=[LIME] Erstellt! text.weapons=Waffen text.paused=Pausiert text.error.title=[crimson] Ein Fehler ist aufgetreten -text.error.crashmessage=[SCARLET] Es ist ein unerwarteter Fehler aufgetreten, der einen Absturz verursacht hätte. [] Bitte geben Sie die genauen Umstände an, unter denen dieser Fehler passiert ist, für den Entwickler: [ORANGE] anukendev@gmail.com [] text.error.crashtitle=EIn Fehler ist aufgetreten! text.blocks.blockinfo=Blockinfo: text.blocks.powercapacity=Energiekapazität @@ -156,7 +143,6 @@ setting.difficulty.insane=Unmöglich setting.difficulty.purge=Auslöschung setting.difficulty.name=Schwierigkeit setting.screenshake.name=Bildschirm wackeln -setting.smoothcam.name=Glatte Kamera setting.indicators.name=Feind Indikatoren setting.effects.name=Effekte anzeigen setting.sensitivity.name=Kontroller Empfindlichkeit @@ -166,7 +152,6 @@ setting.fps.name=Zeige FPS setting.vsync.name=VSync setting.lasers.name=Zeige Energielaser setting.healthbars.name=Zeige Objekt Lebensbalken -setting.pixelate.name=Pixel Bildschirm setting.musicvol.name=Musiklautstärke setting.mutemusic.name=Musik stummschalten setting.sfxvol.name=Audioeffekte Lautstärke @@ -244,11 +229,6 @@ text.maps=Maps text.maps.none=[LIGHT_GRAY]No maps found! text.unlocked=New Block Unlocked! text.unlocked.plural=New Blocks Unlocked! -text.server.rollback=Rollback -text.server.rollback.numberfield=Rollback Amount: -text.blocks.editlogs=Edit Logs -text.block.editlogsnotfound=[red]There are no edit logs for this location. -text.server.player.host={0} (host) text.server.closing=[accent]Closing server... text.server.kicked.fastShoot=You are shooting too quickly. text.server.kicked.clientOutdated=Outdated client! Update your game! @@ -382,7 +362,6 @@ text.category.crafting=Crafting text.category.shooting=Shooting setting.fullscreen.name=Fullscreen setting.multithread.name=Multithreading -setting.previewopacity.name=Placing Preview Opacity setting.minimap.name=Show Minimap text.keybind.title=Rebind Keys keybind.block_info.name=block_info diff --git a/core/assets/bundles/bundle_es.properties b/core/assets/bundles/bundle_es.properties index 33cd83d919..9ee631d3ab 100644 --- a/core/assets/bundles/bundle_es.properties +++ b/core/assets/bundles/bundle_es.properties @@ -25,9 +25,7 @@ text.newgame=Nueva Partida text.quit=Salir text.about.button=Acerca de text.name=Nombre -text.public=Público text.players={0} Jugadores en línea -text.server.player.host={0} ANFITRIÓN text.players.single={0} jugador en línea text.server.mismatch=Error de paquete: posible desajuste de la versión cliente / servidor.\n¡Asegúrate de que tú y el anfitrión tengáis la última versión de Mindustry! text.server.closing=[accent] Cerrando servidor ... @@ -79,7 +77,6 @@ text.confirmban=¿Estás seguro de que quieres prohibir este jugador? text.confirmunban=¿Estás seguro de que quieres desbloquear a este jugador? text.confirmadmin=¿Seguro que quieres que este jugador sea un administrador? text.confirmunadmin=¿Seguro que quieres eliminar el estado de administrador de este reproductor? -text.joingame.byip=Unirse por IP ... text.joingame.title=Unirse a una partida text.joingame.ip=IP: text.disconnect=Desconectado. @@ -91,8 +88,6 @@ text.server.port=Puerto: text.server.addressinuse=¡Dirección ya en uso! text.server.invalidport=¡Número de puerto inválido! text.server.error=[crimson] Error en la creación del servidor: [orange] -text.tutorial.back=< Anterior -text.tutorial.next=Siguiente > text.save.new=Nuevo Guardado text.save.overwrite=¿Seguro que quieres sobrescribir este juego guardado? text.overwrite=Sobreescribir @@ -153,20 +148,12 @@ text.editor.savemap=Guardar mapa text.editor.loadimage=Cargar imagen text.editor.saveimage=Guardar imagen text.editor.unsaved=[scarlet] ¡Tienes cambios sin guardar! [] ¿Estás seguro de que quieres salir? -text.editor.brushsize=Tamaño del pincel: {0} -text.editor.noplayerspawn=¡Este mapa no tiene punto de aparición del jugador! -text.editor.manyplayerspawns=¡Los mapas no pueden tener más de un punto de spawn de jugador! -text.editor.manyenemyspawns={0 }¡No puede tener más de puntos de aparición enemiga! text.editor.resizemap=Cambiar el tamaño del mapa -text.editor.resizebig=[escarlata] ¡Advertencia! [] Los mapas de más de 256 unidades pueden ser inestables. text.editor.mapname=Nombre del mapa text.editor.overwrite=[acento] ¡Advertencia!\nEsto sobrescribe un mapa existente. text.editor.selectmap=Seleccione un mapa para cargar: text.width=Ancho: text.height=Altura: -text.randomize=Aleatorizar -text.apply=Aplicar -text.update=Refrescar text.menu=Menú text.play=Jugar text.load=Cargar @@ -189,7 +176,6 @@ text.weapons=Armas text.paused=Pausado text.info.title=[acento] Información text.error.title=[carmesí] Se ha producido un error -text.error.crashmessage=[SCARLET] Se ha producido un error inesperado, que habría causado un bloqueo. [] Informe las circunstancias exactas bajo las cuales se produjo este error al desarrollador: [ORANGE] anukendev@gmail.com [] text.error.crashtitle=Ha ocurrido un error text.blocks.blockinfo=Información de bloque text.blocks.powercapacity=Capacidad de energía @@ -214,7 +200,6 @@ setting.difficulty.insane=Insano setting.difficulty.purge=Purga setting.difficulty.name=Dificultad: setting.screenshake.name=Shake de pantalla -setting.smoothcam.name=Cámara lisa setting.indicators.name=Indicador del enemigo setting.effects.name=Mostrar efectos setting.sensitivity.name=Sensibilidad del controlador @@ -225,9 +210,7 @@ setting.multithread.name=Multithreading setting.fps.name=Mostrar fps setting.vsync.name=VSync setting.lasers.name=Mostrar láseres de poder -setting.previewopacity.name=Colocando Vista Previa Opacidad setting.healthbars.name=Mostrar barras de vida de enemigos y jugadores -setting.pixelate.name=Pixelear pantalla setting.musicvol.name=Volumen de la música setting.mutemusic.name=Apagar música setting.sfxvol.name=Volumen de los efectos de sonido @@ -301,10 +284,6 @@ text.maps=Maps text.maps.none=[LIGHT_GRAY]No maps found! text.unlocked=New Block Unlocked! text.unlocked.plural=New Blocks Unlocked! -text.server.rollback=Rollback -text.server.rollback.numberfield=Rollback Amount: -text.blocks.editlogs=Edit Logs -text.block.editlogsnotfound=[red]There are no edit logs for this location. text.server.kicked.fastShoot=You are shooting too quickly. text.server.kicked.nameInUse=There is someone with that name\nalready on this server. text.server.kicked.nameEmpty=Your name must contain at least one character or number. diff --git a/core/assets/bundles/bundle_fr.properties b/core/assets/bundles/bundle_fr.properties index e61eee09e0..e1ee0c31a7 100644 --- a/core/assets/bundles/bundle_fr.properties +++ b/core/assets/bundles/bundle_fr.properties @@ -13,9 +13,7 @@ text.joingame=Rejoindre la partie text.quit=Quitter text.about.button=À propos text.name=Nom : -text.public=Publique text.players=joueurs en ligne -text.server.player.host=Héberger text.players.single=joueur en ligne text.server.mismatch=Erreur de paquet: possible incompatibilité de version client/serveur. Assurez-vous que vous et l'hôte avez la dernière version de Mindustry! text.server.closing=[accent]Fermeture du serveur ... @@ -64,7 +62,6 @@ text.confirmban=Êtes-vous sûr de vouloir bannir ce joueur? text.confirmunban=Êtes-vous sûr de vouloir annuler le ban de ce joueur? text.confirmadmin=Êtes-vous sûr de vouloir faire de ce joueur un administrateur? text.confirmunadmin=Êtes-vous sûr de vouloir supprimer le statut d'administrateur de ce joueur? -text.joingame.byip=Rejoindre par IP ... text.joingame.title=Rejoindre une partie text.joingame.ip=IP : text.disconnect=Déconnecté @@ -75,8 +72,6 @@ text.server.port=Port : text.server.addressinuse=Adresse déjà utilisée! text.server.invalidport=Numéro de port incorrect. text.server.error=[crimson]Erreur lors de l'hébergement du serveur: [orange] {0} -text.tutorial.back=< Précédent\n -text.tutorial.next=Suivant> text.save.new=Nouvelle sauvegarde text.save.overwrite=Êtes-vous sûr de vouloir remplacer cette sauvegarde? text.overwrite=Écraser @@ -134,20 +129,12 @@ text.editor.savemap=Enregistrer la carte text.editor.loadimage=Charger l'image text.editor.saveimage=Enregistrer l'image text.editor.unsaved=[scarlet] Vous avez des changements non sauvegardés![] Êtes-vous sûr de vouloir quitter? -text.editor.brushsize=Taille de la brosse: -text.editor.noplayerspawn=Cette carte n'a pas de point d'apparition de joueur! -text.editor.manyplayerspawns=Les cartes ne peuvent pas avoir plus d'un point d'apparition de joueur! -text.editor.manyenemyspawns=Il ne peut pas avoir plus de {0} points d'apparition ennemis! text.editor.resizemap=Redimensionner la carte -text.editor.resizebig=[scarlet]Attention![] Les cartes de plus de 256 unités peuvent laggés et être instables. text.editor.mapname=Nom de la carte: text.editor.overwrite=[accent]Attention! Cela écrasera la carte existante. text.editor.selectmap=Sélectionnez une carte à charger: text.width=Largeur: text.height=Hauteur: -text.randomize=Rendre aléatoire -text.apply=Appliquer -text.update=Modifier text.menu=Menu text.play=Jouer text.load=Charger @@ -170,7 +157,6 @@ text.weapons=Armes text.paused=Pause text.info.title=[accent]Info text.error.title=[crimson]Une erreur est survenue -text.error.crashmessage=[SCARLET]Une erreur inattendue est survenue, et aurait provoqué un crash.[] Veuillez indiquer les circonstances exactes dans lesquelles cette erreur est survenue au développeur:[ORANGE] anukendev@gmail.com[] text.error.crashtitle=Une erreur est survenue text.blocks.blockinfo=Bloquer les infos text.blocks.powercapacity=capacité d'énergie @@ -195,7 +181,6 @@ setting.difficulty.insane=Extreme setting.difficulty.purge=Purge setting.difficulty.name=Difficulté: setting.screenshake.name=Tremblement d'écran -setting.smoothcam.name=Caméra lisse setting.indicators.name=Indicateurs ennemis setting.effects.name=Effets d'affichage setting.sensitivity.name=Sensibilité de la manette @@ -207,7 +192,6 @@ setting.fps.name=Afficher FPS setting.vsync.name=VSync setting.lasers.name=Afficher les rayons des lasers setting.healthbars.name=Afficher les barres de santé des entités -setting.pixelate.name=Pixéliser l'écran setting.musicvol.name=volume musique setting.mutemusic.name=Musique muette setting.sfxvol.name=Volume SFX @@ -289,10 +273,6 @@ text.maps=Maps text.maps.none=[LIGHT_GRAY]No maps found! text.unlocked=New Block Unlocked! text.unlocked.plural=New Blocks Unlocked! -text.server.rollback=Rollback -text.server.rollback.numberfield=Rollback Amount: -text.blocks.editlogs=Edit Logs -text.block.editlogsnotfound=[red]There are no edit logs for this location. text.server.kicked.fastShoot=You are shooting too quickly. text.server.kicked.recentKick=You have been kicked recently.\nWait before connecting again. text.server.kicked.nameInUse=There is someone with that name\nalready on this server. @@ -386,7 +366,6 @@ text.category.liquids=Liquids text.category.items=Items text.category.crafting=Crafting text.category.shooting=Shooting -setting.previewopacity.name=Placing Preview Opacity setting.minimap.name=Show Minimap mode.text.help.title=Description of modes mode.waves.description=the normal mode. limited resources and automatic incoming waves. diff --git a/core/assets/bundles/bundle_in_ID.properties b/core/assets/bundles/bundle_in_ID.properties index 963c0f0eab..9f1ec1436f 100644 --- a/core/assets/bundles/bundle_in_ID.properties +++ b/core/assets/bundles/bundle_in_ID.properties @@ -13,9 +13,7 @@ text.joingame=Bermain Bersama text.quit=Keluar text.about.button=Tentang text.name=Nama: -text.public=Publik text.players={0} pemain online -text.server.player.host={0} (host) text.players.single={0} pemain online text.server.mismatch=Kesalahan paket: kemungkinan versi client / server tidak sesuai.\nPastikan Anda dan host memiliki versi terbaru Mindustry! text.server.closing=[accent]Menutup server... @@ -39,7 +37,6 @@ text.server.add=Tambahkan Server text.server.delete=Yakin ingin menghapus server ini? text.server.hostname=Host: {0} text.server.edit=Sunting Server -text.joingame.byip=Bergabung dengan IP... text.joingame.title=Bermain Bersama text.joingame.ip=IP: text.disconnect=Sambungan terputus. @@ -50,8 +47,6 @@ text.server.port=Port: text.server.addressinuse=Alamat sudah di pakai! text.server.invalidport=Nomor port salah! text.server.error=[crimson]Kesalahan server hosting: [orange]{0} -text.tutorial.back=< Sebelumnya -text.tutorial.next=Berikutnya > text.save.new=Simpan Baru text.save.overwrite=Yakin ingin mengganti slot simpan ini? text.overwrite=Ganti @@ -104,20 +99,12 @@ text.editor.savemap=Simpan Peta text.editor.loadimage=Buka Gambar text.editor.saveimage=Simpan Gambar text.editor.unsaved=[scarlet]Anda memiliki perubahan yang belum disimpan![]\nYakin ingin keluar? -text.editor.brushsize=Ukuran sikat: {0} -text.editor.noplayerspawn=Peta ini tidak memiliki spawnpoint pemain! -text.editor.manyplayerspawns=Peta tidak bisa memiliki lebih dari satu\nspawnpoint pemain! -text.editor.manyenemyspawns=Tidak dapat memiliki lebih dari\n{0} spawnpoint musuh! text.editor.resizemap=Ubah ukuran peta -text.editor.resizebig=[scarlet]Peringatan!\n[]Peta yang lebih besar dari 256 unit mungkin nge-lag dan tidak stabil. text.editor.mapname=Nama Peta: text.editor.overwrite=[accent]Peringatan!\nIni akan mengganti peta yang ada. text.editor.selectmap=Pilih peta yang akan dimuat: text.width=Lebar: text.height=Tinggi: -text.randomize=Acak -text.apply=Terapkan -text.update=Perbarui text.menu=Menu text.play=Main text.load=Buka @@ -140,7 +127,6 @@ text.weapons=Senjata text.paused=Jeda text.info.title=[accent]Info text.error.title=[crimson]Telah terjadi kesalahan -text.error.crashmessage=[SCARLET]Kesalahan tak terduga telah terjadi, yang menyebabkan kerusakan.\n[]Tolong laporkan keadaan yang tepat dimana kesalahan ini terjadi pada pengembang:\n[ORANGE] anukendev@gmail.com[] text.error.crashtitle=Telah terjadi kesalahan text.blocks.blockinfo=Info Blok text.blocks.powercapacity=Kapasitas Tenaga @@ -165,7 +151,6 @@ setting.difficulty.insane=sangat susah setting.difficulty.purge=paling susah setting.difficulty.name=Kesulitan: setting.screenshake.name=Layar Bergoyang -setting.smoothcam.name=Kamera Halus setting.indicators.name=Indikator Musuh setting.effects.name=Efek Tampilan setting.sensitivity.name=Sensitivitas Pengendali @@ -176,7 +161,6 @@ setting.fps.name=Tunjukkan FPS setting.vsync.name=VSync setting.lasers.name=Tampilkan Laser Tenaga setting.healthbars.name=Tampilkan Bar Darah Entitas -setting.pixelate.name=Layar Pixel setting.musicvol.name=Volume Musik setting.mutemusic.name=Bisukan Musik setting.sfxvol.name=Volume Suara @@ -254,10 +238,6 @@ text.maps=Maps text.maps.none=[LIGHT_GRAY]No maps found! text.unlocked=New Block Unlocked! text.unlocked.plural=New Blocks Unlocked! -text.server.rollback=Rollback -text.server.rollback.numberfield=Rollback Amount: -text.blocks.editlogs=Edit Logs -text.block.editlogsnotfound=[red]There are no edit logs for this location. text.server.kicked.fastShoot=You are shooting too quickly. text.server.kicked.banned=You are banned on this server. text.server.kicked.recentKick=You have been kicked recently.\nWait before connecting again. @@ -382,7 +362,6 @@ text.category.items=Items text.category.crafting=Crafting text.category.shooting=Shooting setting.multithread.name=Multithreading -setting.previewopacity.name=Placing Preview Opacity setting.minimap.name=Show Minimap text.keybind.title=Rebind Keys keybind.block_info.name=block_info diff --git a/core/assets/bundles/bundle_ita.properties b/core/assets/bundles/bundle_ita.properties index 9cb1f17c56..93cb8a4f36 100644 --- a/core/assets/bundles/bundle_ita.properties +++ b/core/assets/bundles/bundle_ita.properties @@ -25,9 +25,7 @@ text.newgame=Nuovo gioco text.quit=Esci text.about.button=Informazioni text.name=Nome: -text.public=Pubblico text.players={0} giocatori online -text.server.player.host={0} (host) text.players.single={0} giocatori online text.server.mismatch=Errore nel pacchetto: possibile discrepanza nella versione client / server. Assicurati che tu e l'host abbiate l'ultima versione di Mindustry! text.server.closing=[accent]Chiusura server ... @@ -79,7 +77,6 @@ text.confirmban=Sei sicuro di voler bandire questo giocatore? text.confirmunban=Sei sicuro di voler sbloccare questo giocatore? text.confirmadmin=Sei sicuro di voler rendere questo giocatore un amministratore? text.confirmunadmin=Sei sicuro di voler rimuovere lo stato di amministratore da questo player? -text.joingame.byip=Unisciti a IP ... text.joingame.title=Unisciti alla Partita text.joingame.ip=IP: text.disconnect=Disconnesso. @@ -91,8 +88,6 @@ text.server.port=Porta: text.server.addressinuse=Indirizzo già in uso! text.server.invalidport=Numero di porta non valido! text.server.error=[crimson]Errore nell'hosting del server: [orange] {0} -text.tutorial.back=< Prec -text.tutorial.next=Succ > text.save.new=Nuovo Salvataggio text.save.overwrite=Sei sicuro di voler sovrascrivere questo salvataggio? text.overwrite=Sostituisci @@ -153,20 +148,12 @@ text.editor.savemap=Salva\nla mappa text.editor.loadimage=Carica\nimmagine text.editor.saveimage=Salva\nImmagine text.editor.unsaved=[scarlet]Hai modifiche non salvate![]\nSei sicuro di voler uscire? -text.editor.brushsize=Dimensione del pennello: {0} -text.editor.noplayerspawn=Questa mappa non ha lo spawnpoint del giocatore! -text.editor.manyplayerspawns=Le mappe non possono avere più di un punto di spawn di un giocatore! -text.editor.manyenemyspawns=Non puoi avere più di {0} spawn nemici! text.editor.resizemap=Ridimensiona la mappa -text.editor.resizebig=[Scarlet]Attenzione!\n[]Le mappe più grandi di 256 unità potrebbero causare del lag oltre ad essere instabili. text.editor.mapname=Nome Mappa: text.editor.overwrite=[Accent]Attenzione!\nQuesto sovrascrive una mappa esistente. text.editor.selectmap=Seleziona una mappa da caricare: text.width=Larghezza: text.height=Altezza: -text.randomize=Randomizza -text.apply=Applicare -text.update=Aggiorna text.menu=Menu text.play=Gioca text.load=Carica @@ -189,7 +176,6 @@ text.weapons=Armi text.paused=In pausa text.info.title=[Accent]Informazioni text.error.title=[crimson]Si è verificato un errore -text.error.crashmessage=[SCARLET]Si è verificato un errore imprevisto che ha causato un arresto anomalo.[] Si prega di segnalare le circostanze esatte in cui questo errore si è verificato allo sviluppatore:\n[ORANGE]anukendev@gmail.com[] text.error.crashtitle=Si è verificato un errore text.blocks.blockinfo=Informazioni sul blocco text.blocks.powercapacity=Capacità energetica @@ -214,7 +200,6 @@ setting.difficulty.insane=Folle setting.difficulty.purge=Epurazione setting.difficulty.name=Difficoltà: setting.screenshake.name=Screen Shake -setting.smoothcam.name=Smooth Camera setting.indicators.name=Indicatori nemici setting.effects.name=Visualizza effetti setting.sensitivity.name=Sensibilità del controllore. @@ -226,7 +211,6 @@ setting.fps.name=Mostra FPS setting.vsync.name=Sincronizzazione Verticale setting.lasers.name=Mostra Energia Dei Laser setting.healthbars.name=Mostra barra della salute delle entità -setting.pixelate.name=Schermo Pixelate setting.musicvol.name=Volume Musica setting.mutemusic.name=Musica muta setting.sfxvol.name=Volume SFX @@ -300,10 +284,6 @@ text.maps=Maps text.maps.none=[LIGHT_GRAY]No maps found! text.unlocked=New Block Unlocked! text.unlocked.plural=New Blocks Unlocked! -text.server.rollback=Rollback -text.server.rollback.numberfield=Rollback Amount: -text.blocks.editlogs=Edit Logs -text.block.editlogsnotfound=[red]There are no edit logs for this location. text.server.kicked.fastShoot=You are shooting too quickly. text.server.kicked.nameInUse=There is someone with that name\nalready on this server. text.server.kicked.nameEmpty=Your name must contain at least one character or number. @@ -390,7 +370,6 @@ text.category.liquids=Liquids text.category.items=Items text.category.crafting=Crafting text.category.shooting=Shooting -setting.previewopacity.name=Placing Preview Opacity setting.minimap.name=Show Minimap content.item.name=Items content.liquid.name=Liquids diff --git a/core/assets/bundles/bundle_ko.properties b/core/assets/bundles/bundle_ko.properties index ecdc21feee..3b3f145bd3 100644 --- a/core/assets/bundles/bundle_ko.properties +++ b/core/assets/bundles/bundle_ko.properties @@ -39,13 +39,7 @@ text.about.button=정보 text.name=이름: text.unlocked=새 블록이 잠금 해제되었습니다! text.unlocked.plural=새 블록이 잠금 해제되었습니다! -text.server.rollback=롤백 -text.server.rollback.numberfield=롤백 개수: -text.blocks.editlogs=편집 기록 -text.block.editlogsnotfound=[red]이 위치에 대한 편집 기록이 없습니다. -text.public=공용 text.players={0} 플레이어 온라인 -text.server.player.host=호스트: {0} text.players.single={0} 플레이어 온라인 text.server.mismatch=패킷 오류: 클라이언트와 서버 버전이 일치하지 않습니다.자신이 서버를 호스트하거나 최신 버전을 사용 해 주세요! text.server.closing=[accent]서버 닫는중... @@ -102,7 +96,6 @@ text.confirmban=이 플레이어를 차단하시겠습니까? text.confirmunban=이 플레이어를 차단하시겠습니까? text.confirmadmin=이 플레이어를 관리자로 설정 하시겠습니까? text.confirmunadmin=이 플레이어의 관리자 상태를 해제하시겠습니까? -text.joingame.byip=IP를 입력해서 참가하기... text.joingame.title=게임 참가 text.joingame.ip=IP: text.disconnect=서버와 연결이 해제되었습니다. @@ -114,8 +107,6 @@ text.server.port=포트: text.server.addressinuse=이 주소는 이미 사용중입니다! text.server.invalidport=포트 번호가 잘못되었습니다. text.server.error=[crimson]{0}[orange]서버를 호스팅 하는데 오류가 발생했습니다.[] -text.tutorial.back=< 이전 -text.tutorial.next=다음 > text.save.new=새로 저장\n text.save.overwrite=이 저장 슬롯을 덮어씌우겠습니까?\n text.overwrite=덮어쓰기\n @@ -210,21 +201,13 @@ text.editor.exportimage.description=맵 이미지 파일 내보내기 text.editor.loadimage=지형 가져오기 text.editor.saveimage=지형 내보내기 text.editor.unsaved=[scarlet]변경사항을 저장하지 않았습니다![]\n정말로 나가시겠습니까?\n -text.editor.brushsize=브러쉬 크기 : {0} -text.editor.noplayerspawn=이 맵에는 플레이어의 스폰 지점이 없습니다! -text.editor.manyplayerspawns=맵에는 플레이어 스폰 지점이 둘 이상 있을 수 없습니다! -text.editor.manyenemyspawns={0} 개 이상의 몹 스폰 지점을 설정할 수 없습니다! text.editor.resizemap=맵 크기 조정 -text.editor.resizebig=[scarlet]경고![]맵 크기가 256보다 큰지도는 많은 랙을 유발할 수 있습니다. text.editor.mapname=맵 이름: text.editor.overwrite=[accept]경고!이 명령은 기존 맵을 덮어씌우게 됩니다.\n text.editor.overwrite.confirm=[scarlet]경고![] 이 이름을 가진 맵이 이미 있습니다. 덮어 쓰시겠습니까? text.editor.selectmap=불러올 맵 선택: text.width=넓이: text.height=높이: -text.randomize=무작위 -text.apply=적용 -text.update=업데이트 text.menu=메뉴 text.play=플레이 text.load=불러오기 @@ -251,7 +234,6 @@ text.weapons=무기 text.paused=일시 정지 text.info.title=[accent]정보 text.error.title=[crimson]오류가 발생했습니다. -text.error.crashmessage=[scarlet]예기치 않은 오류가 발생하여 게임이 강제 종료되었습니다![]\n이 오류가 발생한 정확한 상황을 개발자에게 알려주십시오. [ORANGE]anukendev@gmail.com[] text.error.crashtitle=오류가 발생했습니다. text.blocks.blockinfo=블록 정보 text.blocks.powercapacity=최대 전력 용량 @@ -311,7 +293,6 @@ setting.difficulty.insane=미침 setting.difficulty.purge=[#FE2E2E]대한[#2E2EFE]민국 setting.difficulty.name=난이도: setting.screenshake.name=화면 흔들기 -setting.smoothcam.name=부드러운 카메라 setting.indicators.name=적 위치 표시 화살표 setting.effects.name=화면 효과 setting.sensitivity.name=컨트롤러 감도 @@ -322,10 +303,8 @@ setting.multithread.name=멀티 스레딩 setting.fps.name=FPS 표시 setting.vsync.name=VSync setting.lasers.name=파워 레이져 표시 -setting.previewopacity.name=미리보기 블럭 투명도 setting.healthbars.name=몹 체력바 표시 setting.minimap.name=미니맵 보기 -setting.pixelate.name=화면 픽셀화 setting.musicvol.name=음악 크기 setting.mutemusic.name=음소거 setting.sfxvol.name=SFX 볼륨 diff --git a/core/assets/bundles/bundle_pl.properties b/core/assets/bundles/bundle_pl.properties index 17fe566c7e..1336e0650b 100644 --- a/core/assets/bundles/bundle_pl.properties +++ b/core/assets/bundles/bundle_pl.properties @@ -13,9 +13,7 @@ text.joingame=Gra wieloosobowa text.quit=Wyjdź text.about.button=O grze text.name=Nazwa: -text.public=Publiczny text.players={0} graczy online -text.server.player.host={0} (host) text.players.single={0} gracz online text.server.mismatch=Błąd pakietu: możliwa niezgodność wersji klienta/serwera.\nUpewnij się, że Ty i host macie najnowszą wersję Mindustry! text.server.closing=[accent] Zamykanie serwera ... @@ -39,7 +37,6 @@ text.server.add=Dodaj serwer text.server.delete=Czy na pewno chcesz usunąć ten serwer? text.server.hostname=Host: {0} text.server.edit=Edytuj serwer -text.joingame.byip=Dołącz przez IP... text.joingame.title=Dołącz do gry text.joingame.ip=IP: text.disconnect=Rozłączony. @@ -50,8 +47,6 @@ text.server.port=Port: text.server.addressinuse=Adres jest już w użyciu! text.server.invalidport=Nieprawidłowy numer portu. text.server.error=[crimson] Błąd hostowania serwera: [orange] {0} -text.tutorial.back=< Cofnij -text.tutorial.next=Dalej > text.save.new=Nowy zapis text.save.overwrite=Czy na pewno chcesz nadpisać zapis gry? text.overwrite=Nadpisz @@ -104,20 +99,12 @@ text.editor.savemap=Zapisz mapę text.editor.loadimage=Załaduj obraz text.editor.saveimage=Zapisz obraz text.editor.unsaved=[scarlet]Masz niezapisane zmiany![]\nCzy na pewno chcesz wyjść? -text.editor.brushsize=Rozmiar pędzla: {0} -text.editor.noplayerspawn=Ta mapa nie ma ustawionego spawnu gracza! -text.editor.manyplayerspawns=Mapy nie mogą mieć więcej niż jeden punkt spawnu gracza! -text.editor.manyenemyspawns=Nie może mieć więcej niż {0} punktów spawnu wroga! text.editor.resizemap=Zmień rozmiar mapy -text.editor.resizebig=[scarlet]Uwaga![]\nMapy większe niż 256 jednostek mogą przycinać i być niestabilne. text.editor.mapname=Nazwa mapy: text.editor.overwrite=[accent]Uwaga!\nSpowoduje to nadpisanie istniejącej mapy. text.editor.selectmap=Wybierz mapę do załadowania: text.width=Szerokość: text.height=Wysokość: -text.randomize=Wylosuj -text.apply=Zastosuj -text.update=Zaktualizuj text.menu=Menu text.play=Graj text.load=Wczytaj @@ -140,7 +127,6 @@ text.weapons=Bronie text.paused=Wstrzymano text.info.title=[accent]Informacje text.error.title=[crimson]Wystąpił błąd -text.error.crashmessage=[SCARLET]Wystąpił nieoczekiwany błąd, który spowodowałby awarię.[]\nProszę, powiadom dewelopera gry o tym błędzie, pisząc jak do niego doszło: [ORANGE]anukendev@gmail.com[] text.error.crashtitle=Wystąpił błąd text.blocks.blockinfo=Informacje o bloku text.blocks.powercapacity=Moc znamionowa @@ -165,7 +151,6 @@ setting.difficulty.insane=szalony setting.difficulty.purge=Czystka setting.difficulty.name=Poziom trudności setting.screenshake.name=Trzęsienie się ekranu -setting.smoothcam.name=Płynna kamera setting.indicators.name=Wskaźniki wroga setting.effects.name=Wyświetlanie efektów setting.sensitivity.name=Czułość kontrolera @@ -175,7 +160,6 @@ setting.fps.name=Widoczny licznik FPS setting.vsync.name=Synchronizacja pionowa setting.lasers.name=Pokaż lasery zasilające setting.healthbars.name=Pokaż paski zdrowia jednostki -setting.pixelate.name=Rozpikselizowany obraz setting.musicvol.name=Głośność muzyki setting.mutemusic.name=Wycisz muzykę setting.sfxvol.name=Głośność dźwięków @@ -253,10 +237,6 @@ text.maps=Maps text.maps.none=[LIGHT_GRAY]No maps found! text.unlocked=New Block Unlocked! text.unlocked.plural=New Blocks Unlocked! -text.server.rollback=Rollback -text.server.rollback.numberfield=Rollback Amount: -text.blocks.editlogs=Edit Logs -text.block.editlogsnotfound=[red]There are no edit logs for this location. text.server.kicked.fastShoot=You are shooting too quickly. text.server.kicked.banned=You are banned on this server. text.server.kicked.recentKick=You have been kicked recently.\nWait before connecting again. @@ -382,7 +362,6 @@ text.category.crafting=Crafting text.category.shooting=Shooting setting.fullscreen.name=Fullscreen setting.multithread.name=Multithreading -setting.previewopacity.name=Placing Preview Opacity setting.minimap.name=Show Minimap text.keybind.title=Rebind Keys keybind.block_info.name=block_info diff --git a/core/assets/bundles/bundle_pt_BR.properties b/core/assets/bundles/bundle_pt_BR.properties index 873cfa0d5a..04f28bf94a 100644 --- a/core/assets/bundles/bundle_pt_BR.properties +++ b/core/assets/bundles/bundle_pt_BR.properties @@ -46,20 +46,12 @@ text.editor.savemap=Salvar\n Mapa text.editor.loadimage=Carregar\n Imagem text.editor.saveimage=Salvar\nImagem text.editor.unsaved=[scarlet]Você tem alterações não salvas![]\nTem certeza que quer sair? -text.editor.brushsize=Tamanho do pincel: {0} -text.editor.noplayerspawn=Este mapa não tem ponto de spawn para o jogador! -text.editor.manyplayerspawns=Mapas não podem ter mais de um\nponto de spawn para jogador! -text.editor.manyenemyspawns=Não pode haver mais de\n{0} pontos de spawn para inimigos! text.editor.resizemap=Redimensionar Mapa -text.editor.resizebig=[scarlet]Aviso!\n[]Mapas maiores que 256 unidades podem ser 'lentos' e instáveis text.editor.mapname=Nome do Mapa: text.editor.overwrite=[accent]Aviso!\nIsso sobrescreve um mapa existente. text.editor.selectmap=Selecione uma mapa para carregar: text.width=Largura: text.height=Altura: -text.randomize=Aleatório -text.apply=Aplicar -text.update=Atualizar text.menu=Menu text.play=Jogar text.load=Carregar @@ -79,7 +71,6 @@ text.purchased=[LIME]Comprado! text.weapons=Arsenal text.paused=Pausado text.error.title=[crimson]Um erro ocorreu -text.error.crashmessage=[SCARLET]Um erro inesperado aconteceu, que pode ter causado o jogo a fechar. []Por favor, informe as exatas circunstâncias em que o erro ocorreu ao desenvolvidor:\n[ORANGE]anukendev@gmail.com[] text.error.crashtitle=Um erro ocorreu. text.blocks.blockinfo=Informação do Bloco text.blocks.powercapacity=Capacidade de Energia @@ -100,7 +91,6 @@ setting.difficulty.normal=Normal setting.difficulty.hard=Difícil setting.difficulty.name=Dificuldade setting.screenshake.name=Balanço da Tela -setting.smoothcam.name=Câmera suave setting.indicators.name=Indicadores de Inimigos setting.effects.name=Particulas setting.sensitivity.name=Sensibilidade do Controle @@ -108,7 +98,6 @@ setting.fps.name=Mostrar FPS setting.vsync.name=VSync setting.lasers.name=Mostrar lasers setting.healthbars.name=Mostrar barra de saúde de entidades -setting.pixelate.name=Pixelar Tela setting.musicvol.name=Volume da Música setting.mutemusic.name=Desligar Musica setting.sfxvol.name=Volume de Efeitos @@ -189,13 +178,7 @@ text.about.button=About text.name=Name: text.unlocked=New Block Unlocked! text.unlocked.plural=New Blocks Unlocked! -text.server.rollback=Rollback -text.server.rollback.numberfield=Rollback Amount: -text.blocks.editlogs=Edit Logs -text.block.editlogsnotfound=[red]There are no edit logs for this location. -text.public=Public text.players={0} players online -text.server.player.host={0} (host) text.players.single={0} player online text.server.mismatch=Packet error: possible client/server version mismatch.\nMake sure you and the host have the\nlatest version of Mindustry! text.server.closing=[accent]Closing server... @@ -252,7 +235,6 @@ text.confirmban=Are you sure you want to ban this player? text.confirmunban=Are you sure you want to unban this player? text.confirmadmin=Are you sure you want to make this player an admin? text.confirmunadmin=Are you sure you want to remove admin status from this player? -text.joingame.byip=Join by IP... text.joingame.title=Join Game text.joingame.ip=IP: text.disconnect=Disconnected. @@ -264,8 +246,6 @@ text.server.port=Port: text.server.addressinuse=Address already in use! text.server.invalidport=Invalid port number! text.server.error=[crimson]Error hosting server: [orange]{0} -text.tutorial.back=< Prev -text.tutorial.next=Next > text.save.new=New Save text.save.none=No saves found! text.save.delete.confirm=Are you sure you want to delete this save? @@ -382,7 +362,6 @@ setting.saveinterval.name=Autosave Interval setting.seconds={0} Seconds setting.fullscreen.name=Fullscreen setting.multithread.name=Multithreading -setting.previewopacity.name=Placing Preview Opacity setting.minimap.name=Show Minimap text.keybind.title=Rebind Keys keybind.shoot.name=shoot diff --git a/core/assets/bundles/bundle_ru.properties b/core/assets/bundles/bundle_ru.properties index 2e816db671..1d14c9c417 100644 --- a/core/assets/bundles/bundle_ru.properties +++ b/core/assets/bundles/bundle_ru.properties @@ -25,9 +25,7 @@ text.newgame=Новая игра text.quit=Выход text.about.button=Об игре text.name=Название: -text.public=Общие text.players=Игроков на сервере: {0} -text.server.player.host={0} (хост) text.players.single={0} игрок на сервере text.server.mismatch=Ошибка пакета: возможное несоответствие версии клиента / сервера. Убедитесь, что у вас и у создателя сервера установлена ​​последняя версия Mindustry! text.server.closing=[accent]Закрытие сервера... @@ -79,7 +77,6 @@ text.confirmban=Вы действительно хотите заблокиро text.confirmunban=Вы действительно хотите разблокировать этого игрока? text.confirmadmin=Вы уверены, что хотите сделать этого игрока администратором? text.confirmunadmin=Вы действительно хотите удалить статус администратора с этого игрока? -text.joingame.byip=Присоединиться по IP ... text.joingame.title=Присоединиться к игре text.joingame.ip=IP: text.disconnect=Отключён\n @@ -91,8 +88,6 @@ text.server.port=Порт: text.server.addressinuse=Адрес уже используется! text.server.invalidport=Неверный номер порта! text.server.error=[crimson]Ошибка создания сервера: [orange] {0} -text.tutorial.back=<назад -text.tutorial.next=далее> text.save.new=Новое сохранение text.save.overwrite=Вы уверены,что хотите перезаписать этот слот для сохранения? text.overwrite=Перезаписать @@ -154,20 +149,12 @@ text.editor.savemap=Сохранить\nкарту text.editor.loadimage=Загрузить \nизображение text.editor.saveimage=Сохранить \nизображение text.editor.unsaved=[scarlet]У вас есть не сохраненные изменения![] \nВы уверены,что хотите выйти? -text.editor.brushsize=Размер кисти: {0} -text.editor.noplayerspawn=На этой карте нет точки появления игрока! -text.editor.manyplayerspawns=На карте не может быть больше одной точки появления игрока! -text.editor.manyenemyspawns=Не может быть больше {0} вражеских точек появления! text.editor.resizemap=Изменить размер карты -text.editor.resizebig=[scarlet]Внимание! \n[]Карты размером больше 256 единиц могут быть не стабильны и тормозить. text.editor.mapname=Название карты: text.editor.overwrite=[accent]Внимание! \nЭто перезапишет уже существующую карту. text.editor.selectmap=Выберите карту для загрузки: text.width=Ширина: text.height=Высота: -text.randomize=Рандомизировать -text.apply=Применить -text.update=Обновить text.menu=Меню text.play=Играть text.load=Загрузить @@ -190,7 +177,6 @@ text.weapons=Оружие text.paused=Пауза text.info.title=[accent]Информация text.error.title=[crimson]Произошла ошибка -text.error.crashmessage=[SCARLET]Произошла непредвиденная ошибка,которая могла вызвать сбой.[]Пожалуйста, сообщите точные обстоятельства разработчику,при которых эта ошибка возникла : [ORANGE]anukendev@gmail.com[] text.error.crashtitle=Произошла ошибка text.blocks.blockinfo=Информация о блоке text.blocks.powercapacity=Вместимость энергии @@ -215,7 +201,6 @@ setting.difficulty.insane=нереально setting.difficulty.purge=зачистка setting.difficulty.name=Сложность setting.screenshake.name=Дрожание экрана -setting.smoothcam.name=Плавная камера setting.indicators.name=Индикаторы противников setting.effects.name=Эффекты на экране setting.sensitivity.name=Чувствительность контроллера @@ -226,9 +211,7 @@ setting.multithread.name=Многопоточность setting.fps.name=Показать FPS setting.vsync.name=Верт. синхронизация setting.lasers.name=Показывать энергетические лазеры -setting.previewopacity.name=Прозрачность объкта при предв. просм. setting.healthbars.name=Показать полоски здоровья объекта -setting.pixelate.name=Пикселизация экрана setting.musicvol.name=Громкость музыки setting.mutemusic.name=Заглушить музыку setting.sfxvol.name=Громкость звуковых эффектов @@ -303,10 +286,6 @@ text.maps=Maps text.maps.none=[LIGHT_GRAY]No maps found! text.unlocked=New Block Unlocked! text.unlocked.plural=New Blocks Unlocked! -text.server.rollback=Rollback -text.server.rollback.numberfield=Rollback Amount: -text.blocks.editlogs=Edit Logs -text.block.editlogsnotfound=[red]There are no edit logs for this location. text.server.kicked.nameInUse=There is someone with that name\nalready on this server. text.server.kicked.nameEmpty=Your name must contain at least one character or number. text.server.kicked.idInUse=You are already on this server! Connecting with two accounts is not permitted. diff --git a/core/assets/bundles/bundle_tk.properties b/core/assets/bundles/bundle_tk.properties index 493db23aa0..6120e00fd3 100644 --- a/core/assets/bundles/bundle_tk.properties +++ b/core/assets/bundles/bundle_tk.properties @@ -25,9 +25,7 @@ text.newgame=Yeni Oyun text.quit=Çık text.about.button=Hakkında text.name=Adı: -text.public=Herkese açık text.players=1090 oyuncu çevrimiçi -text.server.player.host=Sunucu text.players.single={0} Oyuncu Çevrimiçi text.server.mismatch=Paket hatası: olası istemci / sunucu sürümü uyuşmazlığı. Siz ve ev sahibi Mindustry'nin en son sürümüne sahip olduğunuzdan emin olun! text.server.closing=[accent] Sunucu kapatılıyor ... @@ -79,7 +77,6 @@ text.confirmban=Bu oyuncuyu yasaklamak istediğinizden emin misiniz? text.confirmunban=Bu oyuncunun yasağını kaldırmak istediğinden emin misin? text.confirmadmin=Bu oyuncunun yönetici yapmak istediğinden emin misin? text.confirmunadmin=Bu oyuncudan yönetici durumunu kaldırmak istediğinizden emin misiniz? -text.joingame.byip=IP ile Katılın ... text.joingame.title=Oyuna katılmak text.joingame.ip=IP: text.disconnect=Bağlantı Kesildi @@ -91,8 +88,6 @@ text.server.port=Liman text.server.addressinuse=Adres çoktan kullanımda! text.server.invalidport=Bağlantı noktası numarası geçersiz. text.server.error=[crimson] Sunucu barındırma hatası: [orange] {0} -text.tutorial.back=<Önceki -text.tutorial.next=İleri > text.save.new=6349,Yeni Kayıt text.save.overwrite=Bu kayıt yuvasının üzerine yazmak istediğinizden emin misiniz? text.overwrite=Üzerine Yaz @@ -153,20 +148,12 @@ text.editor.savemap=Harita Kaydet text.editor.loadimage=Resmi yükle text.editor.saveimage=Resmi Kaydet text.editor.unsaved=[scarlet] Kaydedilmemiş değişiklikleriniz var! [] Çıkmak istediğinizden emin misiniz? -text.editor.brushsize=Fırça boyutu: {0} -text.editor.noplayerspawn=Bu haritanın oyuncu spawnpoint'i yok! -text.editor.manyplayerspawns=Haritalar, birden fazla oyuncu spawnpoint'e sahip olamaz! -text.editor.manyenemyspawns={0} düşman spawnpoint {0}'den daha fazlası olamaz! text.editor.resizemap=Haritayı Yeniden Boyutlandır -text.editor.resizebig=[Kızıl] Uyarı! [] 256'dan büyük haritalar yavaş ve dengesiz olabilir. text.editor.mapname=Harita Adı text.editor.overwrite=[Vurgu] Uyarı! Bu mevcut bir haritanın üzerine yazar. text.editor.selectmap=Yüklenecek bir harita seçin: text.width=Genişliği: text.height=Boy: -text.randomize=Rasgele seçmek -text.apply=Uygula -text.update=Güncelle text.menu=Menü text.play=Oyna text.load=Yükle @@ -189,7 +176,6 @@ text.weapons=Silahlar text.paused=Duraklatıldı text.info.title=[Vurgu] Bilgi text.error.title=[crimson] Bir hata oluştu -text.error.crashmessage=[SCARLET] Bir kilitlenme meydana getiren beklenmeyen bir hata oluştu. [] Lütfen geliştiriciye bu hatanın gerçekleştiği koşulları bildirin: [ORANGE] anukendev@gmail.com [] text.error.crashtitle=Bir hata oluştu text.blocks.blockinfo=Blok Bilgisi text.blocks.powercapacity=Güç kapasitesi @@ -214,7 +200,6 @@ setting.difficulty.insane=deli setting.difficulty.purge=tasfiye setting.difficulty.name=Zorluk: setting.screenshake.name=Ekran Sallamak -setting.smoothcam.name=Pürüzsüz kamera setting.indicators.name=Düşman Göstergeleri setting.effects.name=Görüntü Efektleri setting.sensitivity.name=Denetleyici hassasiyeti @@ -226,7 +211,6 @@ setting.fps.name=Saniyede ... Kare göstermek setting.vsync.name=VSync setting.lasers.name=Güç Lazerleri Göster setting.healthbars.name=Varlık Sağlık çubuklarını göster -setting.pixelate.name=Piksel Ekran setting.musicvol.name=Müzik sesi setting.mutemusic.name=Müziği Kapat setting.sfxvol.name=SFX Hacmi @@ -300,10 +284,6 @@ text.maps=Maps text.maps.none=[LIGHT_GRAY]No maps found! text.unlocked=New Block Unlocked! text.unlocked.plural=New Blocks Unlocked! -text.server.rollback=Rollback -text.server.rollback.numberfield=Rollback Amount: -text.blocks.editlogs=Edit Logs -text.block.editlogsnotfound=[red]There are no edit logs for this location. text.server.kicked.fastShoot=You are shooting too quickly. text.server.kicked.nameInUse=There is someone with that name\nalready on this server. text.server.kicked.nameEmpty=Your name must contain at least one character or number. @@ -390,7 +370,6 @@ text.category.liquids=Liquids text.category.items=Items text.category.crafting=Crafting text.category.shooting=Shooting -setting.previewopacity.name=Placing Preview Opacity setting.minimap.name=Show Minimap content.item.name=Items content.liquid.name=Liquids diff --git a/core/assets/bundles/bundle_uk_UA.properties b/core/assets/bundles/bundle_uk_UA.properties index 8764b65c68..ecc5320001 100644 --- a/core/assets/bundles/bundle_uk_UA.properties +++ b/core/assets/bundles/bundle_uk_UA.properties @@ -14,9 +14,7 @@ text.newgame=Нова гра text.quit=Вийти text.about.button=Про text.name=Назва: -text.public=Публічний text.players={0} гравців онлайн -text.server.player.host={0} (host) text.players.single={0} гравців онлайн text.server.mismatch=Пакетна помилка: невідповідність версії версії клієнта / сервера. Переконайтеся, що ви та хост мають останню версію Mindustry! text.server.closing=[accent] Закриття сервера ... @@ -40,7 +38,6 @@ text.server.add=Додати сервер text.server.delete=Ви впевнені, що хочете видалити цей сервер? text.server.hostname=Хост: {0} text.server.edit=Редагувати сервер -text.joingame.byip=[] Приєднатися по IP ...[] text.joingame.title=Приєднатися до гри text.joingame.ip=IP text.disconnect=Роз'єднано @@ -51,8 +48,6 @@ text.server.port=Порт text.server.addressinuse=Адреса вже використовується! text.server.invalidport=Недійсний номер порту. text.server.error=[crimson] Помилка хостингу сервера: [orange] {0} -text.tutorial.back=< Попер. -text.tutorial.next=Далі > text.save.new=Нове збереження text.save.overwrite=Ви впевнені, що хочете перезаписати цей слот для збереження? text.overwrite=Перезаписати @@ -106,20 +101,12 @@ text.editor.savemap=Зберегти карту text.editor.loadimage=Завантажити зображення text.editor.saveimage=Зберегти зображення text.editor.unsaved=[scarlet] У вас є незбережені зміни! [] Ви впевнені, що хочете вийти? -text.editor.brushsize=Розмір пензля: {0} -text.editor.noplayerspawn=Ця карта не має ігрового поля для гравця! -text.editor.manyplayerspawns=Карти не можуть мати більше одного ігрового поля для гравців! -text.editor.manyenemyspawns=Не може бути більше ніж {0} ворожих точок! text.editor.resizemap=Змінити розмір карти -text.editor.resizebig=[scarlet] Попередження! [] Карти, розмір яких перевищує 256 одиниць, можуть виснути і можуть бути нестабільними. text.editor.mapname=Назва карти: text.editor.overwrite=[accent] Попередження! Це перезаписує існуючу карту. text.editor.selectmap=Виберіть карту для завантаження: text.width=Ширина text.height=Висота -text.randomize=Рандомізувати -text.apply=Застосувати -text.update=Оновити text.menu=Меню text.play=Відтворити text.load=Завантаження @@ -142,7 +129,6 @@ text.weapons=Зброя text.paused=Пауза text.info.title=[accent] інформація text.error.title=[crimson] Виникла помилка -text.error.crashmessage=[SCARLET] Виникла несподівана помилка, що призвела до збою. [] Будь ласка, повідомте про конкретні обставини, розробнику: [ORANGE] anukendev@gmail.com [] text.error.crashtitle=Виникла помилка text.blocks.blockinfo=Блокування інформації text.blocks.powercapacity=Потужність @@ -167,7 +153,6 @@ setting.difficulty.insane=Божевільний setting.difficulty.purge=Очистити setting.difficulty.name=Складність setting.screenshake.name=Тряска екрана -setting.smoothcam.name=Гладка камера setting.indicators.name=Індикатори ворога setting.effects.name=Ефекти відображення setting.sensitivity.name=Чутливість контролера @@ -179,7 +164,6 @@ setting.fps.name=Показати FPS setting.vsync.name=VSunc setting.lasers.name=Показати енергетичні лазери setting.healthbars.name=Показати здоров'я -setting.pixelate.name=Пікселяція екрану setting.musicvol.name=Гучність музики setting.mutemusic.name=Вимкнути музику setting.sfxvol.name=Гучність ефектів @@ -261,10 +245,6 @@ text.maps=Maps text.maps.none=[LIGHT_GRAY]No maps found! text.unlocked=New Block Unlocked! text.unlocked.plural=New Blocks Unlocked! -text.server.rollback=Rollback -text.server.rollback.numberfield=Rollback Amount: -text.blocks.editlogs=Edit Logs -text.block.editlogsnotfound=[red]There are no edit logs for this location. text.server.kicked.fastShoot=You are shooting too quickly. text.server.kicked.banned=You are banned on this server. text.server.kicked.recentKick=You have been kicked recently.\nWait before connecting again. @@ -387,7 +367,6 @@ text.category.liquids=Liquids text.category.items=Items text.category.crafting=Crafting text.category.shooting=Shooting -setting.previewopacity.name=Placing Preview Opacity setting.minimap.name=Show Minimap mode.text.help.title=Description of modes mode.waves.description=the normal mode. limited resources and automatic incoming waves. diff --git a/packer/src/io/anuke/mindustry/BundleLauncher.java b/packer/src/io/anuke/mindustry/BundleLauncher.java index 391310179f..8160e39dc5 100644 --- a/packer/src/io/anuke/mindustry/BundleLauncher.java +++ b/packer/src/io/anuke/mindustry/BundleLauncher.java @@ -62,7 +62,7 @@ public class BundleLauncher { result.append(e.toString().replace("\\", "\\\\").replace("\n", "\\n")); result.append("\n"); } - Files.write(output, result.toString().getBytes()); + Files.write(child, result.toString().getBytes()); //PropertiesUtils.store(other, Files.newBufferedWriter(output), null); }catch (IOException e){ From 3d9c9e639d110a1c7599f13c2c0ba233ff892577 Mon Sep 17 00:00:00 2001 From: Anuken Date: Mon, 9 Jul 2018 11:24:24 -0400 Subject: [PATCH 15/47] Automatic external bundle loading --- core/src/io/anuke/mindustry/Vars.java | 2 +- .../io/anuke/mindustry/io/BundleLoader.java | 27 ++++++++++--------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/core/src/io/anuke/mindustry/Vars.java b/core/src/io/anuke/mindustry/Vars.java index 6731aed1dc..d98a9c7d50 100644 --- a/core/src/io/anuke/mindustry/Vars.java +++ b/core/src/io/anuke/mindustry/Vars.java @@ -168,7 +168,7 @@ public class Vars{ for(EntityGroup group : Entities.getAllGroups()){ group.setRemoveListener(entity -> { if(entity instanceof SyncTrait && Net.client()){ - netClient.addRemovedEntity(((SyncTrait) entity).getID()); + netClient.addRemovedEntity((entity).getID()); } }); } diff --git a/core/src/io/anuke/mindustry/io/BundleLoader.java b/core/src/io/anuke/mindustry/io/BundleLoader.java index 1133360838..aa786fd9d2 100644 --- a/core/src/io/anuke/mindustry/io/BundleLoader.java +++ b/core/src/io/anuke/mindustry/io/BundleLoader.java @@ -3,9 +3,10 @@ package io.anuke.mindustry.io; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.utils.I18NBundle; -import io.anuke.mindustry.core.Platform; +import io.anuke.mindustry.Vars; import io.anuke.ucore.core.Core; import io.anuke.ucore.core.Settings; +import io.anuke.ucore.core.Timers; import io.anuke.ucore.util.Log; import java.util.Locale; @@ -13,7 +14,6 @@ import java.util.Locale; import static io.anuke.mindustry.Vars.headless; public class BundleLoader { - private static final boolean externalBundle = false; public static void load(){ Settings.defaults("locale", "default"); @@ -40,20 +40,20 @@ public class BundleLoader { private static void loadBundle(){ I18NBundle.setExceptionOnMissingKey(false); + try { + //try loading external bundle + FileHandle handle = Gdx.files.local("bundle"); - if(externalBundle){ - try { - FileHandle handle = Gdx.files.local("bundle"); + Locale locale = Locale.ENGLISH; + Core.bundle = I18NBundle.createBundle(handle, locale); - Locale locale = Locale.ENGLISH; - Core.bundle = I18NBundle.createBundle(handle, locale); - }catch (Exception e){ - Log.err(e); - Platform.instance.showError("Failed to find bundle!\nMake sure you have bundle.properties in the same directory\nas the jar file.\n\nIf the problem persists, try running it through the command prompt:\n" + - "Hold left-shift, then right click and select 'open command prompt here'.\nThen, type in 'java -jar mindustry.jar' without quotes."); - Gdx.app.exit(); + Log.info("NOTE: external translation bundle has been loaded."); + if(!headless){ + Timers.run(10f, () -> Vars.ui.showInfo("Note: You have successfully loaded an external translation bundle.")); } - }else{ + }catch (Throwable e){ + //no external bundle found + FileHandle handle = Gdx.files.internal("bundles/bundle"); Locale locale = getLocale(); @@ -61,5 +61,6 @@ public class BundleLoader { if(!headless) Log.info("Got locale: {0}", locale); Core.bundle = I18NBundle.createBundle(handle, locale); } + } } From f3cc8819305861b509679dab7bca3b7628ddba7c Mon Sep 17 00:00:00 2001 From: Anuken Date: Mon, 9 Jul 2018 14:25:52 -0400 Subject: [PATCH 16/47] Status bar cleanup / Fixed power distribution / Fixed adjacency updates --- .../src/io/anuke/annotations/Annotations.java | 2 +- .../content/blocks/CraftingBlocks.java | 8 ++--- .../anuke/mindustry/entities/TileEntity.java | 2 ++ .../mindustry/graphics/OverlayRenderer.java | 33 ++++++++++++------- .../mindustry/ui/fragments/DebugFragment.java | 3 ++ .../world/blocks/power/PowerNode.java | 11 ++++--- .../world/blocks/production/PowerSmelter.java | 4 +-- .../world/blocks/production/Separator.java | 2 +- .../mindustry/world/consumers/Consume.java | 4 +++ 9 files changed, 46 insertions(+), 23 deletions(-) diff --git a/annotations/src/io/anuke/annotations/Annotations.java b/annotations/src/io/anuke/annotations/Annotations.java index 5798d13cd8..d685729921 100644 --- a/annotations/src/io/anuke/annotations/Annotations.java +++ b/annotations/src/io/anuke/annotations/Annotations.java @@ -67,7 +67,7 @@ public class Annotations { client(false, true), /**Method can be invoked from anywhere*/ both(true, true), - /**Neither server no client.*/ + /**Neither server nor client.*/ none(false, false); /**If true, this method can be invoked ON clients FROM servers.*/ diff --git a/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java b/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java index cc651b2476..6fe9a4db48 100644 --- a/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java @@ -175,8 +175,8 @@ public class CraftingBlocks extends BlockList implements ContentList { itemCapacity = 40; health = 50; - consumes.item(Items.stone); - consumes.liquid(Liquids.water, 0.2f); + consumes.item(Items.stone, 2); + consumes.liquid(Liquids.water, 0.3f); }}; centrifuge = new Separator("centrifuge") {{ @@ -201,9 +201,9 @@ public class CraftingBlocks extends BlockList implements ContentList { spinnerSpeed = 3f; size = 2; - consumes.item(Items.stone); + consumes.item(Items.stone, 2); consumes.power(0.2f); - consumes.liquid(Liquids.water, 0.35f); + consumes.liquid(Liquids.water, 0.5f); }}; biomatterCompressor = new Compressor("biomattercompressor") {{ diff --git a/core/src/io/anuke/mindustry/entities/TileEntity.java b/core/src/io/anuke/mindustry/entities/TileEntity.java index 5445bf8b95..61e3e44989 100644 --- a/core/src/io/anuke/mindustry/entities/TileEntity.java +++ b/core/src/io/anuke/mindustry/entities/TileEntity.java @@ -151,6 +151,7 @@ public class TileEntity extends BaseEntity implements TargetTrait { for (GridPoint2 point : nearby) { Tile other = world.tile(tile.x + point.x, tile.y + point.y); //remove this tile from all nearby tile's proximities + if(other != null) other = other.target(); if(other != null && other.entity != null){ other.entity.proximity.removeValue(tile, true); } @@ -164,6 +165,7 @@ public class TileEntity extends BaseEntity implements TargetTrait { GridPoint2[] nearby = Edges.getEdges(tile.block().size); for (GridPoint2 point : nearby) { Tile other = world.tile(tile.x + point.x, tile.y + point.y); + if(other != null) other = other.target(); if(other != null && other.entity != null){ tmpTiles.add(other); diff --git a/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java b/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java index 53aeee4a9b..8ebdefae5c 100644 --- a/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java +++ b/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java @@ -12,6 +12,7 @@ import io.anuke.mindustry.game.TeamInfo.TeamData; import io.anuke.mindustry.input.InputHandler; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.consumers.Consume; import io.anuke.mindustry.world.meta.BlockBar; import io.anuke.ucore.core.Graphics; import io.anuke.ucore.core.Settings; @@ -129,6 +130,18 @@ public class OverlayRenderer { drawEncloser(target.drawx(), target.drawy() - block.size * tilesize/2f - 2f - values[1], values[1]); } + Draw.color(Palette.bar); + + int idx = 0; + for(Consume cons : block.consumes.all()){ + if(!cons.valid(block, entity)){ + Fill.crect(entity.x - block.size/2f + idx*4 - 3, entity.y + block.size/2f + values[0] + 11, 3, 3); + idx ++; + } + } + + Draw.color(); + doDraw[0] = true; values[0] = 0; values[1] = 1; @@ -193,25 +206,23 @@ public class OverlayRenderer { float len = 3; - float w = (int) (len * 2 * finion) + 0.5f; - - x -= 0.5f; - y += 0.5f; + float w = (int) (len * 2 * finion); Draw.color(Color.BLACK); - Lines.line(x - len + 1, y, x + len + 0.5f, y); - Draw.color(color); - if(w >= 1) - Lines.line(x - len + 1, y, x - len + w, y); - Draw.reset(); + Fill.crect(x - len, y, len*2f, 1); + if(finion > 0){ + Draw.color(color); + Fill.crect(x - len, y, Math.max(1, w), 1); + } + Draw.color(); } void drawEncloser(float x, float y, float height){ - float len = 3; + float len = 4; Draw.color(Palette.bar); - Fill.crect(x - len - 1, y - 1, len*2f + 2f, height + 2f); + Fill.crect(x - len, y - 1, len*2f, height + 2f); Draw.color(); } } diff --git a/core/src/io/anuke/mindustry/ui/fragments/DebugFragment.java b/core/src/io/anuke/mindustry/ui/fragments/DebugFragment.java index 7a19c45852..8b2b6338ca 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/DebugFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/DebugFragment.java @@ -55,6 +55,9 @@ public class DebugFragment extends Fragment { new table("pane"){{ defaults().fillX().width(100f); + new label(() -> Gdx.app.getJavaHeap() / 1024 / 1024 + "MB"); + row(); + new label("Debug"); row(); new button("noclip", "toggle", () -> noclip = !noclip); diff --git a/core/src/io/anuke/mindustry/world/blocks/power/PowerNode.java b/core/src/io/anuke/mindustry/world/blocks/power/PowerNode.java index 331f2d4546..3f7db1fffe 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/PowerNode.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/PowerNode.java @@ -184,9 +184,12 @@ public class PowerNode extends PowerBlock{ entity.powerRecieved = 0f; } - float added = super.addPower(tile, amount); - entity.powerRecieved += added; - return added; + float canAccept = Math.min(powerCapacity * Timers.delta() - tile.entity.power.amount, amount); + + tile.entity.power.amount += canAccept; + entity.powerRecieved += canAccept; + + return canAccept; } protected boolean shouldDistribute(Tile tile, Tile other) { @@ -226,7 +229,7 @@ public class PowerNode extends PowerBlock{ Tile target = world.tile(entity.links.get(i)); if(shouldDistribute(tile, target)) { - float transmit = Math.min(result * Timers.delta(), entity.power.amount); + float transmit = Math.min(result, entity.power.amount); if (target.block().acceptPower(target, tile, transmit)) { entity.power.amount -= target.block().addPower(target, transmit); } diff --git a/core/src/io/anuke/mindustry/world/blocks/production/PowerSmelter.java b/core/src/io/anuke/mindustry/world/blocks/production/PowerSmelter.java index a2949393c2..67b18228b8 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/PowerSmelter.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/PowerSmelter.java @@ -90,11 +90,11 @@ public class PowerSmelter extends PowerBlock { //heat it up if there's enough power if(entity.cons.valid()){ - entity.heat += 1f / heatUpTime; + entity.heat += 1f / heatUpTime * Timers.delta(); if(Mathf.chance(Timers.delta() * burnEffectChance)) Effects.effect(burnEffect, entity.x + Mathf.range(size*4f), entity.y + Mathf.range(size*4)); }else{ - entity.heat -= 1f / heatUpTime; + entity.heat -= 1f / heatUpTime * Timers.delta(); } entity.heat = Mathf.clamp(entity.heat); diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Separator.java b/core/src/io/anuke/mindustry/world/blocks/production/Separator.java index 3fbaab0ef2..9ba2cb2d7c 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Separator.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Separator.java @@ -94,7 +94,7 @@ public class Separator extends Block { if(entity.progress >= 1f){ entity.progress = 0f; Item item = Mathf.select(results); - entity.items.remove(consumes.item(), 1); + entity.items.remove(consumes.item(), consumes.itemAmount()); if(item != null){ offloading = true; offloadNear(tile, item); diff --git a/core/src/io/anuke/mindustry/world/consumers/Consume.java b/core/src/io/anuke/mindustry/world/consumers/Consume.java index 8712ecb175..6e3cbabb0e 100644 --- a/core/src/io/anuke/mindustry/world/consumers/Consume.java +++ b/core/src/io/anuke/mindustry/world/consumers/Consume.java @@ -26,6 +26,10 @@ public abstract class Consume { return update; } + public void draw(TileEntity entity){ + + } + public abstract void update(Block block, TileEntity entity); public abstract boolean valid(Block block, TileEntity entity); public abstract void display(BlockStats stats); From dbee30a412a5ecc06f640022e2d17bce7e2aaa23 Mon Sep 17 00:00:00 2001 From: Anuken Date: Mon, 9 Jul 2018 18:14:55 -0400 Subject: [PATCH 17/47] Updated uCore / Made splitter distribute properly --- build.gradle | 2 +- core/src/io/anuke/mindustry/Mindustry.java | 2 -- .../content/blocks/ProductionBlocks.java | 4 +-- .../io/anuke/mindustry/world/ItemBuffer.java | 22 ++++++++++++-- .../world/blocks/distribution/Splitter.java | 30 ++++++++----------- .../world/blocks/power/PowerNode.java | 2 +- 6 files changed, 36 insertions(+), 26 deletions(-) diff --git a/build.gradle b/build.gradle index cd13ee0ad0..ede4be0f00 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ allprojects { gdxVersion = '1.9.8' roboVMVersion = '2.3.0' aiVersion = '1.8.1' - uCoreVersion = '2241e5402e' + uCoreVersion = 'f937c5cad6' getVersionString = { String buildVersion = getBuildVersion() diff --git a/core/src/io/anuke/mindustry/Mindustry.java b/core/src/io/anuke/mindustry/Mindustry.java index 79c208d4cb..86cc421e96 100644 --- a/core/src/io/anuke/mindustry/Mindustry.java +++ b/core/src/io/anuke/mindustry/Mindustry.java @@ -1,6 +1,5 @@ package io.anuke.mindustry; -import com.badlogic.gdx.utils.async.AsyncExecutor; import io.anuke.mindustry.core.*; import io.anuke.mindustry.io.BundleLoader; import io.anuke.ucore.core.Timers; @@ -10,7 +9,6 @@ import io.anuke.ucore.util.Log; import static io.anuke.mindustry.Vars.*; public class Mindustry extends ModuleCore { - private AsyncExecutor exec = new AsyncExecutor(1); @Override public void init(){ diff --git a/core/src/io/anuke/mindustry/content/blocks/ProductionBlocks.java b/core/src/io/anuke/mindustry/content/blocks/ProductionBlocks.java index 8bbdba1f1e..0e3ae62edd 100644 --- a/core/src/io/anuke/mindustry/content/blocks/ProductionBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/ProductionBlocks.java @@ -84,12 +84,12 @@ public class ProductionBlocks extends BlockList implements ContentList { updateEffect = BlockFx.pulverize; liquidCapacity = 50f; updateEffectChance = 0.05f; - pumpAmount = 0.06f; + pumpAmount = 0.08f; size = 3; liquidCapacity = 30f; consumes.item(Items.sand); - consumes.power(0.6f); + consumes.power(0.5f); consumes.liquid(Liquids.water, 0.3f); }}; diff --git a/core/src/io/anuke/mindustry/world/ItemBuffer.java b/core/src/io/anuke/mindustry/world/ItemBuffer.java index fa0ba89eb5..d09583d6d1 100644 --- a/core/src/io/anuke/mindustry/world/ItemBuffer.java +++ b/core/src/io/anuke/mindustry/world/ItemBuffer.java @@ -20,9 +20,13 @@ public class ItemBuffer { return index < buffer.length; } - public void accept(Item item){ + public void accept(Item item, short data){ //if(!accepts()) return; - buffer[index ++] = Bits.packLong(NumberUtils.floatToIntBits(Timers.time()), item.id); + buffer[index ++] = Bits.packLong(NumberUtils.floatToIntBits(Timers.time()), Bits.packInt((short)item.id, data)); + } + + public void accept(Item item){ + accept(item, (short)-1); } public Item poll(){ @@ -31,12 +35,24 @@ public class ItemBuffer { float time = NumberUtils.intBitsToFloat(Bits.getLeftInt(l)); if(Timers.time() >= time + speed || Timers.time() < time){ - return Item.getByID(Bits.getRightInt(l)); + return Item.getByID(Bits.getLeftShort(Bits.getRightInt(l))); } } return null; } + public short pollData(){ + if(index > 0){ + long l = buffer[0]; + float time = NumberUtils.intBitsToFloat(Bits.getLeftInt(l)); + + if(Timers.time() >= time + speed || Timers.time() < time){ + return Bits.getRightShort(Bits.getRightInt(l)); + } + } + return -1; + } + public void remove(){ System.arraycopy(buffer, 1, buffer, 0, index - 1); index --; diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/Splitter.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Splitter.java index 86301fb49c..e42a3f721c 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Splitter.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Splitter.java @@ -1,21 +1,19 @@ package io.anuke.mindustry.world.blocks.distribution; -import com.badlogic.gdx.math.GridPoint2; +import com.badlogic.gdx.utils.Array; import io.anuke.mindustry.type.Item; import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.Edges; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.meta.BlockGroup; -import static io.anuke.mindustry.Vars.world; - public class Splitter extends Block{ + protected float speed = 30f; public Splitter(String name){ super(name); solid = true; instantTransfer = true; - destructible = true; + update = true; group = BlockGroup.transportation; } @@ -23,26 +21,24 @@ public class Splitter extends Block{ public boolean acceptItem(Item item, Tile tile, Tile source){ Tile to = getTileTarget(item, tile, source, false); - return to != null && to.block().acceptItem(item, to, tile); + return to != null; } @Override public void handleItem(Item item, Tile tile, Tile source){ Tile to = getTileTarget(item, tile, source, true); - to.block().handleItem(item, to, tile); } - Tile getTileTarget(Item item, Tile dest, Tile source, boolean flip){ - GridPoint2[] points = Edges.getEdges(size); - int counter = source.getDump(); - for (int i = 0; i < points.length; i++) { - GridPoint2 point = points[(i + counter++) % points.length]; - source.setDump((byte)(counter % points.length)); - Tile tile = world.tile(dest.x + point.x, dest.y + point.y); - if(tile != source && !(tile.block().instantTransfer && source.block().instantTransfer) && - tile.block().acceptItem(item, tile, dest)){ - return tile; + Tile getTileTarget(Item item, Tile tile, Tile source, boolean flip){ + Array proximity = tile.entity.proximity(); + int counter = tile.getDump(); + for (int i = 0; i < proximity.size; i++) { + Tile other = proximity.get((i + counter) % proximity.size); + if(flip) tile.setDump((byte)((tile.getDump() + 1) % proximity.size)); + if(other != source && !(source.block().instantTransfer && other.block().instantTransfer && !(other.block() instanceof Splitter)) && + other.block().acceptItem(item, other, tile)){ + return other; } } return null; diff --git a/core/src/io/anuke/mindustry/world/blocks/power/PowerNode.java b/core/src/io/anuke/mindustry/world/blocks/power/PowerNode.java index 3f7db1fffe..84b3769fdd 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/PowerNode.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/PowerNode.java @@ -70,7 +70,7 @@ public class PowerNode extends PowerBlock{ super.setStats(); stats.add(BlockStat.powerRange, laserRange, StatUnit.blocks); - stats.add(BlockStat.powerTransferSpeed, powerSpeed * 60, StatUnit.powerSecond); + stats.add(BlockStat.powerTransferSpeed, powerSpeed * 60 / 2f, StatUnit.powerSecond); //divided by 2 since passback exists } @Override From d31fbf3b6fdcb31cff5a86d3a77ea3fb126ef410 Mon Sep 17 00:00:00 2001 From: Anuken Date: Mon, 9 Jul 2018 20:18:50 -0400 Subject: [PATCH 18/47] Added flyer shadows / Fixed block inventory rebuild spawn / Distributor --- .../content/blocks/DistributionBlocks.java | 1 - .../src/io/anuke/mindustry/core/Renderer.java | 32 ++++++++++++++++++- .../io/anuke/mindustry/entities/Player.java | 5 +++ .../src/io/anuke/mindustry/entities/Unit.java | 5 +++ .../mindustry/entities/units/FlyingUnit.java | 5 +++ .../mindustry/graphics/OverlayRenderer.java | 4 +-- .../io/anuke/mindustry/ui/MobileButton.java | 2 +- .../ui/fragments/BlockInventoryFragment.java | 3 +- .../world/blocks/distribution/Splitter.java | 8 +++-- 9 files changed, 56 insertions(+), 9 deletions(-) diff --git a/core/src/io/anuke/mindustry/content/blocks/DistributionBlocks.java b/core/src/io/anuke/mindustry/content/blocks/DistributionBlocks.java index 2a7033b30d..cda352e0f6 100644 --- a/core/src/io/anuke/mindustry/content/blocks/DistributionBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/DistributionBlocks.java @@ -41,7 +41,6 @@ public class DistributionBlocks extends BlockList implements ContentList{ distributor = new Splitter("distributor") {{ size = 2; - itemCapacity = 80; }}; overflowGate = new OverflowGate("overflow-gate"); diff --git a/core/src/io/anuke/mindustry/core/Renderer.java b/core/src/io/anuke/mindustry/core/Renderer.java index cf25d06aae..dfa84d2041 100644 --- a/core/src/io/anuke/mindustry/core/Renderer.java +++ b/core/src/io/anuke/mindustry/core/Renderer.java @@ -219,12 +219,13 @@ public class Renderer extends RendererModule{ Graphics.endShaders(); } - drawAllTeams(false); blocks.skipLayer(Layer.turret); blocks.drawBlocks(Layer.laser); + drawFlyerShadows(); + drawAllTeams(true); drawAndInterpolate(bulletGroup); @@ -251,6 +252,35 @@ public class Renderer extends RendererModule{ batch.end(); } + private void drawFlyerShadows(){ + Graphics.surface(effectSurface); + + float trnsX = 12, trnsY = -13; + + Graphics.end(); + Core.batch.getTransformMatrix().translate(trnsX, trnsY, 0); + Graphics.begin(); + + for(EntityGroup group : unitGroups){ + if(!group.isEmpty()){ + drawAndInterpolate(group, Unit::isFlying, Unit::drawShadow); + } + } + + if(!playerGroup.isEmpty()){ + drawAndInterpolate(playerGroup, Unit::isFlying, Unit::drawShadow); + } + + Graphics.end(); + Core.batch.getTransformMatrix().translate(-trnsX, -trnsY, 0); + Graphics.begin(); + + //TODO this actually isn't necessary + Draw.color(0, 0, 0, 0.15f); + Graphics.flushSurface(); + Draw.color(); + } + private void drawAllTeams(boolean flying){ for(Team team : Team.all){ EntityGroup group = unitGroups[team.ordinal()]; diff --git a/core/src/io/anuke/mindustry/entities/Player.java b/core/src/io/anuke/mindustry/entities/Player.java index 612b9bd379..89aecc1120 100644 --- a/core/src/io/anuke/mindustry/entities/Player.java +++ b/core/src/io/anuke/mindustry/entities/Player.java @@ -274,6 +274,11 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra return isLocal ? Float.MAX_VALUE : 40; } + @Override + public void drawShadow(){ + Draw.rect(mech.iconRegion, x + elevation*elevationScale, y - elevation*elevationScale, rotation - 90); + } + @Override public void draw(){ if((debug && (!showPlayer || !showUI)) || dead) return; diff --git a/core/src/io/anuke/mindustry/entities/Unit.java b/core/src/io/anuke/mindustry/entities/Unit.java index 80bbd239a7..53619b7fdd 100644 --- a/core/src/io/anuke/mindustry/entities/Unit.java +++ b/core/src/io/anuke/mindustry/entities/Unit.java @@ -38,6 +38,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ public static final float velocityPercision = 8f; /**Maximum absolute value of a velocity vector component.*/ public static final float maxAbsVelocity = 127f/velocityPercision; + public static final float elevationScale = 4f; private static final Vector2 moveVector = new Vector2(); @@ -52,6 +53,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ protected Vector2 velocity = new Translator(0f, 0.0001f); protected float hitTime; protected float drownTime; + protected float elevation; @Override public UnitInventory getInventory() { @@ -225,6 +227,8 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ if(isFlying()) { x += velocity.x / getMass() * Timers.delta(); y += velocity.y / getMass() * Timers.delta(); + + elevation = Mathf.lerpDelta(elevation, tile.elevation, 0.04f); }else{ boolean onLiquid = floor.isLiquid; @@ -299,6 +303,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ public void drawUnder(){} public void drawOver(){} + public void drawShadow(){} public void drawView(){ Fill.circle(x, y, getViewDistance()); diff --git a/core/src/io/anuke/mindustry/entities/units/FlyingUnit.java b/core/src/io/anuke/mindustry/entities/units/FlyingUnit.java index ebb3eb2287..7a65787501 100644 --- a/core/src/io/anuke/mindustry/entities/units/FlyingUnit.java +++ b/core/src/io/anuke/mindustry/entities/units/FlyingUnit.java @@ -32,6 +32,11 @@ public abstract class FlyingUnit extends BaseUnit implements CarryTrait{ } + @Override + public void drawShadow(){ + Draw.rect(type.region, x + elevation*elevationScale, y - elevation*elevationScale, rotation - 90); + } + @Override public CarriableTrait getCarry() { return carrying; diff --git a/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java b/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java index 8ebdefae5c..dcb7eb167f 100644 --- a/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java +++ b/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java @@ -134,8 +134,8 @@ public class OverlayRenderer { int idx = 0; for(Consume cons : block.consumes.all()){ - if(!cons.valid(block, entity)){ - Fill.crect(entity.x - block.size/2f + idx*4 - 3, entity.y + block.size/2f + values[0] + 11, 3, 3); + if(!cons.isOptional() && !cons.valid(block, entity)){ + Fill.crect(entity.x - 4 + idx*4, entity.y + block.size*tilesize/2f + values[0] + 4, 3, 3); idx ++; } } diff --git a/core/src/io/anuke/mindustry/ui/MobileButton.java b/core/src/io/anuke/mindustry/ui/MobileButton.java index af996644d7..6d7f8211d5 100644 --- a/core/src/io/anuke/mindustry/ui/MobileButton.java +++ b/core/src/io/anuke/mindustry/ui/MobileButton.java @@ -10,6 +10,6 @@ public class MobileButton extends ImageButton { resizeImage(isize); clicked(listener); row(); - add(text); + add(text).growX().wrap(); } } diff --git a/core/src/io/anuke/mindustry/ui/fragments/BlockInventoryFragment.java b/core/src/io/anuke/mindustry/ui/fragments/BlockInventoryFragment.java index 3e65ac45de..2720ad7096 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/BlockInventoryFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/BlockInventoryFragment.java @@ -95,7 +95,8 @@ public class BlockInventoryFragment extends Fragment { updateTablePosition(); if(tile.block().hasItems) { for (int i = 0; i < Item.all().size; i++) { - if ((tile.entity.items.has(Item.getByID(i))) == container.contains(i)) { + boolean has = tile.entity.items.has(Item.getByID(i)); + if (has != container.contains(i)) { rebuild(false); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/Splitter.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Splitter.java index e42a3f721c..fd5c57bf19 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Splitter.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Splitter.java @@ -3,6 +3,7 @@ package io.anuke.mindustry.world.blocks.distribution; import com.badlogic.gdx.utils.Array; import io.anuke.mindustry.type.Item; import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.Edges; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.meta.BlockGroup; @@ -14,6 +15,7 @@ public class Splitter extends Block{ solid = true; instantTransfer = true; update = true; + hasItems = false; group = BlockGroup.transportation; } @@ -27,7 +29,7 @@ public class Splitter extends Block{ @Override public void handleItem(Item item, Tile tile, Tile source){ Tile to = getTileTarget(item, tile, source, true); - to.block().handleItem(item, to, tile); + to.block().handleItem(item, to, Edges.getFacingEdge(tile, to)); } Tile getTileTarget(Item item, Tile tile, Tile source, boolean flip){ @@ -36,8 +38,8 @@ public class Splitter extends Block{ for (int i = 0; i < proximity.size; i++) { Tile other = proximity.get((i + counter) % proximity.size); if(flip) tile.setDump((byte)((tile.getDump() + 1) % proximity.size)); - if(other != source && !(source.block().instantTransfer && other.block().instantTransfer && !(other.block() instanceof Splitter)) && - other.block().acceptItem(item, other, tile)){ + if(other != source && !(source.block().instantTransfer && other.block().instantTransfer) && !(other.block() instanceof Splitter) && + other.block().acceptItem(item, other, Edges.getFacingEdge(tile, other))){ return other; } } From 66b9cdf64c24b170ad6157d2b914061b6ab3e1cc Mon Sep 17 00:00:00 2001 From: Anuken Date: Tue, 10 Jul 2018 10:14:10 -0400 Subject: [PATCH 19/47] Conduit autotiling / Added Block.onProximityUpdate() --- .../blocks/liquid/conduit-bottom-0.png | Bin 0 -> 213 bytes .../blocks/liquid/conduit-bottom-1.png | Bin 0 -> 225 bytes .../blocks/liquid/conduit-bottom-2.png | Bin 0 -> 216 bytes .../blocks/liquid/conduit-bottom-3.png | Bin 0 -> 224 bytes .../blocks/liquid/conduit-bottom-4.png | Bin 0 -> 224 bytes .../blocks/liquid/conduit-bottom-5.png | Bin 0 -> 225 bytes .../blocks/liquid/conduit-bottom-6.png | Bin 0 -> 199 bytes .../sprites/blocks/liquid/conduit-bottom.png | Bin 199 -> 217 bytes .../sprites/blocks/liquid/conduit-top-0.png | Bin 0 -> 209 bytes .../sprites/blocks/liquid/conduit-top-1.png | Bin 0 -> 243 bytes .../sprites/blocks/liquid/conduit-top-2.png | Bin 0 -> 252 bytes .../sprites/blocks/liquid/conduit-top-3.png | Bin 0 -> 262 bytes .../sprites/blocks/liquid/conduit-top-4.png | Bin 0 -> 251 bytes .../sprites/blocks/liquid/conduit-top-5.png | Bin 0 -> 242 bytes .../sprites/blocks/liquid/conduit-top-6.png | Bin 0 -> 239 bytes .../sprites/blocks/liquid/conduit-top.png | Bin 191 -> 0 bytes .../blocks/liquid/pulse-conduit-bottom.png | Bin 219 -> 0 bytes ...onduit-top.png => pulse-conduit-top-0.png} | Bin .../blocks/liquid/pulse-conduit-top-1.png | Bin 0 -> 287 bytes .../blocks/liquid/pulse-conduit-top-2.png | Bin 0 -> 282 bytes .../blocks/liquid/pulse-conduit-top-3.png | Bin 0 -> 289 bytes .../blocks/liquid/pulse-conduit-top-4.png | Bin 0 -> 284 bytes .../blocks/liquid/pulse-conduit-top-5.png | Bin 0 -> 289 bytes .../blocks/liquid/pulse-conduit-top-6.png | Bin 0 -> 270 bytes core/assets/sprites/sprites.atlas | 508 +++++++++++------- core/assets/sprites/sprites.png | Bin 134646 -> 135823 bytes .../anuke/mindustry/entities/TileEntity.java | 14 +- .../io/anuke/mindustry/world/BaseBlock.java | 2 + .../world/blocks/distribution/Conduit.java | 54 +- 29 files changed, 379 insertions(+), 199 deletions(-) create mode 100644 core/assets-raw/sprites/blocks/liquid/conduit-bottom-0.png create mode 100644 core/assets-raw/sprites/blocks/liquid/conduit-bottom-1.png create mode 100644 core/assets-raw/sprites/blocks/liquid/conduit-bottom-2.png create mode 100644 core/assets-raw/sprites/blocks/liquid/conduit-bottom-3.png create mode 100644 core/assets-raw/sprites/blocks/liquid/conduit-bottom-4.png create mode 100644 core/assets-raw/sprites/blocks/liquid/conduit-bottom-5.png create mode 100644 core/assets-raw/sprites/blocks/liquid/conduit-bottom-6.png create mode 100644 core/assets-raw/sprites/blocks/liquid/conduit-top-0.png create mode 100644 core/assets-raw/sprites/blocks/liquid/conduit-top-1.png create mode 100644 core/assets-raw/sprites/blocks/liquid/conduit-top-2.png create mode 100644 core/assets-raw/sprites/blocks/liquid/conduit-top-3.png create mode 100644 core/assets-raw/sprites/blocks/liquid/conduit-top-4.png create mode 100644 core/assets-raw/sprites/blocks/liquid/conduit-top-5.png create mode 100644 core/assets-raw/sprites/blocks/liquid/conduit-top-6.png delete mode 100644 core/assets-raw/sprites/blocks/liquid/conduit-top.png delete mode 100644 core/assets-raw/sprites/blocks/liquid/pulse-conduit-bottom.png rename core/assets-raw/sprites/blocks/liquid/{pulse-conduit-top.png => pulse-conduit-top-0.png} (100%) create mode 100644 core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-1.png create mode 100644 core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-2.png create mode 100644 core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-3.png create mode 100644 core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-4.png create mode 100644 core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-5.png create mode 100644 core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-6.png diff --git a/core/assets-raw/sprites/blocks/liquid/conduit-bottom-0.png b/core/assets-raw/sprites/blocks/liquid/conduit-bottom-0.png new file mode 100644 index 0000000000000000000000000000000000000000..04eca38d45d463a66cd8a5b49dd1c9823a6ea480 GIT binary patch literal 213 zcmeAS@N?(olHy`uVBq!ia0y~yVBi2@4mJh`h9ms@x)~T4*pj^6T^O1`@N!aVJ_7>- zXMsm#F#`j)FbFd;%$g&?z`(#>;_2(k{)nB6S6@?Tvphco1A}CVYeb22er|4RUI~M9 zQEFmIYKlU6W=V#EyQgnJcq5-U0|SGtr;B5V#O35Qt5+vCFe)o6v$3(UwG}@mdK II;Vst0G7Nvi~s-t literal 0 HcmV?d00001 diff --git a/core/assets-raw/sprites/blocks/liquid/conduit-bottom-1.png b/core/assets-raw/sprites/blocks/liquid/conduit-bottom-1.png new file mode 100644 index 0000000000000000000000000000000000000000..dff4e78e7fad21842fea2f485a75a0ca8d867900 GIT binary patch literal 225 zcmeAS@N?(olHy`uVBq!ia0y~yVBi2@4mJh`h9ms@x)~T4*pj^6T^O1`@N!aVJ_7>- zXMsm#F#`j)FbFd;%$g&?z`(#>;_2(k{)nB6S6|kc{q-&e1_sFz*N775{M_8syb=cI zqSVBa)D(sC%#sWRcTeAd@J2py1_lOCPZ!4!iOa3$H}W1Z;9$MToVsW~hxEqD;)zR_ za8BgDz4Mpi0V82gmm_DdNE}|()$RMfVW*1UiJvD|zv6keIP3t!ugSU@EHxX#?S)$; U7}=(Mabf_O>gwmR&MBb@0M4vL{Qv*} literal 0 HcmV?d00001 diff --git a/core/assets-raw/sprites/blocks/liquid/conduit-bottom-2.png b/core/assets-raw/sprites/blocks/liquid/conduit-bottom-2.png new file mode 100644 index 0000000000000000000000000000000000000000..5fec0419e0bd24a2cb649b14ca3cb1f1f78898f8 GIT binary patch literal 216 zcmeAS@N?(olHy`uVBq!ia0y~yVBi2@4mJh`h9ms@x)~T4*pj^6T^O1`@N!aVJ_7>- zXMsm#F#`j)FbFd;%$g&?z`(#>;_2(k{)nB6S6{$)+jMpY1_sFz*N775{M_8syb=cI zqSVBa)D(sC%#sWRcTeAd@J2py1_lNPPZ!4!iOXwGZDc%Pz{BMD!9yy*xYISlbKNAZ ziPH6)$Il!Ic41)1*)P4CEBMX3c?S+HpF literal 0 HcmV?d00001 diff --git a/core/assets-raw/sprites/blocks/liquid/conduit-bottom-3.png b/core/assets-raw/sprites/blocks/liquid/conduit-bottom-3.png new file mode 100644 index 0000000000000000000000000000000000000000..8983671a0dd6430177d7d983b0e878278842a292 GIT binary patch literal 224 zcmeAS@N?(olHy`uVBq!ia0y~yVBi2@4mJh`h9ms@x)~T4*pj^6T^O1`@N!aVJ_7>- zXMsm#F#`j)FbFd;%$g&?z`(#>;_2(k{)nB6SD*PL$F-Xb3=EPbt`Q~9`MJ5Nc_j?a zMX8A;sVNHOnI#zt?w-B@;f;La3=9k&o-U3d5|`JWKginq%l(zo-4(_MmDYs# zt7ZN9_-Hp1`=Z?wXRcUvuyx4>-U&x?Cb*nAllybahllM=OK!AIO1gP0`sJQV2Dxpf W>x14OJj=kqz~JfX=d#Wzp$P!Y;!C&y literal 0 HcmV?d00001 diff --git a/core/assets-raw/sprites/blocks/liquid/conduit-bottom-4.png b/core/assets-raw/sprites/blocks/liquid/conduit-bottom-4.png new file mode 100644 index 0000000000000000000000000000000000000000..3a8d3ac7a8dcac240629469b9a451940de6c5c7a GIT binary patch literal 224 zcmeAS@N?(olHy`uVBq!ia0y~yVBi2@4mJh`h9ms@x)~T4*pj^6T^O1`@N!aVJ_7>- zXMsm#F#`j)FbFd;%$g&?z`(#>;_2(k{)nB6SI;2-a9bY(1A}CVYeb22er|4RUI~M9 zQEFmIYKlU6W=V#EyQgnJcq5-U0|SGHr;B5V#O0-@5Arr7a5P`!f9#@j$n8vsSfc8l zneoD3AMDuP`@F3>R!HGzj@yA9k?aCCf9&B UVeN@(1_lNOPgg&ebxsLQ0ROm00RR91 literal 0 HcmV?d00001 diff --git a/core/assets-raw/sprites/blocks/liquid/conduit-bottom-5.png b/core/assets-raw/sprites/blocks/liquid/conduit-bottom-5.png new file mode 100644 index 0000000000000000000000000000000000000000..5fa2763614cdee91dc94543eeb73a84478f70219 GIT binary patch literal 225 zcmeAS@N?(olHy`uVBq!ia0y~yVBi2@4mJh`h9ms@x)~T4*pj^6T^O1`@N!aVJ_7>- zXMsm#F#`j)FbFd;%$g&?z`(#>;_2(k{)nB6S5I!UXtyH+1A}CVYeb22er|4RUI~M9 zQEFmIYKlU6W=V#EyQgnJcq5-U0|SGnr;B5V#O2z)gS-a}IGFj}k_63?)Wtq}yI;h*qCIv*%iq{|!$Dd*6DozShd!i+7*>zeFXkCQVFe T#hQi83?Nfo{an^LB{Ts5);K}b literal 0 HcmV?d00001 diff --git a/core/assets-raw/sprites/blocks/liquid/conduit-bottom-6.png b/core/assets-raw/sprites/blocks/liquid/conduit-bottom-6.png new file mode 100644 index 0000000000000000000000000000000000000000..931b4d5b873603957cdfb09157348364180507d6 GIT binary patch literal 199 zcmeAS@N?(olHy`uVBq!ia0y~yVBi2@4mJh`h9ms@x)~T4*pj^6T^O1`@N!aVJ_7>- zXMsm#F#`j)FbFd;%$g&?z`(#>;_2(k{)nB6k4y6hTj53q1_sFz*N775{M_8syb=cI zqSVBa)D(sC%#sWRcTeAd@J2py1_lOwPZ!4!iOYM>?Br!I;5qDY>G%6JCWa4=X%?@Q u+c~MlQFKAXmjiDXuH~Q4wdyMuBcEgf4}XwSOfmxl1B0ilpUXO@geCwz4L9Zh literal 0 HcmV?d00001 diff --git a/core/assets-raw/sprites/blocks/liquid/conduit-bottom.png b/core/assets-raw/sprites/blocks/liquid/conduit-bottom.png index a869a8cdaee52f58151bd02c72fb87e2b8114e44..7e3892102fa837037d02b33c2d225489869f4d98 100644 GIT binary patch delta 39 scmX@kc$0C03O`$tx4R2N69`^TD$Sp$D981Por^(K$A87LRTDky0QQFr@Bjb+ delta 47 zcmcb~c${&9iYjM;M`SSr1Gg{;GcwGYBf-GHz+U3%>&pI+Q;3mCIzBdT_r&Nr00D6e AN&o-= diff --git a/core/assets-raw/sprites/blocks/liquid/conduit-top-0.png b/core/assets-raw/sprites/blocks/liquid/conduit-top-0.png new file mode 100644 index 0000000000000000000000000000000000000000..c3809b0f6002647201a077f3a9d6cc37d342f993 GIT binary patch literal 209 zcmeAS@N?(olHy`uVBq!ia0y~yVBi2@4mJh`h9ms@x)~T4*pj^6T^O1`@N!aVJ_7>- zXMsm#F#`j)FbFd;%$g&?z`(#>;_2(k{)nB6LDW*I)X9;7fkCpwHKN2hKQ}iuuY|$5 zC^fMpHASI3vm`^o-P1Q9ypd0wfq}u&)5S4F;&Squ)vJG>XJB=6bK?Pl=jY}&a~c}t z-PpjmjzRs$|NsB%=hW<(6k^!9Ksj>4-j=ho84i9lc=_m$+-n8~1_n=8KbLh*2~7YX CAwGxz literal 0 HcmV?d00001 diff --git a/core/assets-raw/sprites/blocks/liquid/conduit-top-1.png b/core/assets-raw/sprites/blocks/liquid/conduit-top-1.png new file mode 100644 index 0000000000000000000000000000000000000000..37ee6de2a6f6701f9e30a3b55830e9e388600210 GIT binary patch literal 243 zcmeAS@N?(olHy`uVBq!ia0y~yVBi2@4mJh`h9ms@x)~T4*pj^6T^O1`@N!aVJ_7>- zXMsm#F#`j)FbFd;%$g&?z`(#>;_2(k{)nB6Q9`%paFZJY1A}CVYeb22er|4RUI~M9 zQEFmIYKlU6W=V#EyQgnJcq5-U0|P^}r;B5V#O2cdgS-b6c$oD&IyZ6nOqprq8CGP! z)T!g%cbVvo9TRpNKR59-GG;#E6Dm4)dMs0qA;XJzd- zXMsm#F#`j)FbFd;%$g&?z`(#>;_2(k{)nB6L6Xm`c7+oI1A}CVYeb22er|4RUI~M9 zQEFmIYKlU6W=V#EyQgnJcq5-U0|P^fr;B5V#O2)n)4T^1c${V3k|yS`XdmsaQ2idz z)pcaIt@GIdeve7$hgq= y6K8Txs?JHP+BsWg!kzW0%bw3+JTU14gG!y}lTEjz(is>S7(8A5T-G@yGywodS5W!@ literal 0 HcmV?d00001 diff --git a/core/assets-raw/sprites/blocks/liquid/conduit-top-3.png b/core/assets-raw/sprites/blocks/liquid/conduit-top-3.png new file mode 100644 index 0000000000000000000000000000000000000000..e067a5bb7b728aa590cf8076edb61682e05f66c1 GIT binary patch literal 262 zcmeAS@N?(olHy`uVBq!ia0y~yVBi2@4mJh`h9ms@x)~T4*pj^6T^O1`@N!aVJ_7>- zXMsm#F#`j)FbFd;%$g&?z`(#>;_2(k{)nB6K~jzR1j7yn1_sFz*N775{M_8syb=cI zqSVBa)D(sC%#sWRcTeAd@J2py1_p*aPZ!4!iOact2RV;8@Gu{Ls8yu=PUCc0ius~x zCG%Bc9(=iEpdj+R(#m+ENOk^s9}gvC-A?~BCL19IFVdQ I&MBb@0J}F- zXMsm#F#`j)FbFd;%$g&?z`(#>;_2(k{)nB6L6J+X|H1+W1_sFz*N775{M_8syb=cI zqSVBa)D(sC%#sWRcTeAd@J2py1_p*?PZ!4!iOaS92RV;8@Gu{D*9ua;9hhMK$!yWI zlKC+woOV=J>Ikej_qj(U=lUGu?6sSEm)v+Zfu*3GVUk2LLjZ$kQ0P(DNxNTu7PG5W w_N*%}&);|Y>0RH_@*js2e^1L6Jbzz&zopr0Mdn0djJ3c literal 0 HcmV?d00001 diff --git a/core/assets-raw/sprites/blocks/liquid/conduit-top-5.png b/core/assets-raw/sprites/blocks/liquid/conduit-top-5.png new file mode 100644 index 0000000000000000000000000000000000000000..794130612347bf68f9a9be21fa934b938e55e087 GIT binary patch literal 242 zcmeAS@N?(olHy`uVBq!ia0y~yVBi2@4mJh`h9ms@x)~T4*pj^6T^O1`@N!aVJ_7>- zXMsm#F#`j)FbFd;%$g&?z`(#>;_2(k{)nB6QNnP>p?jPR3=EPbt`Q~9`MJ5Nc_j?a zMX8A;sVNHOnI#zt?w-B@;f;La3=9lWo-U3d5|>N+4hk|GaxjNadE&L=gz3%R3zI%^ zO7_2BBp;jIwhuOB596A%ie0$oG3}xfg+xwTO-0s`F>uhn~@w3^Q oziw~;wRPI#_@ei|p&uE~^H}g3w<%s_U|?YIboFyt=akR{02f0~B>(^b literal 0 HcmV?d00001 diff --git a/core/assets-raw/sprites/blocks/liquid/conduit-top-6.png b/core/assets-raw/sprites/blocks/liquid/conduit-top-6.png new file mode 100644 index 0000000000000000000000000000000000000000..eb0cdfbb250b7f57469541001701416b3a052401 GIT binary patch literal 239 zcmeAS@N?(olHy`uVBq!ia0y~yVBi2@4mJh`h9ms@x)~T4*pj^6T^O1`@N!aVJ_7>- zXMsm#F#`j)FbFd;%$g&?z`(#>;_2(k{)nB6k3%i0pKlQZ1A}CVYeb22er|4RUI~M9 zQEFmIYKlU6W=V#EyQgnJcq5-U0|P_2r;B5V#O2y^2YC-0@Hk)eR~F6`tX4X|#B+yG zv1Wj|!{OJ;8kz6su1Z?FRAr({!@Is*Jv&Rmgmau9bmvq|a}O{TiN+ k-?qOycfM2o)-$%Zc6MwZP6_>HU|?YIboFyt=akR{0AkooG5`Po literal 0 HcmV?d00001 diff --git a/core/assets-raw/sprites/blocks/liquid/conduit-top.png b/core/assets-raw/sprites/blocks/liquid/conduit-top.png deleted file mode 100644 index 8a9894c8b58e6fa1d066cafba427c5710205189f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 191 zcmeAS@N?(olHy`uVBq!ia0y~yVBi2@4mJh`h9ms@x)~T4I14-?iy0WWg+Z8+Vb&Z8 z1_lQ95>H=O_J^E845rfOW=z?^z`!6`;u=xnoS&PUnpeW$T$GwvlA5AWo>`Ki;O^-g z5Z=fq&cMK6>FMGaB5^r+&Fa;^&oi*Pxw-Lx!1Hr+n>h^)@@{NkT*sjPb?W nObRjVT%a5|VQ2=A diff --git a/core/assets-raw/sprites/blocks/liquid/pulse-conduit-bottom.png b/core/assets-raw/sprites/blocks/liquid/pulse-conduit-bottom.png deleted file mode 100644 index 304dc71403eaa28986733eed178486e1d990ce8a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 219 zcmeAS@N?(olHy`uVBq!ia0y~yVBi2@4mJh`h9ms@x)~T4*pj^6T^Rm@;DWu&Co?cG za29w(7Bet#3xhBt!>l6p PFfe$!`njxgN@xNA$%8+G diff --git a/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top.png b/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-0.png similarity index 100% rename from core/assets-raw/sprites/blocks/liquid/pulse-conduit-top.png rename to core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-0.png diff --git a/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-1.png b/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-1.png new file mode 100644 index 0000000000000000000000000000000000000000..43017050e78421d5bba774f763c3ee4ef520ab38 GIT binary patch literal 287 zcmeAS@N?(olHy`uVBq!ia0y~yVBi2@4mJh`h9ms@x)~T4*pj^6T^O1`@N!aVJ_7>- zXMsm#F#`j)FbFd;%$g&?z`(#>;_2(k{)nB6*NP?K=F&U{1_sFz*N775{M_8syb=cI zqSVBa)D(sC%#sWRcTeAd@J2py1_p+9PZ!4!iOZ$^hJ4KiJg)NtZy0abu*mCfaof9- ztUJ2&53?3E%u}=$N=kis;M@bh5Y4Q;cK`q91cq`eP6!e*HWn0Yn6r6mP(htU=I(h? zOh+Ub3%*D5JMg}K+IBi-_N>aA|D24Cx33p&=YKNi?Q}PWbN;v9eOziC=WoXuYHHsz i$Nl{2WBdH}Fp5;$w}e&A5M^LsVDNPHb6Mw<&;$T*8DN+I literal 0 HcmV?d00001 diff --git a/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-2.png b/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-2.png new file mode 100644 index 0000000000000000000000000000000000000000..77963325246e73a1a92e1ceed16590cdaa7175ad GIT binary patch literal 282 zcmeAS@N?(olHy`uVBq!ia0y~yVBi2@4mJh`h9ms@x)~T4*pj^6T^O1`@N!aVJ_7>- zXMsm#F#`j)FbFd;%$g&?z`(#>;_2(k{)nB6*HZrAM0-aD1_sFz*N775{M_8syb=cI zqSVBa)D(sC%#sWRcTeAd@J2py1_p*EPZ!4!iOacthFnaJ0xa2lXLx;lTt6s3`^~g} z)9)F-5BR*3RueyR<;a0M3wZZf$L#3arIB$fYc10aIlVc3)eL#x)!!fIXxO>iY`1at z&pqoT8dPS^JnJ+`>5$RnMBjhK;-0g1?=HV{_{h6|){$O{O4Duzb*WvOFw=!)=FSt> eW-*@f*%$fH{|Pf=-wg%^1_n=8KbLh*2~7ZcjbnEJ literal 0 HcmV?d00001 diff --git a/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-3.png b/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-3.png new file mode 100644 index 0000000000000000000000000000000000000000..4ae0db8bf1c258e1096c5c90b8b795634cfc3200 GIT binary patch literal 289 zcmeAS@N?(olHy`uVBq!ia0y~yVBi2@4mJh`h9ms@x)~T4*pj^6T^O1`@N!aVJ_7>- zXMsm#F#`j)FbFd;%$g&?z`(#>;_2(k{)nB6*HUWZ@6+cP7#Ji=Tq8=H^K)}k^GX<; zi&7IyQd1PlGfOfQ+&z5*!W;R-85kHkJzX3_BrfOnDe@h15OKMmUtr64{RdlM|B9En z6LP<~?Pl_0`1V`v>t2Qz2TP=98Dz!98A(bX4}9n665-{!RHU0j@A~X#sZXE0W7y47 zQ2uKj>p6)Am6=G lwX@5cqFHLpB7!xHe-=e132c@B$iTqB;OXk;vd$@?2><{CWpn@l literal 0 HcmV?d00001 diff --git a/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-4.png b/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-4.png new file mode 100644 index 0000000000000000000000000000000000000000..b90b2a51b226708ca6353b1030004f59e7c7e380 GIT binary patch literal 284 zcmeAS@N?(olHy`uVBq!ia0y~yVBi2@4mJh`h9ms@x)~T4*pj^6T^O1`@N!aVJ_7>- zXMsm#F#`j)FbFd;%$g&?z`(#>;_2(k{)nB6*OG6wqWoqC1_sFz*N775{M_8syb=cI zqSVBa)D(sC%#sWRcTeAd@J2py1_p)}PZ!4!iOab?ihPF{1X!MXeB|JnHsQg0-XHrK z)|y=eDmZEZvR|1iLkPeXX+8C+C#JK0o6U^riNFqJ)#s%uG*{nVbf$4QH0* zMl&!S*ru!`vC-Cc?X*Yx&u@5pf8DB?=TpC|Qd)KD_1nykA3yK9- zXMsm#F#`j)FbFd;%$g&?z`(#>;_2(k{)nB6*OF_E&+SzV3=EPbt`Q~9`MJ5Nc_j?a zMX8A;sVNHOnI#zt?w-B@;f;La3=9mNo-U3d5|>kZ75SP21YGX_X>e9Bh`6*wu6NDh zFHA8{(BZmezv7pjbIoT9#_#z)O^o-vJKq75^>4hpn{-Z3vZyY6 zdV_6|0^=*KzzJz-m!bosOpZkQ#j5^l5o%DGIdkWRO5XL`A0ONMWLf3k+^npX8-^I literal 0 HcmV?d00001 diff --git a/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-6.png b/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-6.png new file mode 100644 index 0000000000000000000000000000000000000000..30c4e4bcc2a57737edd9863ff443292aae7ec115 GIT binary patch literal 270 zcmeAS@N?(olHy`uVBq!ia0y~yVBi2@4mJh`h9ms@x)~T4*pj^6T^O1`@N!aVJ_7>- zXMsm#F#`j)FbFd;%$g&?z`(#>;_2(k{)nB6k3(io^K}6R1_sFz*N775{M_8syb=cI zqSVBa)D(sC%#sWRcTeAd@J2py1_p*QPZ!4!iOaR;7VxA-X&DLJ6{c&{6(e|<`X%19evtU>tCqaCd}>g}+L zA=7$3qeP+3?~fbO&wQWs-`Vri>$e-b($fCziVw4QX_0yGH|9s{%#-ZRN-k{cTb_Mm QU|?YIboFyt=akR{0C1LB(*OVf literal 0 HcmV?d00001 diff --git a/core/assets/sprites/sprites.atlas b/core/assets/sprites/sprites.atlas index 6ca44197c3..51cdebf247 100644 --- a/core/assets/sprites/sprites.atlas +++ b/core/assets/sprites/sprites.atlas @@ -34,28 +34,28 @@ bridge-conveyor-end index: -1 phase-conveyor-arrow rotate: false - xy: 587, 118 + xy: 637, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 phase-conveyor-bridge rotate: false - xy: 597, 128 + xy: 627, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 phase-conveyor-end rotate: false - xy: 587, 108 + xy: 637, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 router rotate: false - xy: 617, 108 + xy: 662, 158 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -195,21 +195,21 @@ plasma-drill-top index: -1 tungsten-drill rotate: false - xy: 670, 138 + xy: 429, 38 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 tungsten-drill-rotator rotate: false - xy: 680, 139 + xy: 433, 28 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 tungsten-drill-top rotate: false - xy: 677, 128 + xy: 433, 18 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -321,14 +321,14 @@ coal3 index: -1 dirt2 rotate: false - xy: 363, 5 + xy: 517, 121 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 dirt3 rotate: false - xy: 373, 15 + xy: 517, 111 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -342,42 +342,42 @@ dirtedge index: -1 grass-cliff-edge rotate: false - xy: 393, 15 + xy: 987, 355 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 grass-cliff-edge-1 rotate: false - xy: 393, 5 + xy: 997, 355 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 grass-cliff-edge-2 rotate: false - xy: 403, 25 + xy: 987, 345 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 grass-cliff-side rotate: false - xy: 403, 15 + xy: 997, 345 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 grass2 rotate: false - xy: 393, 25 + xy: 977, 357 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 grass3 rotate: false - xy: 383, 5 + xy: 977, 347 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -391,42 +391,42 @@ grassedge index: -1 ice-cliff-edge rotate: false - xy: 517, 121 + xy: 409, 48 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ice-cliff-edge-1 rotate: false - xy: 517, 111 + xy: 409, 38 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ice-cliff-edge-2 rotate: false - xy: 517, 101 + xy: 413, 28 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ice-cliff-side rotate: false - xy: 967, 357 + xy: 413, 18 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ice2 rotate: false - xy: 403, 5 + xy: 1007, 349 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ice3 rotate: false - xy: 517, 131 + xy: 1007, 339 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -440,35 +440,35 @@ iceedge index: -1 icerock2 rotate: false - xy: 967, 347 + xy: 413, 8 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 icerockshadow1 rotate: false - xy: 977, 357 + xy: 701, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 rockshadow1 rotate: false - xy: 977, 357 + xy: 701, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 icerockshadow2 rotate: false - xy: 977, 347 + xy: 711, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 rockshadow2 rotate: false - xy: 977, 347 + xy: 711, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -482,56 +482,56 @@ lavaedge index: -1 lead1 rotate: false - xy: 795, 302 + xy: 555, 171 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 lead2 rotate: false - xy: 507, 181 + xy: 565, 171 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 lead3 rotate: false - xy: 505, 171 + xy: 522, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 metalfloor2 rotate: false - xy: 567, 181 + xy: 542, 151 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 metalfloor3 rotate: false - xy: 555, 171 + xy: 540, 141 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 metalfloor4 rotate: false - xy: 565, 171 + xy: 527, 121 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 metalfloor5 rotate: false - xy: 522, 161 + xy: 537, 131 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 metalfloor6 rotate: false - xy: 532, 161 + xy: 552, 151 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -552,49 +552,49 @@ oiledge index: -1 rock2 rotate: false - xy: 627, 128 + xy: 647, 98 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 sand-cliff-edge rotate: false - xy: 637, 118 + xy: 657, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 sand-cliff-edge-1 rotate: false - xy: 647, 128 + xy: 657, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 sand-cliff-edge-2 rotate: false - xy: 637, 108 + xy: 657, 98 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 sand-cliff-side rotate: false - xy: 647, 118 + xy: 672, 159 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 sand2 rotate: false - xy: 637, 128 + xy: 660, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 sand3 rotate: false - xy: 627, 108 + xy: 657, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -608,49 +608,49 @@ sandedge index: -1 shrubshadow rotate: false - xy: 577, 98 + xy: 682, 149 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 snow-cliff-edge rotate: false - xy: 607, 98 + xy: 667, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 snow-cliff-edge-1 rotate: false - xy: 617, 98 + xy: 667, 98 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 snow-cliff-edge-2 rotate: false - xy: 627, 98 + xy: 670, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 snow-cliff-side rotate: false - xy: 637, 98 + xy: 680, 139 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 snow2 rotate: false - xy: 587, 98 + xy: 667, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 snow3 rotate: false - xy: 597, 98 + xy: 667, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -671,42 +671,42 @@ spaceedge index: -1 stone-cliff-edge rotate: false - xy: 662, 148 + xy: 677, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 stone-cliff-edge-1 rotate: false - xy: 660, 138 + xy: 677, 98 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 stone-cliff-edge-2 rotate: false - xy: 657, 128 + xy: 889, 317 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 stone-cliff-side rotate: false - xy: 657, 118 + xy: 516, 63 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 stone2 rotate: false - xy: 647, 98 + xy: 677, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 stone3 rotate: false - xy: 662, 158 + xy: 677, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -720,91 +720,91 @@ stoneedge index: -1 thorium1 rotate: false - xy: 657, 108 + xy: 516, 53 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 thorium2 rotate: false - xy: 657, 98 + xy: 506, 49 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 thorium3 rotate: false - xy: 672, 159 + xy: 496, 48 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 titanium1 rotate: false - xy: 682, 159 + xy: 516, 43 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 titanium2 rotate: false - xy: 672, 149 + xy: 506, 39 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 titanium3 rotate: false - xy: 682, 149 + xy: 419, 38 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 tungsten1 rotate: false - xy: 667, 118 + xy: 423, 18 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 tungsten2 rotate: false - xy: 667, 108 + xy: 423, 8 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 tungsten3 rotate: false - xy: 667, 98 + xy: 516, 33 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 water-cliff-edge rotate: false - xy: 677, 118 + xy: 433, 8 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 water-cliff-edge-1 rotate: false - xy: 677, 108 + xy: 439, 38 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 water-cliff-edge-2 rotate: false - xy: 677, 98 + xy: 443, 28 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 water-cliff-side rotate: false - xy: 889, 317 + xy: 443, 18 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -867,14 +867,14 @@ border index: -1 conduit-liquid rotate: false - xy: 353, 5 + xy: 383, 15 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 cross-1 rotate: false - xy: 373, 25 + xy: 517, 131 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -902,7 +902,7 @@ cross-4 index: -1 enemyspawn rotate: false - xy: 383, 15 + xy: 967, 347 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -916,14 +916,14 @@ nuclearreactor-shadow index: -1 place-arrow rotate: false - xy: 597, 118 + xy: 647, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 playerspawn rotate: false - xy: 607, 128 + xy: 637, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -1068,37 +1068,128 @@ conduit-bottom orig: 8, 8 offset: 0, 0 index: -1 -conduit-top +conduit-bottom-0 + rotate: false + xy: 353, 5 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +conduit-bottom-1 rotate: false xy: 363, 15 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 +conduit-bottom-2 + rotate: false + xy: 373, 25 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +conduit-bottom-3 + rotate: false + xy: 363, 5 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +conduit-bottom-4 + rotate: false + xy: 373, 15 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +conduit-bottom-5 + rotate: false + xy: 383, 25 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +conduit-bottom-6 + rotate: false + xy: 373, 5 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +conduit-top-0 + rotate: false + xy: 393, 25 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +conduit-top-1 + rotate: false + xy: 383, 5 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +conduit-top-2 + rotate: false + xy: 393, 15 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +conduit-top-3 + rotate: false + xy: 393, 5 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +conduit-top-4 + rotate: false + xy: 403, 25 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +conduit-top-5 + rotate: false + xy: 403, 15 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +conduit-top-6 + rotate: false + xy: 403, 5 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 liquid-router rotate: false - xy: 547, 181 + xy: 520, 141 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 liquid-router-bottom rotate: false - xy: 535, 171 + xy: 532, 151 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 liquid-router-liquid rotate: false - xy: 557, 181 + xy: 530, 141 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 liquid-router-top rotate: false - xy: 545, 171 + xy: 527, 131 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -1126,35 +1217,70 @@ liquid-tank-top index: -1 phase-conduit-arrow rotate: false - xy: 577, 118 + xy: 627, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 phase-conduit-bridge rotate: false - xy: 587, 128 + xy: 617, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 phase-conduit-end rotate: false - xy: 577, 108 + xy: 627, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -pulse-conduit-bottom +pulse-conduit-top-0 rotate: false - xy: 597, 108 + xy: 647, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -pulse-conduit-top +pulse-conduit-top-1 rotate: false - xy: 607, 118 + xy: 647, 108 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +pulse-conduit-top-2 + rotate: false + xy: 567, 98 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +pulse-conduit-top-3 + rotate: false + xy: 577, 98 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +pulse-conduit-top-4 + rotate: false + xy: 587, 98 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +pulse-conduit-top-5 + rotate: false + xy: 597, 98 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +pulse-conduit-top-6 + rotate: false + xy: 607, 98 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -1259,7 +1385,7 @@ nuclear-reactor-lights index: -1 rtg-generator-top rotate: false - xy: 627, 118 + xy: 662, 148 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -1434,14 +1560,14 @@ cultivator-top index: -1 lavasmelter rotate: false - xy: 785, 302 + xy: 567, 181 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 oilrefinery rotate: false - xy: 542, 161 + xy: 550, 141 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -1483,21 +1609,21 @@ poweralloysmelter-top index: -1 pulverizer rotate: false - xy: 617, 128 + xy: 617, 98 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 pulverizer-rotator rotate: false - xy: 607, 108 + xy: 627, 98 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 separator-liquid rotate: false - xy: 647, 108 + xy: 682, 159 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -1595,7 +1721,7 @@ mass-driver-turret index: -1 duo rotate: false - xy: 373, 5 + xy: 967, 357 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -1798,7 +1924,7 @@ reconstructor-open index: -1 repair-point-turret rotate: false - xy: 617, 118 + xy: 637, 98 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -1819,7 +1945,7 @@ door-large-open index: -1 door-open rotate: false - xy: 383, 25 + xy: 517, 101 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -1987,7 +2113,7 @@ shell-back index: -1 shot rotate: false - xy: 567, 98 + xy: 672, 149 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -2001,7 +2127,7 @@ transfer index: -1 transfer-arrow rotate: false - xy: 667, 128 + xy: 423, 28 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -3352,28 +3478,28 @@ block-icon-wave index: -1 liquid-icon-cryofluid rotate: false - xy: 527, 181 + xy: 542, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 liquid-icon-lava rotate: false - xy: 515, 171 + xy: 552, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 liquid-icon-oil rotate: false - xy: 537, 181 + xy: 562, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 liquid-icon-water rotate: false - xy: 525, 171 + xy: 522, 151 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -3429,525 +3555,525 @@ mech-icon-tau-mech index: -1 ore-coal-grass1 rotate: false - xy: 552, 161 + xy: 527, 111 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-grass2 rotate: false - xy: 562, 161 + xy: 537, 121 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-grass3 rotate: false - xy: 522, 151 + xy: 547, 131 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-ice1 rotate: false - xy: 520, 141 + xy: 562, 151 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-ice2 rotate: false - xy: 532, 151 + xy: 560, 141 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-ice3 rotate: false - xy: 530, 141 + xy: 527, 101 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-sand1 rotate: false - xy: 527, 131 + xy: 537, 111 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-sand2 rotate: false - xy: 542, 151 + xy: 547, 121 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-sand3 rotate: false - xy: 540, 141 + xy: 557, 131 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-snow1 rotate: false - xy: 527, 121 + xy: 537, 101 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-snow2 rotate: false - xy: 537, 131 + xy: 547, 111 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-snow3 rotate: false - xy: 552, 151 + xy: 557, 121 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-stone1 rotate: false - xy: 550, 141 + xy: 547, 101 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-stone2 rotate: false - xy: 527, 111 + xy: 557, 111 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-stone3 rotate: false - xy: 537, 121 + xy: 557, 101 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-grass1 rotate: false - xy: 547, 131 + xy: 577, 178 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-grass2 rotate: false - xy: 562, 151 + xy: 587, 178 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-grass3 rotate: false - xy: 560, 141 + xy: 597, 178 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-ice1 rotate: false - xy: 527, 101 + xy: 607, 178 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-ice2 rotate: false - xy: 537, 111 + xy: 617, 178 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-ice3 rotate: false - xy: 547, 121 + xy: 627, 178 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-sand1 rotate: false - xy: 557, 131 + xy: 637, 178 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-sand2 rotate: false - xy: 537, 101 + xy: 575, 168 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-sand3 rotate: false - xy: 547, 111 + xy: 585, 168 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-snow1 rotate: false - xy: 557, 121 + xy: 595, 168 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-snow2 rotate: false - xy: 547, 101 + xy: 605, 168 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-snow3 rotate: false - xy: 557, 111 + xy: 615, 168 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-stone1 rotate: false - xy: 557, 101 + xy: 625, 168 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-stone2 rotate: false - xy: 577, 178 + xy: 635, 168 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-stone3 rotate: false - xy: 587, 178 + xy: 572, 158 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-grass1 rotate: false - xy: 597, 178 + xy: 582, 158 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-grass2 rotate: false - xy: 607, 178 + xy: 592, 158 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-grass3 rotate: false - xy: 617, 178 + xy: 602, 158 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-ice1 rotate: false - xy: 627, 178 + xy: 612, 158 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-ice2 rotate: false - xy: 637, 178 + xy: 622, 158 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-ice3 rotate: false - xy: 575, 168 + xy: 632, 158 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-sand1 rotate: false - xy: 585, 168 + xy: 572, 148 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-sand2 rotate: false - xy: 595, 168 + xy: 582, 148 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-sand3 rotate: false - xy: 605, 168 + xy: 592, 148 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-snow1 rotate: false - xy: 615, 168 + xy: 602, 148 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-snow2 rotate: false - xy: 625, 168 + xy: 612, 148 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-snow3 rotate: false - xy: 635, 168 + xy: 622, 148 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-stone1 rotate: false - xy: 572, 158 + xy: 632, 148 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-stone2 rotate: false - xy: 582, 158 + xy: 645, 168 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-stone3 rotate: false - xy: 592, 158 + xy: 655, 168 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-grass1 rotate: false - xy: 602, 158 + xy: 642, 158 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-grass2 rotate: false - xy: 612, 158 + xy: 642, 148 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-grass3 rotate: false - xy: 622, 158 + xy: 652, 158 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-ice1 rotate: false - xy: 632, 158 + xy: 652, 148 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-ice2 rotate: false - xy: 572, 148 + xy: 570, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-ice3 rotate: false - xy: 582, 148 + xy: 580, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-sand1 rotate: false - xy: 592, 148 + xy: 590, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-sand2 rotate: false - xy: 602, 148 + xy: 600, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-sand3 rotate: false - xy: 612, 148 + xy: 610, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-snow1 rotate: false - xy: 622, 148 + xy: 620, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-snow2 rotate: false - xy: 632, 148 + xy: 630, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-snow3 rotate: false - xy: 645, 168 + xy: 640, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-stone1 rotate: false - xy: 655, 168 + xy: 650, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-stone2 rotate: false - xy: 642, 158 + xy: 567, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-stone3 rotate: false - xy: 642, 148 + xy: 567, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-grass1 rotate: false - xy: 652, 158 + xy: 577, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-grass2 rotate: false - xy: 652, 148 + xy: 567, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-grass3 rotate: false - xy: 570, 138 + xy: 577, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-ice1 rotate: false - xy: 580, 138 + xy: 587, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-ice2 rotate: false - xy: 590, 138 + xy: 577, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-ice3 rotate: false - xy: 600, 138 + xy: 587, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-sand1 rotate: false - xy: 610, 138 + xy: 597, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-sand2 rotate: false - xy: 620, 138 + xy: 587, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-sand3 rotate: false - xy: 630, 138 + xy: 597, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-snow1 rotate: false - xy: 640, 138 + xy: 607, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-snow2 rotate: false - xy: 650, 138 + xy: 597, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-snow3 rotate: false - xy: 567, 128 + xy: 607, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-stone1 rotate: false - xy: 567, 118 + xy: 617, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-stone2 rotate: false - xy: 577, 128 + xy: 607, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-stone3 rotate: false - xy: 567, 108 + xy: 617, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -3982,112 +4108,112 @@ vtol index: -1 item-biomatter rotate: false - xy: 997, 355 + xy: 731, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-blast-compound rotate: false - xy: 987, 345 + xy: 775, 302 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-carbide rotate: false - xy: 997, 345 + xy: 785, 302 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-coal rotate: false - xy: 1007, 349 + xy: 795, 302 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-lead rotate: false - xy: 1007, 339 + xy: 507, 181 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-phase-matter rotate: false - xy: 409, 48 + xy: 505, 171 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-plastanium rotate: false - xy: 409, 38 + xy: 517, 181 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-pyratite rotate: false - xy: 413, 28 + xy: 527, 181 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-sand rotate: false - xy: 413, 18 + xy: 515, 171 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-silicon rotate: false - xy: 413, 8 + xy: 537, 181 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-stone rotate: false - xy: 701, 161 + xy: 525, 171 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-surge-alloy rotate: false - xy: 711, 161 + xy: 547, 181 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-thorium rotate: false - xy: 721, 161 + xy: 535, 171 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-titanium rotate: false - xy: 731, 161 + xy: 557, 181 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-tungsten rotate: false - xy: 775, 302 + xy: 545, 171 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 liquid-icon rotate: false - xy: 517, 181 + xy: 532, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -4740,7 +4866,7 @@ icon-itch.io index: -1 icon-items-none rotate: false - xy: 987, 355 + xy: 721, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 diff --git a/core/assets/sprites/sprites.png b/core/assets/sprites/sprites.png index da1679d01eb1453f5328318dddca9d6e0a7ceeab..9ae2122cd9b1cc99a5bf0de7e7a93dbe559e86b4 100644 GIT binary patch delta 132757 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

I^6kc128QBgel0(~Gc`zXEa<&l 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 sud}~nAG~#Y*P!2-Yv&TSSw$uhn^L^{-dLQg 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

%$RclK<;-P-C-TqHv`K`?5J3(u|9htx9axv^k zEON+|?5#c#ctou z=7=YhGBpUR`&B6ZpFIE1k-vYQ+q*ZuuTO`x8WN(p7?ZS|Rxl`-ao0~ZY6OkaXWB9G zGPFRvY;|pAQ`qYA*i`${Du#^j_q7&h-2H#WsL4`}!RE3R6GO@2#tHs31P(LLM++z@7 zn2;s^=K;I#$NsXDs?)FVF=((TyuR~t`TTclnHhg=TkOeTU;QmIHn!~B⋙w^0g+K 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 diff --git a/core/src/io/anuke/mindustry/entities/TileEntity.java b/core/src/io/anuke/mindustry/entities/TileEntity.java index 61e3e44989..4c784bebd5 100644 --- a/core/src/io/anuke/mindustry/entities/TileEntity.java +++ b/core/src/io/anuke/mindustry/entities/TileEntity.java @@ -151,7 +151,10 @@ public class TileEntity extends BaseEntity implements TargetTrait { for (GridPoint2 point : nearby) { Tile other = world.tile(tile.x + point.x, tile.y + point.y); //remove this tile from all nearby tile's proximities - if(other != null) other = other.target(); + if(other != null){ + other = other.target(); + other.block().onProximityUpdate(other); + } if(other != null && other.entity != null){ other.entity.proximity.removeValue(tile, true); } @@ -165,7 +168,12 @@ public class TileEntity extends BaseEntity implements TargetTrait { GridPoint2[] nearby = Edges.getEdges(tile.block().size); for (GridPoint2 point : nearby) { Tile other = world.tile(tile.x + point.x, tile.y + point.y); - if(other != null) other = other.target(); + + if(other != null){ + other.block().onProximityUpdate(other); + other = other.target(); + } + if(other != null && other.entity != null){ tmpTiles.add(other); @@ -180,6 +188,8 @@ public class TileEntity extends BaseEntity implements TargetTrait { for(Tile tile : tmpTiles){ proximity.add(tile); } + + tile.block().onProximityUpdate(tile); } public Array proximity(){ diff --git a/core/src/io/anuke/mindustry/world/BaseBlock.java b/core/src/io/anuke/mindustry/world/BaseBlock.java index 00863d5f2c..68dbda64a5 100644 --- a/core/src/io/anuke/mindustry/world/BaseBlock.java +++ b/core/src/io/anuke/mindustry/world/BaseBlock.java @@ -64,6 +64,8 @@ public abstract class BaseBlock { } + public void onProximityUpdate(Tile tile){} + public void handleItem(Item item, Tile tile, Tile source){ tile.entity.items.add(item, 1); } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java index f4beeb228a..e5bcdbf84f 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java @@ -16,6 +16,9 @@ import java.io.IOException; public class Conduit extends LiquidBlock { protected final int timerFlow = timers++; + protected TextureRegion[] topRegions = new TextureRegion[7]; + protected TextureRegion[] botRegions = new TextureRegion[7]; + public Conduit(String name) { super(name); rotate = true; @@ -27,24 +30,59 @@ public class Conduit extends LiquidBlock { super.load(); liquidRegion = Draw.region("conduit-liquid"); + for (int i = 0; i < topRegions.length; i++) { + topRegions[i] = Draw.region(name + "-top-" + i); + botRegions[i] = Draw.region("conduit-bottom-" + i); + } + } + public void onProximityUpdate(Tile tile){ + ConduitEntity entity = tile.entity(); + entity.blendbits = 0; + + if(blends(tile, 0)){ + if(blends(tile, 2) && blends(tile, 1) && blends(tile, 3)) { + entity.blendbits = 3; + }else if(blends(tile, 1) && blends(tile, 3)) { + entity.blendbits = 6; + }else if(blends(tile, 1) && blends(tile, 2)) { + entity.blendbits = 2; + }else if(blends(tile, 3) && blends(tile, 2)) { + entity.blendbits = 4; + }else if(blends(tile, 1)) { + entity.blendbits = 5; + }else if(blends(tile, 3)) { + entity.blendbits = 1; + } + }else if(blends(tile, 1)) { + entity.blendbits = 5; + }else if(blends(tile, 3)) { + entity.blendbits = 1; + } + } + + private boolean blends(Tile tile, int direction){ + Tile other = tile.getNearby(Mathf.mod(tile.getRotation() - direction, 4)); + if(other == null || !(other.block().hasLiquids)) return false; + return (tile.getNearby(tile.getRotation()) == other) + || (!other.block().rotate || other.getNearby(other.getRotation()) == tile); } @Override public void draw(Tile tile){ ConduitEntity entity = tile.entity(); LiquidModule mod = tile.entity.liquids; + int rotation = tile.getRotation() * 90; - int rotation = rotate ? tile.getRotation() * 90 : 0; - - Draw.rect(bottomRegion, tile.drawx(), tile.drawy(), rotation); + Draw.colorl(0.34f); + Draw.rect(botRegions[entity.blendbits], tile.drawx(), tile.drawy(), rotation); Draw.color(mod.current().color); Draw.alpha(entity.smoothLiquid); - Draw.rect(liquidRegion, tile.drawx(), tile.drawy(), rotation); + Draw.rect(botRegions[entity.blendbits], tile.drawx(), tile.drawy(), rotation); Draw.color(); - Draw.rect(topRegion, tile.drawx(), tile.drawy(), rotation); + Draw.rect(topRegions[entity.blendbits], tile.drawx(), tile.drawy(), rotation); } @Override @@ -62,7 +100,10 @@ public class Conduit extends LiquidBlock { @Override public TextureRegion[] getIcon(){ - return new TextureRegion[]{Draw.region(name() + "-bottom"), Draw.region(name() + "-top")}; + if(icon == null){ + icon = new TextureRegion[]{Draw.region("conduit-bottom"), Draw.region(name + "-top-0")}; + } + return icon; } @Override @@ -78,6 +119,7 @@ public class Conduit extends LiquidBlock { public static class ConduitEntity extends TileEntity { public float smoothLiquid; + public byte blendbits; @Override public void write(DataOutputStream stream) throws IOException { From 3aafffabc1af35df92a23578e2a460adcc21c58a Mon Sep 17 00:00:00 2001 From: Anuken Date: Tue, 10 Jul 2018 11:41:51 -0400 Subject: [PATCH 20/47] Added BlockConsumeFragment --- .../src/io/anuke/mindustry/entities/Unit.java | 4 +- .../anuke/mindustry/input/InputHandler.java | 26 ++++-- .../ui/fragments/BlockConsumeFragment.java | 91 +++++++++++++++++++ .../ui/fragments/OverlayFragment.java | 3 + .../mindustry/world/consumers/Consume.java | 20 +++- .../mindustry/world/consumers/Consumers.java | 10 ++ 6 files changed, 145 insertions(+), 9 deletions(-) create mode 100644 core/src/io/anuke/mindustry/ui/fragments/BlockConsumeFragment.java diff --git a/core/src/io/anuke/mindustry/entities/Unit.java b/core/src/io/anuke/mindustry/entities/Unit.java index 53619b7fdd..016925e73f 100644 --- a/core/src/io/anuke/mindustry/entities/Unit.java +++ b/core/src/io/anuke/mindustry/entities/Unit.java @@ -228,7 +228,9 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ x += velocity.x / getMass() * Timers.delta(); y += velocity.y / getMass() * Timers.delta(); - elevation = Mathf.lerpDelta(elevation, tile.elevation, 0.04f); + if(tile != null){ + elevation = Mathf.lerpDelta(elevation, tile.elevation, 0.04f); + } }else{ boolean onLiquid = floor.isLiquid; diff --git a/core/src/io/anuke/mindustry/input/InputHandler.java b/core/src/io/anuke/mindustry/input/InputHandler.java index 68e048cf3e..689a637db1 100644 --- a/core/src/io/anuke/mindustry/input/InputHandler.java +++ b/core/src/io/anuke/mindustry/input/InputHandler.java @@ -101,8 +101,7 @@ public abstract class InputHandler extends InputAdapter{ boolean tileTapped(Tile tile){ tile = tile.target(); - boolean consumed = false; - boolean showedInventory = false; + boolean consumed = false, showedInventory = false, showedConsume = false; //check if tapped block is configurable if(tile.block().configurable && tile.getTeam() == player.getTeam()){ @@ -129,16 +128,28 @@ public abstract class InputHandler extends InputAdapter{ //consume tap event if necessary if(tile.getTeam() == player.getTeam() && tile.block().consumesTap){ consumed = true; - }else if(tile.getTeam() == player.getTeam() && tile.block().synthetic() && tile.block().hasItems && tile.entity.items.total() > 0 && !consumed){ - frag.inv.showFor(tile); - consumed = true; - showedInventory = true; + }else if(tile.getTeam() == player.getTeam() && tile.block().synthetic() && !consumed) { + if(tile.block().hasItems && tile.entity.items.total() > 0) { + frag.inv.showFor(tile); + consumed = true; + showedInventory = true; + } + + if(tile.block().consumes.hasAny()){ + frag.consume.show(tile); + consumed = true; + showedConsume = true; + } } if(!showedInventory){ frag.inv.hide(); } + if(!showedConsume){ + frag.consume.hide(); + } + return consumed; } @@ -166,7 +177,8 @@ public abstract class InputHandler extends InputAdapter{ } boolean canMine(Tile tile){ - return tile.floor().drops != null && tile.floor().drops.item.hardness <= player.mech.drillPower + return !ui.hasMouse() + && tile.floor().drops != null && tile.floor().drops.item.hardness <= player.mech.drillPower && !tile.floor().playerUnmineable && player.inventory.canAcceptItem(tile.floor().drops.item) && Units.getClosestEnemy(player.getTeam(), tile.worldx(), tile.worldy(), 40f, e -> true) == null //don't being mining when an enemy is near diff --git a/core/src/io/anuke/mindustry/ui/fragments/BlockConsumeFragment.java b/core/src/io/anuke/mindustry/ui/fragments/BlockConsumeFragment.java new file mode 100644 index 0000000000..93f260d0a2 --- /dev/null +++ b/core/src/io/anuke/mindustry/ui/fragments/BlockConsumeFragment.java @@ -0,0 +1,91 @@ +package io.anuke.mindustry.ui.fragments; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.Align; +import com.badlogic.gdx.utils.ObjectSet; +import io.anuke.mindustry.core.GameState.State; +import io.anuke.mindustry.entities.TileEntity; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.consumers.Consume; +import io.anuke.ucore.core.Graphics; +import io.anuke.ucore.scene.Group; +import io.anuke.ucore.scene.ui.layout.Table; + +import static io.anuke.mindustry.Vars.state; +import static io.anuke.mindustry.Vars.tilesize; + +public class BlockConsumeFragment extends Fragment { + private Table table; + private boolean visible; + + @Override + public void build(Group parent) { + table = new Table(); + table.setVisible(() -> !state.is(State.menu) && visible); + table.setTransform(true); + parent.setTransform(true); + parent.addChild(table); + } + + public void show(Tile tile){ + ObjectSet consumers = new ObjectSet<>(); + TileEntity entity = tile.entity; + Block block = tile.block(); + + //table.background("clear"); + rebuild(block, entity); + visible = true; + + table.update(() -> { + + if(tile.entity == null){ + hide(); + return; + } + + boolean rebuild = false; + + for(Consume c : block.consumes.array()){ + boolean valid = c.isOptional() || c.valid(block, entity); + + if(consumers.contains(c) == valid){ + if(valid){ + consumers.remove(c); + }else{ + consumers.add(c); + } + rebuild = true; + } + } + + if(rebuild){ + rebuild(block, entity); + } + + Vector2 v = Graphics.screen(tile.drawx() - tile.block().size * tilesize/2f, tile.drawy() + tile.block().size * tilesize/2f); + table.pack(); + table.setPosition(v.x, v.y, Align.topRight); + }); + + table.act(Gdx.graphics.getDeltaTime()); + } + + public void hide(){ + table.clear(); + table.update(() -> {}); + visible = false; + } + + private void rebuild(Block block, TileEntity entity){ + table.clearChildren(); + + for(Consume c : block.consumes.array()){ + if(!c.isOptional() && !c.valid(block, entity)){ + c.build(table); + table.row(); + } + } + } +} diff --git a/core/src/io/anuke/mindustry/ui/fragments/OverlayFragment.java b/core/src/io/anuke/mindustry/ui/fragments/OverlayFragment.java index 08cfff42c6..060467f109 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/OverlayFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/OverlayFragment.java @@ -7,6 +7,7 @@ import io.anuke.ucore.scene.Group; public class OverlayFragment extends Fragment{ public final BlockInventoryFragment inv; public final BlockConfigFragment config; + public final BlockConsumeFragment consume; private Group group = new Group(); private InputHandler input; @@ -16,6 +17,7 @@ public class OverlayFragment extends Fragment{ inv = new BlockInventoryFragment(input); config = new BlockConfigFragment(input); + consume = new BlockConsumeFragment(); } @Override @@ -25,6 +27,7 @@ public class OverlayFragment extends Fragment{ inv.build(group); config.build(group); + consume.build(group); input.buildUI(group); } diff --git a/core/src/io/anuke/mindustry/world/consumers/Consume.java b/core/src/io/anuke/mindustry/world/consumers/Consume.java index 6e3cbabb0e..352f0c8cfb 100644 --- a/core/src/io/anuke/mindustry/world/consumers/Consume.java +++ b/core/src/io/anuke/mindustry/world/consumers/Consume.java @@ -1,8 +1,12 @@ package io.anuke.mindustry.world.consumers; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.utils.reflect.ClassReflection; import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.meta.BlockStats; +import io.anuke.ucore.scene.ui.Tooltip; +import io.anuke.ucore.scene.ui.layout.Table; public abstract class Consume { private boolean optional; @@ -26,8 +30,22 @@ public abstract class Consume { return update; } - public void draw(TileEntity entity){ + public void build(Table table){ + Table t = new Table("clear"); + t.margin(4); + buildTooltip(t); + table.table("clear", out -> { + out.addImage(getIcon()).size(10*4).color(Color.RED); + }).size(10*4).get().addListener(new Tooltip<>(t)); + } + + public void buildTooltip(Table table){ + table.add("no " + ClassReflection.getSimpleName(getClass()).replace("Consume", "")); + } + + public String getIcon(){ + return "icon-power"; } public abstract void update(Block block, TileEntity entity); diff --git a/core/src/io/anuke/mindustry/world/consumers/Consumers.java b/core/src/io/anuke/mindustry/world/consumers/Consumers.java index 6873f3f7be..8bb729054f 100644 --- a/core/src/io/anuke/mindustry/world/consumers/Consumers.java +++ b/core/src/io/anuke/mindustry/world/consumers/Consumers.java @@ -8,10 +8,12 @@ import io.anuke.mindustry.type.ItemStack; import io.anuke.mindustry.type.Liquid; import io.anuke.mindustry.world.Block; import io.anuke.ucore.function.Consumer; +import io.anuke.ucore.util.ThreadArray; public class Consumers { private ObjectMap, Consume> map = new ObjectMap<>(); private ObjectSet> required = new ObjectSet<>(); + private ThreadArray results = new ThreadArray<>(); public void require(Class type){ required.add(type); @@ -23,6 +25,10 @@ public class Consumers { throw new RuntimeException("Missing required consumer of type \"" + ClassReflection.getSimpleName(c) + "\" in block \"" + block.name + "\"!"); } } + + for(Consume cons : map.values()){ + results.add(cons); + } } public ConsumePower power(float amount){ @@ -93,6 +99,10 @@ public class Consumers { return map.values(); } + public ThreadArray array() { + return results; + } + public boolean hasAny(){ return map.size > 0; } From cb58eb82fc06f68f3d65ead3f9bfbaa07916bf79 Mon Sep 17 00:00:00 2001 From: Anuken Date: Tue, 10 Jul 2018 16:53:35 -0400 Subject: [PATCH 21/47] Added requirement display / Text fixes / Better crash reports --- build.gradle | 2 +- .../assets-raw/sprites/ui/icons/icon-item.png | Bin 0 -> 189 bytes .../sprites/ui/icons/icon-missing.png | Bin 0 -> 191 bytes core/assets/bundles/bundle.properties | 1 + core/assets/sprites/sprites.atlas | 742 +++++++++--------- core/assets/sprites/sprites.png | Bin 135823 -> 135832 bytes .../content/blocks/CraftingBlocks.java | 1 + .../mindustry/editor/MapEditorDialog.java | 16 +- .../mindustry/graphics/OverlayRenderer.java | 12 - core/src/io/anuke/mindustry/io/Version.java | 2 +- core/src/io/anuke/mindustry/ui/ItemImage.java | 8 +- .../ui/dialogs/ContentInfoDialog.java | 2 +- .../ui/fragments/BlockConsumeFragment.java | 29 +- .../mindustry/world/consumers/Consume.java | 23 +- .../world/consumers/ConsumeItem.java | 13 + .../world/consumers/ConsumeItemFilter.java | 32 +- .../world/consumers/ConsumeItems.java | 14 + .../world/consumers/ConsumeLiquid.java | 11 + .../world/consumers/ConsumeLiquidFilter.java | 39 +- .../world/consumers/ConsumePower.java | 11 + .../anuke/mindustry/desktop/CrashHandler.java | 6 +- .../mindustry/desktop/DesktopLauncher.java | 76 +- 22 files changed, 579 insertions(+), 461 deletions(-) create mode 100644 core/assets-raw/sprites/ui/icons/icon-item.png create mode 100644 core/assets-raw/sprites/ui/icons/icon-missing.png diff --git a/build.gradle b/build.gradle index ede4be0f00..2181519448 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ allprojects { gdxVersion = '1.9.8' roboVMVersion = '2.3.0' aiVersion = '1.8.1' - uCoreVersion = 'f937c5cad6' + uCoreVersion = 'e1749b8798' getVersionString = { String buildVersion = getBuildVersion() diff --git a/core/assets-raw/sprites/ui/icons/icon-item.png b/core/assets-raw/sprites/ui/icons/icon-item.png new file mode 100644 index 0000000000000000000000000000000000000000..0e99951b81095d884f012c96706f29ea13796823 GIT binary patch literal 189 zcmeAS@N?(olHy`uVBq!ia0y~yU;weXIM^5%7j~AJ`6b<*z)Ha#_|lz9`}Aq3XgghwS8a lJU?~oBsibxUz}gb@J3TXEaE25Zw3Yi22WQ%mvv4FO#nRBIbZ+) literal 0 HcmV?d00001 diff --git a/core/assets-raw/sprites/ui/icons/icon-missing.png b/core/assets-raw/sprites/ui/icons/icon-missing.png new file mode 100644 index 0000000000000000000000000000000000000000..fcae107678d4fb1ae08ce76d5ed859092533f152 GIT binary patch literal 191 zcmeAS@N?(olHy`uVBq!ia0y~yU;weXIM^5%7k=hC~FUYu|)RY^ev38UzIs?J7lF97#J8lUHx3vIVCg!0EI?5*#H0l literal 0 HcmV?d00001 diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index dc37a8a402..1ed040bc32 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -292,6 +292,7 @@ text.blocks.inputfuel=Fuel text.blocks.fuelburntime=Fuel Burn Time text.blocks.inputcapacity=Input capacity text.blocks.outputcapacity=Output capacity +text.blocks.required=Required: text.unit.blocks=blocks text.unit.powersecond=power units/second diff --git a/core/assets/sprites/sprites.atlas b/core/assets/sprites/sprites.atlas index 51cdebf247..be782dff2f 100644 --- a/core/assets/sprites/sprites.atlas +++ b/core/assets/sprites/sprites.atlas @@ -13,49 +13,49 @@ background index: -1 bridge-conveyor-arrow rotate: false - xy: 333, 25 + xy: 333, 5 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 bridge-conveyor-bridge rotate: false - xy: 323, 5 + xy: 343, 15 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 bridge-conveyor-end rotate: false - xy: 333, 15 + xy: 353, 25 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 phase-conveyor-arrow rotate: false - xy: 637, 128 + xy: 627, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 phase-conveyor-bridge rotate: false - xy: 627, 108 + xy: 637, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 phase-conveyor-end rotate: false - xy: 637, 118 + xy: 627, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 router rotate: false - xy: 662, 158 + xy: 647, 98 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -97,21 +97,21 @@ blast-drill-top index: -1 carbide-drill rotate: false - xy: 343, 25 + xy: 343, 5 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 carbide-drill-rotator rotate: false - xy: 333, 5 + xy: 353, 15 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 carbide-drill-top rotate: false - xy: 343, 15 + xy: 363, 25 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -195,21 +195,21 @@ plasma-drill-top index: -1 tungsten-drill rotate: false - xy: 429, 38 + xy: 741, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 tungsten-drill-rotator rotate: false - xy: 433, 28 + xy: 747, 181 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 tungsten-drill-top rotate: false - xy: 433, 18 + xy: 747, 171 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -279,14 +279,14 @@ block-icon-blackstone index: -1 blackstone2 rotate: false - xy: 974, 367 + xy: 512, 72 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 blackstone3 rotate: false - xy: 512, 83 + xy: 957, 359 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -300,35 +300,35 @@ blackstoneedge index: -1 coal1 rotate: false - xy: 353, 25 + xy: 353, 5 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 coal2 rotate: false - xy: 343, 5 + xy: 363, 15 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 coal3 rotate: false - xy: 353, 15 + xy: 373, 25 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 dirt2 rotate: false - xy: 517, 121 + xy: 967, 356 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 dirt3 rotate: false - xy: 517, 111 + xy: 977, 356 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -342,42 +342,42 @@ dirtedge index: -1 grass-cliff-edge rotate: false - xy: 987, 355 + xy: 997, 345 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 grass-cliff-edge-1 rotate: false - xy: 997, 355 + xy: 1007, 349 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 grass-cliff-edge-2 rotate: false - xy: 987, 345 + xy: 1007, 339 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 grass-cliff-side rotate: false - xy: 997, 345 + xy: 409, 48 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 grass2 rotate: false - xy: 977, 357 + xy: 997, 355 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 grass3 rotate: false - xy: 977, 347 + xy: 987, 345 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -391,42 +391,42 @@ grassedge index: -1 ice-cliff-edge rotate: false - xy: 409, 48 + xy: 971, 336 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ice-cliff-edge-1 rotate: false - xy: 409, 38 + xy: 413, 28 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ice-cliff-edge-2 rotate: false - xy: 413, 28 + xy: 413, 18 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ice-cliff-side rotate: false - xy: 413, 18 + xy: 413, 8 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ice2 rotate: false - xy: 1007, 349 + xy: 409, 38 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ice3 rotate: false - xy: 1007, 339 + xy: 961, 336 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -440,35 +440,35 @@ iceedge index: -1 icerock2 rotate: false - xy: 413, 8 + xy: 981, 335 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 icerockshadow1 rotate: false - xy: 701, 161 + xy: 991, 335 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 rockshadow1 rotate: false - xy: 701, 161 + xy: 991, 335 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 icerockshadow2 rotate: false - xy: 711, 161 + xy: 701, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 rockshadow2 rotate: false - xy: 711, 161 + xy: 701, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -482,56 +482,56 @@ lavaedge index: -1 lead1 rotate: false - xy: 555, 171 + xy: 567, 181 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 lead2 rotate: false - xy: 565, 171 + xy: 555, 171 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 lead3 rotate: false - xy: 522, 161 + xy: 565, 171 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 metalfloor2 rotate: false - xy: 542, 151 + xy: 527, 131 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 metalfloor3 rotate: false - xy: 540, 141 + xy: 542, 151 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 metalfloor4 rotate: false - xy: 527, 121 + xy: 540, 141 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 metalfloor5 rotate: false - xy: 537, 131 + xy: 527, 121 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 metalfloor6 rotate: false - xy: 552, 151 + xy: 537, 131 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -552,49 +552,49 @@ oiledge index: -1 rock2 rotate: false - xy: 647, 98 + xy: 637, 98 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 sand-cliff-edge rotate: false - xy: 657, 118 + xy: 657, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 sand-cliff-edge-1 rotate: false - xy: 657, 108 + xy: 657, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 sand-cliff-edge-2 rotate: false - xy: 657, 98 + xy: 657, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 sand-cliff-side rotate: false - xy: 672, 159 + xy: 657, 98 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 sand2 rotate: false - xy: 660, 138 + xy: 662, 148 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 sand3 rotate: false - xy: 657, 128 + xy: 660, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -608,49 +608,49 @@ sandedge index: -1 shrubshadow rotate: false - xy: 682, 149 + xy: 672, 149 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 snow-cliff-edge rotate: false - xy: 667, 108 + xy: 667, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 snow-cliff-edge-1 rotate: false - xy: 667, 98 + xy: 667, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 snow-cliff-edge-2 rotate: false - xy: 670, 138 + xy: 667, 98 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 snow-cliff-side rotate: false - xy: 680, 139 + xy: 670, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 snow2 rotate: false - xy: 667, 128 + xy: 682, 149 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 snow3 rotate: false - xy: 667, 118 + xy: 667, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -671,42 +671,42 @@ spaceedge index: -1 stone-cliff-edge rotate: false - xy: 677, 108 + xy: 677, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 stone-cliff-edge-1 rotate: false - xy: 677, 98 + xy: 677, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 stone-cliff-edge-2 rotate: false - xy: 889, 317 + xy: 677, 98 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 stone-cliff-side rotate: false - xy: 516, 63 + xy: 889, 317 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 stone2 rotate: false - xy: 677, 128 + xy: 680, 139 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 stone3 rotate: false - xy: 677, 118 + xy: 677, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -720,91 +720,91 @@ stoneedge index: -1 thorium1 rotate: false - xy: 516, 53 + xy: 736, 241 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 thorium2 rotate: false - xy: 506, 49 + xy: 736, 231 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 thorium3 rotate: false - xy: 496, 48 + xy: 736, 221 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 titanium1 rotate: false - xy: 516, 43 + xy: 736, 211 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 titanium2 rotate: false - xy: 506, 39 + xy: 731, 201 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 titanium3 rotate: false - xy: 419, 38 + xy: 731, 191 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 tungsten1 rotate: false - xy: 423, 18 + xy: 741, 191 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 tungsten2 rotate: false - xy: 423, 8 + xy: 737, 181 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 tungsten3 rotate: false - xy: 516, 33 + xy: 737, 171 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 water-cliff-edge rotate: false - xy: 433, 8 + xy: 751, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 water-cliff-edge-1 rotate: false - xy: 439, 38 + xy: 873, 305 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 water-cliff-edge-2 rotate: false - xy: 443, 28 + xy: 883, 305 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 water-cliff-side rotate: false - xy: 443, 18 + xy: 871, 295 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -818,63 +818,63 @@ wateredge index: -1 block-border rotate: false - xy: 957, 349 + xy: 931, 335 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-elevation rotate: false - xy: 921, 335 + xy: 941, 335 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 combustion-generator-top rotate: false - xy: 921, 335 + xy: 941, 335 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-middle rotate: false - xy: 313, 25 + xy: 313, 5 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 pump-liquid rotate: false - xy: 313, 25 + xy: 313, 5 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-slope rotate: false - xy: 303, 5 + xy: 323, 15 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 border rotate: false - xy: 313, 15 + xy: 333, 25 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 conduit-liquid rotate: false - xy: 383, 15 + xy: 393, 5 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 cross-1 rotate: false - xy: 517, 131 + xy: 512, 62 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -902,7 +902,7 @@ cross-4 index: -1 enemyspawn rotate: false - xy: 967, 347 + xy: 987, 355 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -916,21 +916,21 @@ nuclearreactor-shadow index: -1 place-arrow rotate: false - xy: 647, 128 + xy: 637, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 playerspawn rotate: false - xy: 637, 108 + xy: 647, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ripples rotate: false - xy: 488, 68 + xy: 416, 58 size: 10, 10 orig: 10, 10 offset: 0, 0 @@ -979,7 +979,7 @@ rubble-3-1 index: -1 shadow-1 rotate: false - xy: 428, 56 + xy: 452, 56 size: 10, 10 orig: 10, 10 offset: 0, 0 @@ -1021,7 +1021,7 @@ shadow-6 index: -1 shadow-round-1 rotate: false - xy: 440, 56 + xy: 464, 56 size: 10, 10 orig: 10, 10 offset: 0, 0 @@ -1042,154 +1042,154 @@ shadow-rounded-2 index: -1 bridge-conduit-arrow rotate: false - xy: 323, 25 + xy: 323, 5 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 bridge-conduit-bridge rotate: false - xy: 313, 5 + xy: 333, 15 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 bridge-conduit-end rotate: false - xy: 323, 15 + xy: 343, 25 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 conduit-bottom rotate: false - xy: 363, 25 + xy: 363, 5 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 conduit-bottom-0 rotate: false - xy: 353, 5 + xy: 373, 15 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 conduit-bottom-1 rotate: false - xy: 363, 15 + xy: 383, 25 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 conduit-bottom-2 rotate: false - xy: 373, 25 + xy: 373, 5 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 conduit-bottom-3 rotate: false - xy: 363, 5 + xy: 383, 15 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 conduit-bottom-4 rotate: false - xy: 373, 15 + xy: 393, 25 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 conduit-bottom-5 rotate: false - xy: 383, 25 + xy: 383, 5 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 conduit-bottom-6 rotate: false - xy: 373, 5 + xy: 393, 15 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 conduit-top-0 rotate: false - xy: 393, 25 + xy: 403, 25 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 conduit-top-1 rotate: false - xy: 383, 5 + xy: 403, 15 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 conduit-top-2 rotate: false - xy: 393, 15 + xy: 403, 5 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 conduit-top-3 rotate: false - xy: 393, 5 + xy: 517, 131 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 conduit-top-4 rotate: false - xy: 403, 25 + xy: 517, 121 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 conduit-top-5 rotate: false - xy: 403, 15 + xy: 517, 111 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 conduit-top-6 rotate: false - xy: 403, 5 + xy: 517, 101 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 liquid-router rotate: false - xy: 520, 141 + xy: 522, 151 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 liquid-router-bottom rotate: false - xy: 532, 151 + xy: 520, 141 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 liquid-router-liquid rotate: false - xy: 530, 141 + xy: 532, 151 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 liquid-router-top rotate: false - xy: 527, 131 + xy: 530, 141 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -1217,70 +1217,70 @@ liquid-tank-top index: -1 phase-conduit-arrow rotate: false - xy: 627, 128 + xy: 617, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 phase-conduit-bridge rotate: false - xy: 617, 108 + xy: 627, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 phase-conduit-end rotate: false - xy: 627, 118 + xy: 617, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 pulse-conduit-top-0 rotate: false - xy: 647, 118 + xy: 637, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 pulse-conduit-top-1 rotate: false - xy: 647, 108 + xy: 647, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 pulse-conduit-top-2 rotate: false - xy: 567, 98 + xy: 647, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 pulse-conduit-top-3 rotate: false - xy: 577, 98 + xy: 567, 98 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 pulse-conduit-top-4 rotate: false - xy: 587, 98 + xy: 577, 98 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 pulse-conduit-top-5 rotate: false - xy: 597, 98 + xy: 587, 98 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 pulse-conduit-top-6 rotate: false - xy: 607, 98 + xy: 597, 98 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -1385,7 +1385,7 @@ nuclear-reactor-lights index: -1 rtg-generator-top rotate: false - xy: 662, 148 + xy: 662, 158 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -1560,14 +1560,14 @@ cultivator-top index: -1 lavasmelter rotate: false - xy: 567, 181 + xy: 545, 171 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 oilrefinery rotate: false - xy: 550, 141 + xy: 552, 151 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -1609,21 +1609,21 @@ poweralloysmelter-top index: -1 pulverizer rotate: false - xy: 617, 98 + xy: 607, 98 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 pulverizer-rotator rotate: false - xy: 627, 98 + xy: 617, 98 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 separator-liquid rotate: false - xy: 682, 159 + xy: 672, 159 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -1637,14 +1637,14 @@ core-open index: -1 block-1 rotate: false - xy: 512, 73 + xy: 957, 349 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-1-top rotate: false - xy: 957, 359 + xy: 921, 335 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -1721,7 +1721,7 @@ mass-driver-turret index: -1 duo rotate: false - xy: 967, 357 + xy: 977, 346 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -1826,14 +1826,14 @@ salvo-panel-right index: -1 scorch rotate: false - xy: 500, 69 + xy: 428, 56 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 scorch-shoot rotate: false - xy: 416, 58 + xy: 440, 56 size: 10, 10 orig: 10, 10 offset: 0, 0 @@ -1924,7 +1924,7 @@ reconstructor-open index: -1 repair-point-turret rotate: false - xy: 637, 98 + xy: 627, 98 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -1945,7 +1945,7 @@ door-large-open index: -1 door-open rotate: false - xy: 517, 101 + xy: 967, 346 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -2057,7 +2057,7 @@ missile index: -1 missile-back rotate: false - xy: 452, 57 + xy: 476, 57 size: 9, 9 orig: 9, 9 offset: 0, 0 @@ -2099,21 +2099,21 @@ scorch5 index: -1 shell rotate: false - xy: 463, 57 + xy: 487, 57 size: 9, 9 orig: 9, 9 offset: 0, 0 index: -1 shell-back rotate: false - xy: 474, 57 + xy: 974, 366 size: 9, 9 orig: 9, 9 offset: 0, 0 index: -1 shot rotate: false - xy: 672, 149 + xy: 682, 159 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -2127,7 +2127,7 @@ transfer index: -1 transfer-arrow rotate: false - xy: 423, 28 + xy: 741, 201 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -2162,49 +2162,49 @@ block-icon-blast-drill index: -1 block-icon-bridge-conduit rotate: false - xy: 931, 335 + xy: 951, 335 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 bridge-conduit rotate: false - xy: 931, 335 + xy: 951, 335 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-bridge-conveyor rotate: false - xy: 941, 335 + xy: 728, 291 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 bridge-conveyor rotate: false - xy: 941, 335 + xy: 728, 291 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-carbide-drill rotate: false - xy: 951, 335 + xy: 728, 281 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-carbide-wall rotate: false - xy: 496, 58 + xy: 728, 271 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 carbide-wall rotate: false - xy: 496, 58 + xy: 728, 271 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -2239,35 +2239,35 @@ centrifuge index: -1 block-icon-combustion-generator rotate: false - xy: 506, 59 + xy: 728, 261 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 combustion-generator rotate: false - xy: 506, 59 + xy: 728, 261 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-conduit rotate: false - xy: 728, 291 + xy: 728, 251 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-conveyor rotate: false - xy: 728, 281 + xy: 726, 241 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 conveyor rotate: false - xy: 728, 281 + xy: 726, 241 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -2323,42 +2323,42 @@ dart-ship-factory index: -1 block-icon-deepwater rotate: false - xy: 728, 271 + xy: 726, 231 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 deepwater rotate: false - xy: 728, 271 + xy: 726, 231 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-deflector-wall rotate: false - xy: 728, 261 + xy: 726, 221 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 deflector-wall rotate: false - xy: 728, 261 + xy: 726, 221 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 phase-wall rotate: false - xy: 728, 261 + xy: 726, 221 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-phase-wall rotate: false - xy: 728, 261 + xy: 726, 221 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -2407,14 +2407,14 @@ delta-mech-factory index: -1 block-icon-dirt rotate: false - xy: 728, 251 + xy: 726, 211 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 dirt1 rotate: false - xy: 728, 251 + xy: 726, 211 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -2435,14 +2435,14 @@ distributor index: -1 block-icon-door rotate: false - xy: 726, 241 + xy: 863, 309 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 door rotate: false - xy: 726, 241 + xy: 863, 309 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -2470,14 +2470,14 @@ block-icon-drone-factory index: -1 block-icon-drop-point rotate: false - xy: 726, 231 + xy: 861, 299 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 drop-point rotate: false - xy: 726, 231 + xy: 861, 299 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -2512,14 +2512,14 @@ block-icon-fusion-reactor index: -1 block-icon-grass rotate: false - xy: 726, 221 + xy: 289, 53 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 grass1 rotate: false - xy: 726, 221 + xy: 289, 53 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -2533,70 +2533,70 @@ block-icon-hail index: -1 block-icon-ice rotate: false - xy: 726, 211 + xy: 289, 43 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ice1 rotate: false - xy: 726, 211 + xy: 289, 43 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-icerock rotate: false - xy: 863, 309 + xy: 289, 33 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 icerock1 rotate: false - xy: 863, 309 + xy: 289, 33 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-incinerator rotate: false - xy: 861, 299 + xy: 299, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 incinerator rotate: false - xy: 861, 299 + xy: 299, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-itemsource rotate: false - xy: 289, 53 + xy: 309, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 itemsource rotate: false - xy: 289, 53 + xy: 309, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-itemvoid rotate: false - xy: 289, 43 + xy: 299, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 itemvoid rotate: false - xy: 289, 43 + xy: 299, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -2617,14 +2617,14 @@ javelin-ship-factory index: -1 block-icon-junction rotate: false - xy: 289, 33 + xy: 319, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 junction rotate: false - xy: 289, 33 + xy: 319, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -2645,35 +2645,35 @@ block-icon-laser-drill index: -1 block-icon-lava rotate: false - xy: 299, 55 + xy: 299, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 lava rotate: false - xy: 299, 55 + xy: 299, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-liquid-junction rotate: false - xy: 309, 55 + xy: 309, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 liquid-junction rotate: false - xy: 309, 55 + xy: 309, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-liquid-router rotate: false - xy: 299, 45 + xy: 329, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -2687,14 +2687,14 @@ block-icon-liquid-tank index: -1 block-icon-liquidsource rotate: false - xy: 319, 55 + xy: 309, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 liquidsource rotate: false - xy: 319, 55 + xy: 309, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -2722,14 +2722,14 @@ core-top index: -1 block-icon-mechanical-pump rotate: false - xy: 299, 35 + xy: 319, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 mechanical-pump rotate: false - xy: 299, 35 + xy: 319, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -2743,28 +2743,28 @@ block-icon-meltdown index: -1 block-icon-melter rotate: false - xy: 309, 45 + xy: 339, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 melter rotate: false - xy: 309, 45 + xy: 339, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-metalfloor rotate: false - xy: 329, 55 + xy: 319, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 metalfloor1 rotate: false - xy: 329, 55 + xy: 319, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -2785,14 +2785,14 @@ nuclear-reactor index: -1 block-icon-oil rotate: false - xy: 309, 35 + xy: 329, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 oil rotate: false - xy: 309, 35 + xy: 329, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -2806,42 +2806,42 @@ block-icon-oil-extractor index: -1 block-icon-overflow-gate rotate: false - xy: 319, 45 + xy: 349, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 overflow-gate rotate: false - xy: 319, 45 + xy: 349, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-phase-conduit rotate: false - xy: 339, 55 + xy: 329, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 phase-conduit rotate: false - xy: 339, 55 + xy: 329, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-phase-conveyor rotate: false - xy: 319, 35 + xy: 339, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 phase-conveyor rotate: false - xy: 319, 35 + xy: 339, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -2876,14 +2876,14 @@ plastanium-compressor index: -1 block-icon-power-node rotate: false - xy: 329, 45 + xy: 359, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 power-node rotate: false - xy: 329, 45 + xy: 359, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -2904,42 +2904,42 @@ power-node-large index: -1 block-icon-powerinfinite rotate: false - xy: 349, 55 + xy: 339, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 powerinfinite rotate: false - xy: 349, 55 + xy: 339, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-powervoid rotate: false - xy: 329, 35 + xy: 349, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 powervoid rotate: false - xy: 329, 35 + xy: 349, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-pulse-conduit rotate: false - xy: 339, 45 + xy: 369, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-pulverizer rotate: false - xy: 359, 55 + xy: 349, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -2988,28 +2988,28 @@ reconstructor index: -1 block-icon-repair-point rotate: false - xy: 339, 35 + xy: 359, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 repair-point rotate: false - xy: 339, 35 + xy: 359, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-resupply-point rotate: false - xy: 349, 45 + xy: 379, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 resupply-point rotate: false - xy: 349, 45 + xy: 379, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -3023,14 +3023,14 @@ block-icon-ripple index: -1 block-icon-rock rotate: false - xy: 369, 55 + xy: 359, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 rock1 rotate: false - xy: 369, 55 + xy: 359, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -3051,14 +3051,14 @@ rotary-pump index: -1 block-icon-rtg-generator rotate: false - xy: 349, 35 + xy: 369, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 rtg-generator rotate: false - xy: 349, 35 + xy: 369, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -3072,14 +3072,14 @@ block-icon-salvo index: -1 block-icon-sand rotate: false - xy: 359, 45 + xy: 389, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 sand1 rotate: false - xy: 359, 45 + xy: 389, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -3093,28 +3093,28 @@ block-icon-scorch index: -1 block-icon-separator rotate: false - xy: 379, 55 + xy: 369, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 separator rotate: false - xy: 379, 55 + xy: 369, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-shrub rotate: false - xy: 359, 35 + xy: 379, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 shrub rotate: false - xy: 359, 35 + xy: 379, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -3135,42 +3135,42 @@ silicon-smelter index: -1 block-icon-smelter rotate: false - xy: 369, 45 + xy: 379, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 smelter rotate: false - xy: 369, 45 + xy: 379, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-snow rotate: false - xy: 389, 55 + xy: 389, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 snow1 rotate: false - xy: 389, 55 + xy: 389, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-solar-panel rotate: false - xy: 369, 35 + xy: 389, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 solar-panel rotate: false - xy: 369, 35 + xy: 389, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -3191,56 +3191,56 @@ solar-panel-large index: -1 block-icon-solidifer rotate: false - xy: 379, 45 + xy: 399, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 solidifer rotate: false - xy: 379, 45 + xy: 399, 55 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-sortedunloader rotate: false - xy: 379, 35 + xy: 399, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 sortedunloader rotate: false - xy: 379, 35 + xy: 399, 45 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-sorter rotate: false - xy: 389, 45 + xy: 399, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 sorter rotate: false - xy: 389, 45 + xy: 399, 35 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-space rotate: false - xy: 389, 35 + xy: 293, 23 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 space rotate: false - xy: 389, 35 + xy: 293, 23 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -3254,28 +3254,28 @@ block-icon-spectre index: -1 block-icon-splitter rotate: false - xy: 399, 55 + xy: 293, 13 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 splitter rotate: false - xy: 399, 55 + xy: 293, 13 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-stone rotate: false - xy: 399, 45 + xy: 293, 3 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 stone1 rotate: false - xy: 399, 45 + xy: 293, 3 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -3317,14 +3317,14 @@ thermal-pump index: -1 block-icon-thorium-wall rotate: false - xy: 399, 35 + xy: 303, 25 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 thorium-wall rotate: false - xy: 399, 35 + xy: 303, 25 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -3345,35 +3345,35 @@ thorium-wall-large index: -1 block-icon-titanium-conveyor rotate: false - xy: 293, 23 + xy: 303, 15 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 titanium-conveyor rotate: false - xy: 293, 23 + xy: 303, 15 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-tungsten-drill rotate: false - xy: 293, 13 + xy: 313, 25 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 block-icon-tungsten-wall rotate: false - xy: 293, 3 + xy: 303, 5 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 tungsten-wall rotate: false - xy: 293, 3 + xy: 303, 5 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -3408,14 +3408,14 @@ turbine-generator index: -1 block-icon-unloader rotate: false - xy: 303, 25 + xy: 313, 15 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 unloader rotate: false - xy: 303, 25 + xy: 313, 15 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -3450,14 +3450,14 @@ warp-gate index: -1 block-icon-water rotate: false - xy: 303, 15 + xy: 323, 25 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 water rotate: false - xy: 303, 15 + xy: 323, 25 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -3478,28 +3478,28 @@ block-icon-wave index: -1 liquid-icon-cryofluid rotate: false - xy: 542, 161 + xy: 532, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 liquid-icon-lava rotate: false - xy: 552, 161 + xy: 542, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 liquid-icon-oil rotate: false - xy: 562, 161 + xy: 552, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 liquid-icon-water rotate: false - xy: 522, 151 + xy: 562, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -3555,525 +3555,525 @@ mech-icon-tau-mech index: -1 ore-coal-grass1 rotate: false - xy: 527, 111 + xy: 550, 141 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-grass2 rotate: false - xy: 537, 121 + xy: 527, 111 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-grass3 rotate: false - xy: 547, 131 + xy: 537, 121 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-ice1 rotate: false - xy: 562, 151 + xy: 547, 131 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-ice2 rotate: false - xy: 560, 141 + xy: 562, 151 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-ice3 rotate: false - xy: 527, 101 + xy: 560, 141 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-sand1 rotate: false - xy: 537, 111 + xy: 527, 101 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-sand2 rotate: false - xy: 547, 121 + xy: 537, 111 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-sand3 rotate: false - xy: 557, 131 + xy: 547, 121 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-snow1 rotate: false - xy: 537, 101 + xy: 557, 131 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-snow2 rotate: false - xy: 547, 111 + xy: 537, 101 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-snow3 rotate: false - xy: 557, 121 + xy: 547, 111 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-stone1 rotate: false - xy: 547, 101 + xy: 557, 121 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-stone2 rotate: false - xy: 557, 111 + xy: 547, 101 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-coal-stone3 rotate: false - xy: 557, 101 + xy: 557, 111 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-grass1 rotate: false - xy: 577, 178 + xy: 557, 101 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-grass2 rotate: false - xy: 587, 178 + xy: 577, 178 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-grass3 rotate: false - xy: 597, 178 + xy: 587, 178 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-ice1 rotate: false - xy: 607, 178 + xy: 597, 178 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-ice2 rotate: false - xy: 617, 178 + xy: 607, 178 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-ice3 rotate: false - xy: 627, 178 + xy: 617, 178 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-sand1 rotate: false - xy: 637, 178 + xy: 627, 178 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-sand2 rotate: false - xy: 575, 168 + xy: 637, 178 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-sand3 rotate: false - xy: 585, 168 + xy: 575, 168 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-snow1 rotate: false - xy: 595, 168 + xy: 585, 168 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-snow2 rotate: false - xy: 605, 168 + xy: 595, 168 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-snow3 rotate: false - xy: 615, 168 + xy: 605, 168 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-stone1 rotate: false - xy: 625, 168 + xy: 615, 168 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-stone2 rotate: false - xy: 635, 168 + xy: 625, 168 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-lead-stone3 rotate: false - xy: 572, 158 + xy: 635, 168 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-grass1 rotate: false - xy: 582, 158 + xy: 572, 158 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-grass2 rotate: false - xy: 592, 158 + xy: 582, 158 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-grass3 rotate: false - xy: 602, 158 + xy: 592, 158 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-ice1 rotate: false - xy: 612, 158 + xy: 602, 158 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-ice2 rotate: false - xy: 622, 158 + xy: 612, 158 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-ice3 rotate: false - xy: 632, 158 + xy: 622, 158 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-sand1 rotate: false - xy: 572, 148 + xy: 632, 158 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-sand2 rotate: false - xy: 582, 148 + xy: 572, 148 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-sand3 rotate: false - xy: 592, 148 + xy: 582, 148 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-snow1 rotate: false - xy: 602, 148 + xy: 592, 148 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-snow2 rotate: false - xy: 612, 148 + xy: 602, 148 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-snow3 rotate: false - xy: 622, 148 + xy: 612, 148 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-stone1 rotate: false - xy: 632, 148 + xy: 622, 148 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-stone2 rotate: false - xy: 645, 168 + xy: 632, 148 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-thorium-stone3 rotate: false - xy: 655, 168 + xy: 645, 168 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-grass1 rotate: false - xy: 642, 158 + xy: 655, 168 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-grass2 rotate: false - xy: 642, 148 + xy: 642, 158 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-grass3 rotate: false - xy: 652, 158 + xy: 642, 148 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-ice1 rotate: false - xy: 652, 148 + xy: 652, 158 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-ice2 rotate: false - xy: 570, 138 + xy: 652, 148 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-ice3 rotate: false - xy: 580, 138 + xy: 570, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-sand1 rotate: false - xy: 590, 138 + xy: 580, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-sand2 rotate: false - xy: 600, 138 + xy: 590, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-sand3 rotate: false - xy: 610, 138 + xy: 600, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-snow1 rotate: false - xy: 620, 138 + xy: 610, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-snow2 rotate: false - xy: 630, 138 + xy: 620, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-snow3 rotate: false - xy: 640, 138 + xy: 630, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-stone1 rotate: false - xy: 650, 138 + xy: 640, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-stone2 rotate: false - xy: 567, 128 + xy: 650, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-titanium-stone3 rotate: false - xy: 567, 118 + xy: 567, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-grass1 rotate: false - xy: 577, 128 + xy: 567, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-grass2 rotate: false - xy: 567, 108 + xy: 577, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-grass3 rotate: false - xy: 577, 118 + xy: 567, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-ice1 rotate: false - xy: 587, 128 + xy: 577, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-ice2 rotate: false - xy: 577, 108 + xy: 587, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-ice3 rotate: false - xy: 587, 118 + xy: 577, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-sand1 rotate: false - xy: 597, 128 + xy: 587, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-sand2 rotate: false - xy: 587, 108 + xy: 597, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-sand3 rotate: false - xy: 597, 118 + xy: 587, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-snow1 rotate: false - xy: 607, 128 + xy: 597, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-snow2 rotate: false - xy: 597, 108 + xy: 607, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-snow3 rotate: false - xy: 607, 118 + xy: 597, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-stone1 rotate: false - xy: 617, 128 + xy: 607, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-stone2 rotate: false - xy: 607, 108 + xy: 617, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 ore-tungsten-stone3 rotate: false - xy: 617, 118 + xy: 607, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -4108,112 +4108,112 @@ vtol index: -1 item-biomatter rotate: false - xy: 731, 161 + xy: 721, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-blast-compound rotate: false - xy: 775, 302 + xy: 731, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-carbide rotate: false - xy: 785, 302 + xy: 775, 302 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-coal rotate: false - xy: 795, 302 + xy: 785, 302 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-lead rotate: false - xy: 507, 181 + xy: 795, 302 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-phase-matter rotate: false - xy: 505, 171 + xy: 507, 181 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-plastanium rotate: false - xy: 517, 181 + xy: 505, 171 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-pyratite rotate: false - xy: 527, 181 + xy: 517, 181 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-sand rotate: false - xy: 515, 171 + xy: 527, 181 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-silicon rotate: false - xy: 537, 181 + xy: 515, 171 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-stone rotate: false - xy: 525, 171 + xy: 537, 181 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-surge-alloy rotate: false - xy: 547, 181 + xy: 525, 171 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-thorium rotate: false - xy: 535, 171 + xy: 547, 181 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-titanium rotate: false - xy: 557, 181 + xy: 535, 171 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 item-tungsten rotate: false - xy: 545, 171 + xy: 557, 181 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 liquid-icon rotate: false - xy: 532, 161 + xy: 522, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -4864,9 +4864,16 @@ icon-itch.io orig: 14, 14 offset: 0, 0 index: -1 +icon-item + rotate: false + xy: 404, 66 + size: 10, 10 + orig: 10, 10 + offset: 0, 0 + index: -1 icon-items-none rotate: false - xy: 721, 161 + xy: 711, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 @@ -4887,7 +4894,7 @@ icon-link index: -1 icon-liquid rotate: false - xy: 404, 66 + xy: 416, 70 size: 10, 10 orig: 10, 10 offset: 0, 0 @@ -4929,7 +4936,7 @@ icon-locked index: -1 icon-logic rotate: false - xy: 416, 70 + xy: 428, 80 size: 10, 10 orig: 10, 10 offset: 0, 0 @@ -4943,7 +4950,7 @@ icon-map index: -1 icon-menu rotate: false - xy: 428, 80 + xy: 440, 80 size: 10, 10 orig: 10, 10 offset: 0, 0 @@ -4955,16 +4962,23 @@ icon-menu-large orig: 16, 16 offset: 0, 0 index: -1 +icon-missing + rotate: false + xy: 452, 80 + size: 10, 10 + orig: 10, 10 + offset: 0, 0 + index: -1 icon-none rotate: false - xy: 440, 80 + xy: 464, 80 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-pause rotate: false - xy: 452, 80 + xy: 476, 80 size: 10, 10 orig: 10, 10 offset: 0, 0 @@ -4992,7 +5006,7 @@ icon-pick index: -1 icon-play rotate: false - xy: 464, 80 + xy: 488, 80 size: 10, 10 orig: 10, 10 offset: 0, 0 @@ -5006,21 +5020,21 @@ icon-play-2 index: -1 icon-players rotate: false - xy: 476, 80 + xy: 500, 81 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-power rotate: false - xy: 488, 80 + xy: 428, 68 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-production rotate: false - xy: 500, 81 + xy: 440, 68 size: 10, 10 orig: 10, 10 offset: 0, 0 @@ -5111,7 +5125,7 @@ icon-save-map index: -1 icon-settings rotate: false - xy: 428, 68 + xy: 452, 68 size: 10, 10 orig: 10, 10 offset: 0, 0 @@ -5132,14 +5146,14 @@ icon-tools index: -1 icon-touch rotate: false - xy: 440, 68 + xy: 464, 68 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-touchDelete rotate: false - xy: 452, 68 + xy: 476, 68 size: 10, 10 orig: 10, 10 offset: 0, 0 @@ -5181,7 +5195,7 @@ icon-undo index: -1 icon-units rotate: false - xy: 464, 68 + xy: 488, 68 size: 10, 10 orig: 10, 10 offset: 0, 0 @@ -5195,7 +5209,7 @@ icon-unlocks index: -1 icon-weapon rotate: false - xy: 476, 68 + xy: 500, 69 size: 10, 10 orig: 10, 10 offset: 0, 0 @@ -5474,7 +5488,7 @@ titan-leg index: -1 vtol-flame rotate: false - xy: 485, 57 + xy: 512, 82 size: 9, 9 orig: 9, 9 offset: 0, 0 diff --git a/core/assets/sprites/sprites.png b/core/assets/sprites/sprites.png index 9ae2122cd9b1cc99a5bf0de7e7a93dbe559e86b4..367ec17fe64122910879689ba1ece718203d383b 100644 GIT binary patch delta 135319 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 z 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 diff --git a/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java b/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java index 6fe9a4db48..945a15ef36 100644 --- a/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java @@ -128,6 +128,7 @@ public class CraftingBlocks extends BlockList implements ContentList { itemCapacity = 20; hasItems = true; hasPower = true; + hasLiquids = true; output = Items.blastCompound; size = 2; diff --git a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java index 777d844540..25c3191b95 100644 --- a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java +++ b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java @@ -3,7 +3,6 @@ package io.anuke.mindustry.editor; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.Color; -import com.badlogic.gdx.graphics.Pixmap; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.Align; @@ -26,7 +25,6 @@ import io.anuke.ucore.core.Timers; import io.anuke.ucore.function.Consumer; import io.anuke.ucore.function.Listenable; import io.anuke.ucore.graphics.Draw; -import io.anuke.ucore.graphics.Pixmaps; import io.anuke.ucore.input.Input; import io.anuke.ucore.scene.actions.Actions; import io.anuke.ucore.scene.builders.build; @@ -115,10 +113,10 @@ public class MapEditorDialog extends Dialog implements Disposable{ } }); }, true, mapExtension); - }, + }/*, "$text.editor.importimage", "$text.editor.importimage.description", "icon-file-image", (Listenable)() -> { if(gwt){ - ui.showError("text.web.unsupported"); + ui.showError("$text.web.unsupported"); }else { Platform.instance.showFileChooser("$text.loadimage", "Image Files", file -> { ui.loadAnd(() -> { @@ -134,7 +132,7 @@ public class MapEditorDialog extends Dialog implements Disposable{ }); }, true, "png"); } - })); + }*/)); t.addImageTextButton("$text.editor.export", "icon-save-map", isize, () -> createDialog("$text.editor.export", "$text.editor.exportfile", "$text.editor.exportfile.description", "icon-file", (Listenable)() -> { @@ -165,11 +163,11 @@ public class MapEditorDialog extends Dialog implements Disposable{ Log.err(e); } } - }, + }/*, "$text.editor.exportimage", "$text.editor.exportimage.description", "icon-file-image", (Listenable)() -> { if(gwt){ - ui.showError("text.web.unsupported"); - }else { + ui.showError("$text.web.unsupported"); + }else{ Platform.instance.showFileChooser("$text.saveimage", "Image Files", file -> { file = file.parent().child(file.nameWithoutExtension() + ".png"); FileHandle result = file; @@ -183,7 +181,7 @@ public class MapEditorDialog extends Dialog implements Disposable{ }); }, false, "png"); } - })); + }*/)); t.row(); diff --git a/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java b/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java index dcb7eb167f..dd23a56dd5 100644 --- a/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java +++ b/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java @@ -130,18 +130,6 @@ public class OverlayRenderer { drawEncloser(target.drawx(), target.drawy() - block.size * tilesize/2f - 2f - values[1], values[1]); } - Draw.color(Palette.bar); - - int idx = 0; - for(Consume cons : block.consumes.all()){ - if(!cons.isOptional() && !cons.valid(block, entity)){ - Fill.crect(entity.x - 4 + idx*4, entity.y + block.size*tilesize/2f + values[0] + 4, 3, 3); - idx ++; - } - } - - Draw.color(); - doDraw[0] = true; values[0] = 0; values[1] = 1; diff --git a/core/src/io/anuke/mindustry/io/Version.java b/core/src/io/anuke/mindustry/io/Version.java index 473d266b0d..ea5cd6d542 100644 --- a/core/src/io/anuke/mindustry/io/Version.java +++ b/core/src/io/anuke/mindustry/io/Version.java @@ -12,7 +12,7 @@ public class Version { public static String name; public static String type; public static String code; - public static int build; + public static int build = 0; public static String buildName; public static void init(){ diff --git a/core/src/io/anuke/mindustry/ui/ItemImage.java b/core/src/io/anuke/mindustry/ui/ItemImage.java index e5260938b4..0786016780 100644 --- a/core/src/io/anuke/mindustry/ui/ItemImage.java +++ b/core/src/io/anuke/mindustry/ui/ItemImage.java @@ -18,9 +18,7 @@ public class ItemImage extends Stack { t.row(); t.label(text).get().setFontScale(Unit.dp.scl(0.5f)); - Image image = new Image(region); - - add(image); + add(new Image(region)); add(t); } @@ -31,9 +29,7 @@ public class ItemImage extends Stack { t.row(); t.add(stack.amount + "").get().setFontScale(Unit.dp.scl(0.5f)); - Image image = new Image(stack.item.region); - - add(image); + add(new Image(stack.item.region)); add(t); } } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/ContentInfoDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/ContentInfoDialog.java index da26c251f9..2e1a0973d9 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/ContentInfoDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/ContentInfoDialog.java @@ -7,7 +7,7 @@ import io.anuke.ucore.scene.ui.layout.Table; public class ContentInfoDialog extends FloatingDialog { public ContentInfoDialog(){ - super("$text.info"); + super("$text.info.title"); addCloseButton(); } diff --git a/core/src/io/anuke/mindustry/ui/fragments/BlockConsumeFragment.java b/core/src/io/anuke/mindustry/ui/fragments/BlockConsumeFragment.java index 93f260d0a2..36560f5366 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/BlockConsumeFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/BlockConsumeFragment.java @@ -1,11 +1,13 @@ package io.anuke.mindustry.ui.fragments; import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.Align; import com.badlogic.gdx.utils.ObjectSet; import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.entities.TileEntity; +import io.anuke.mindustry.graphics.Palette; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.consumers.Consume; @@ -13,8 +15,7 @@ import io.anuke.ucore.core.Graphics; import io.anuke.ucore.scene.Group; import io.anuke.ucore.scene.ui.layout.Table; -import static io.anuke.mindustry.Vars.state; -import static io.anuke.mindustry.Vars.tilesize; +import static io.anuke.mindustry.Vars.*; public class BlockConsumeFragment extends Fragment { private Table table; @@ -33,6 +34,9 @@ public class BlockConsumeFragment extends Fragment { ObjectSet consumers = new ObjectSet<>(); TileEntity entity = tile.entity; Block block = tile.block(); + Consume[] lastCurrent = {null}; + + table.clearChildren(); //table.background("clear"); rebuild(block, entity); @@ -64,7 +68,7 @@ public class BlockConsumeFragment extends Fragment { rebuild(block, entity); } - Vector2 v = Graphics.screen(tile.drawx() - tile.block().size * tilesize/2f, tile.drawy() + tile.block().size * tilesize/2f); + Vector2 v = Graphics.screen(tile.drawx() - tile.block().size * tilesize/2f, tile.drawy() + tile.block().size * tilesize/2f); table.pack(); table.setPosition(v.x, v.y, Align.topRight); }); @@ -80,10 +84,27 @@ public class BlockConsumeFragment extends Fragment { private void rebuild(Block block, TileEntity entity){ table.clearChildren(); + table.left(); + + int scale = mobile ? 4 : 3; for(Consume c : block.consumes.array()){ if(!c.isOptional() && !c.valid(block, entity)){ - c.build(table); + boolean[] hovered = {false}; + + table.table("inventory", c::buildTooltip).visible(() -> hovered[0]).height(scale * 10 + 6).padBottom(-4).right().update(t -> { + if(t.getChildren().size == 0) t.remove(); + }); + + Table result = table.table(out -> { + out.addImage(c.getIcon()).size(10*scale).color(Color.DARK_GRAY).padRight(-10*scale).padBottom(-scale*2); + out.addImage(c.getIcon()).size(10*scale).color(Palette.accent); + out.addImage("icon-missing").size(10*scale).color(Palette.remove).padLeft(-10*scale); + }).size(10*scale).get(); + + result.hovered(() -> hovered[0] = true); + result.exited(() -> hovered[0] = false); + table.row(); } } diff --git a/core/src/io/anuke/mindustry/world/consumers/Consume.java b/core/src/io/anuke/mindustry/world/consumers/Consume.java index 352f0c8cfb..c26cd66e27 100644 --- a/core/src/io/anuke/mindustry/world/consumers/Consume.java +++ b/core/src/io/anuke/mindustry/world/consumers/Consume.java @@ -1,13 +1,15 @@ package io.anuke.mindustry.world.consumers; import com.badlogic.gdx.graphics.Color; -import com.badlogic.gdx.utils.reflect.ClassReflection; import io.anuke.mindustry.entities.TileEntity; +import io.anuke.mindustry.graphics.Palette; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.meta.BlockStats; import io.anuke.ucore.scene.ui.Tooltip; import io.anuke.ucore.scene.ui.layout.Table; +import static io.anuke.mindustry.Vars.mobile; + public abstract class Consume { private boolean optional; private boolean update = true; @@ -35,19 +37,18 @@ public abstract class Consume { t.margin(4); buildTooltip(t); - table.table("clear", out -> { - out.addImage(getIcon()).size(10*4).color(Color.RED); - }).size(10*4).get().addListener(new Tooltip<>(t)); + int scale = mobile ? 4 : 3; + + table.table(out -> { + out.addImage(getIcon()).size(10*scale).color(Color.DARK_GRAY).padRight(-10*scale).padBottom(-scale*2); + out.addImage(getIcon()).size(10*scale).color(Palette.accent); + out.addImage("icon-missing").size(10*scale).color(Palette.remove).padLeft(-10*scale); + }).size(10*scale).get().addListener(new Tooltip<>(t)); } - public void buildTooltip(Table table){ - table.add("no " + ClassReflection.getSimpleName(getClass()).replace("Consume", "")); - } - - public String getIcon(){ - return "icon-power"; - } + public abstract void buildTooltip(Table table); + public abstract String getIcon(); public abstract void update(Block block, TileEntity entity); public abstract boolean valid(Block block, TileEntity entity); public abstract void display(BlockStats stats); diff --git a/core/src/io/anuke/mindustry/world/consumers/ConsumeItem.java b/core/src/io/anuke/mindustry/world/consumers/ConsumeItem.java index b618c7b7ab..91041bec16 100644 --- a/core/src/io/anuke/mindustry/world/consumers/ConsumeItem.java +++ b/core/src/io/anuke/mindustry/world/consumers/ConsumeItem.java @@ -2,9 +2,12 @@ package io.anuke.mindustry.world.consumers; import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.type.Item; +import io.anuke.mindustry.type.ItemStack; +import io.anuke.mindustry.ui.ItemImage; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.meta.BlockStat; import io.anuke.mindustry.world.meta.BlockStats; +import io.anuke.ucore.scene.ui.layout.Table; public class ConsumeItem extends Consume { private final Item item; @@ -28,6 +31,16 @@ public class ConsumeItem extends Consume { return item; } + @Override + public void buildTooltip(Table table) { + table.add(new ItemImage(new ItemStack(item, amount))).size(8*4); + } + + @Override + public String getIcon() { + return "icon-item"; + } + @Override public void update(Block block, TileEntity entity) { //doesn't update because consuming items is very specific diff --git a/core/src/io/anuke/mindustry/world/consumers/ConsumeItemFilter.java b/core/src/io/anuke/mindustry/world/consumers/ConsumeItemFilter.java index 44e9cc2b08..e226ad49b9 100644 --- a/core/src/io/anuke/mindustry/world/consumers/ConsumeItemFilter.java +++ b/core/src/io/anuke/mindustry/world/consumers/ConsumeItemFilter.java @@ -1,5 +1,6 @@ package io.anuke.mindustry.world.consumers; +import com.badlogic.gdx.utils.Array; import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.type.Item; import io.anuke.mindustry.world.Block; @@ -7,12 +8,35 @@ import io.anuke.mindustry.world.meta.BlockStat; import io.anuke.mindustry.world.meta.BlockStats; import io.anuke.mindustry.world.meta.values.ItemFilterValue; import io.anuke.ucore.function.Predicate; +import io.anuke.ucore.scene.ui.layout.Table; public class ConsumeItemFilter extends Consume{ - private final Predicate item; + private final Predicate filter; public ConsumeItemFilter(Predicate item) { - this.item = item; + this.filter = item; + } + + @Override + public void buildTooltip(Table table) { + Array list = new Array<>(); + + for(Item item : Item.all()){ + if(filter.test(item)) list.add(item); + } + + for (int i = 0; i < list.size; i++) { + Item item = list.get(i); + table.addImage(item.region).size(8*4).padRight(2).padLeft(2); + if(i != list.size - 1){ + table.add("/"); + } + } + } + + @Override + public String getIcon() { + return "icon-item"; } @Override @@ -24,7 +48,7 @@ public class ConsumeItemFilter extends Consume{ public boolean valid(Block block, TileEntity entity) { for(int i = 0; i < Item.all().size; i ++){ Item item = Item.getByID(i); - if(entity.items.has(item) && this.item.test(item)){ + if(entity.items.has(item) && this.filter.test(item)){ return true; } } @@ -33,6 +57,6 @@ public class ConsumeItemFilter extends Consume{ @Override public void display(BlockStats stats) { - stats.add(BlockStat.inputItems, new ItemFilterValue(item)); + stats.add(BlockStat.inputItems, new ItemFilterValue(filter)); } } diff --git a/core/src/io/anuke/mindustry/world/consumers/ConsumeItems.java b/core/src/io/anuke/mindustry/world/consumers/ConsumeItems.java index 8680eb6403..c6336105f3 100644 --- a/core/src/io/anuke/mindustry/world/consumers/ConsumeItems.java +++ b/core/src/io/anuke/mindustry/world/consumers/ConsumeItems.java @@ -2,10 +2,12 @@ package io.anuke.mindustry.world.consumers; import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.type.ItemStack; +import io.anuke.mindustry.ui.ItemImage; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.meta.BlockStat; import io.anuke.mindustry.world.meta.BlockStats; import io.anuke.mindustry.world.meta.values.ItemListValue; +import io.anuke.ucore.scene.ui.layout.Table; public class ConsumeItems extends Consume { private ItemStack[] items; @@ -18,6 +20,18 @@ public class ConsumeItems extends Consume { return items; } + @Override + public void buildTooltip(Table table) { + for(ItemStack stack : items){ + table.add(new ItemImage(stack)).size(8*4).padRight(5); + } + } + + @Override + public String getIcon() { + return "icon-item"; + } + @Override public void update(Block block, TileEntity entity) { diff --git a/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquid.java b/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquid.java index eb8ac197e9..084e7cbe60 100644 --- a/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquid.java +++ b/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquid.java @@ -7,6 +7,7 @@ import io.anuke.mindustry.world.meta.BlockStat; import io.anuke.mindustry.world.meta.BlockStats; import io.anuke.mindustry.world.meta.StatUnit; import io.anuke.ucore.core.Timers; +import io.anuke.ucore.scene.ui.layout.Table; public class ConsumeLiquid extends Consume { protected final float use; @@ -25,6 +26,16 @@ public class ConsumeLiquid extends Consume { return liquid; } + @Override + public void buildTooltip(Table table) { + table.addImage(liquid.getContentIcon()).size(8*3); + } + + @Override + public String getIcon() { + return "icon-liquid"; + } + @Override public void update(Block block, TileEntity entity) { entity.liquids.remove(liquid, Math.min(use(block), entity.liquids.get(liquid))); diff --git a/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquidFilter.java b/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquidFilter.java index 65c1ccb557..8ad414f22a 100644 --- a/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquidFilter.java +++ b/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquidFilter.java @@ -1,5 +1,6 @@ package io.anuke.mindustry.world.consumers; +import com.badlogic.gdx.utils.Array; import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.type.Liquid; import io.anuke.mindustry.world.Block; @@ -9,14 +10,16 @@ import io.anuke.mindustry.world.meta.StatUnit; import io.anuke.mindustry.world.meta.values.LiquidFilterValue; import io.anuke.ucore.core.Timers; import io.anuke.ucore.function.Predicate; +import io.anuke.ucore.scene.ui.layout.Table; +import io.anuke.ucore.util.Strings; public class ConsumeLiquidFilter extends Consume{ - private final Predicate liquid; + private final Predicate filter; private final float use; private final boolean isFuel; public ConsumeLiquidFilter(Predicate liquid, float amount, boolean isFuel) { - this.liquid = liquid; + this.filter = liquid; this.use = amount; this.isFuel = isFuel; } @@ -25,23 +28,47 @@ public class ConsumeLiquidFilter extends Consume{ this(liquid, amount, false); } - @Override + @Override + public void buildTooltip(Table table) { + Array list = new Array<>(); + + for(Liquid item : Liquid.all()){ + if(!item.isHidden() && filter.test(item)) list.add(item); + } + + for (int i = 0; i < list.size; i++) { + Liquid item = list.get(i); + table.addImage(item.getContentIcon()).size(8*3).padRight(2).padLeft(2).padTop(2).padBottom(2); + if(i != list.size - 1){ + table.add("/"); + } + } + + table.add("x" + Strings.toFixed(use * 60f, 1)); + } + + @Override + public String getIcon() { + return "icon-liquid"; + } + + @Override public void update(Block block, TileEntity entity) { entity.liquids.remove(entity.liquids.current(), use(block)); } @Override public boolean valid(Block block, TileEntity entity) { - return liquid.test(entity.liquids.current()) && entity.liquids.currentAmount() >= use(block); + return filter.test(entity.liquids.current()) && entity.liquids.currentAmount() >= use(block); } @Override public void display(BlockStats stats) { if(isFuel){ - stats.add(BlockStat.inputLiquidFuel, new LiquidFilterValue(liquid)); + stats.add(BlockStat.inputLiquidFuel, new LiquidFilterValue(filter)); stats.add(BlockStat.liquidFuelUse, 60f * use, StatUnit.liquidSecond); }else { - stats.add(BlockStat.inputLiquid, new LiquidFilterValue(liquid)); + stats.add(BlockStat.inputLiquid, new LiquidFilterValue(filter)); stats.add(BlockStat.liquidUse, 60f * use, StatUnit.liquidSecond); } } diff --git a/core/src/io/anuke/mindustry/world/consumers/ConsumePower.java b/core/src/io/anuke/mindustry/world/consumers/ConsumePower.java index 84f8636b90..482c53916f 100644 --- a/core/src/io/anuke/mindustry/world/consumers/ConsumePower.java +++ b/core/src/io/anuke/mindustry/world/consumers/ConsumePower.java @@ -6,6 +6,7 @@ import io.anuke.mindustry.world.meta.BlockStat; import io.anuke.mindustry.world.meta.BlockStats; import io.anuke.mindustry.world.meta.StatUnit; import io.anuke.ucore.core.Timers; +import io.anuke.ucore.scene.ui.layout.Table; public class ConsumePower extends Consume { private final float use; @@ -14,6 +15,16 @@ public class ConsumePower extends Consume { this.use = use; } + @Override + public void buildTooltip(Table table) { + + } + + @Override + public String getIcon() { + return "icon-power"; + } + @Override public void update(Block block, TileEntity entity) { entity.power.amount -= Math.min(use(block), entity.power.amount); diff --git a/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java b/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java index 1680ba4434..64fcf2da36 100644 --- a/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java +++ b/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java @@ -38,10 +38,10 @@ public class CrashHandler { header += "Build: " + Version.build + "\n"; header += "Net Active: " + netActive + "\n"; header += "Net Server: " + netServer + "\n"; - header += "OS: " + System.getProperty("os.name")+ "\n----\n"; - header += "Multithreading: " + Settings.getBool("multithread")+ "\n"; + header += "OS: " + System.getProperty("os.name") + "\n"; + header += "Multithreading: " + Settings.getBool("multithread")+ "\n----\n"; }catch (Throwable e4){ - header += "[Error getting additional game info.]\n"; + header += "\n--error getting additional info--\n"; e4.printStackTrace(); } diff --git a/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java b/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java index cfd729a066..aef5a71b04 100644 --- a/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java +++ b/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java @@ -32,56 +32,54 @@ public class DesktopLauncher extends Lwjgl3Application{ ObjectMap prefmap; public static void main (String[] arg) { - - Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration(); - config.setTitle("Mindustry"); - config.setMaximized(true); - config.setWindowedMode(960, 540); - config.setWindowIcon("sprites/icon.png"); + try { + Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration(); + config.setTitle("Mindustry"); + config.setMaximized(true); + config.setWindowedMode(960, 540); + config.setWindowIcon("sprites/icon.png"); - if(OS.isMac) { - Application.getApplication().setOpenFileHandler(e -> { - List list = e.getFiles(); + if(OS.isMac) { + Application.getApplication().setOpenFileHandler(e -> { + List list = e.getFiles(); - File target = (File)list.get(0); + File target = (File)list.get(0); - Gdx.app.postRunnable(() -> { - FileHandle file = OS.getAppDataDirectory("Mindustry").child("tmp").child(target.getName()); + Gdx.app.postRunnable(() -> { + FileHandle file = OS.getAppDataDirectory("Mindustry").child("tmp").child(target.getName()); - Gdx.files.absolute(target.getAbsolutePath()).copyTo(file); + Gdx.files.absolute(target.getAbsolutePath()).copyTo(file); - if(file.extension().equalsIgnoreCase(saveExtension)){ //open save + if(file.extension().equalsIgnoreCase(saveExtension)){ //open save - if(SaveIO.isSaveValid(file)){ - try{ - SaveSlot slot = control.getSaves().importSave(file); - ui.load.runLoadSave(slot); - }catch (IOException e2){ - ui.showError(Bundles.format("text.save.import.fail", Strings.parseException(e2, false))); + if(SaveIO.isSaveValid(file)){ + try{ + SaveSlot slot = control.getSaves().importSave(file); + ui.load.runLoadSave(slot); + }catch (IOException e2){ + ui.showError(Bundles.format("text.save.import.fail", Strings.parseException(e2, false))); + } + }else{ + ui.showError("$text.save.import.invalid"); } - }else{ - ui.showError("$text.save.import.invalid"); + + }else if(file.extension().equalsIgnoreCase(mapExtension)){ //open map + Gdx.app.postRunnable(() -> { + if (!ui.editor.isShown()) { + ui.editor.show(); + } + + ui.editor.beginEditMap(file.read()); + }); } - - }else if(file.extension().equalsIgnoreCase(mapExtension)){ //open map - Gdx.app.postRunnable(() -> { - if (!ui.editor.isShown()) { - ui.editor.show(); - } - - ui.editor.beginEditMap(file.read()); - }); - } + }); }); - }); - } + } - Platform.instance = new DesktopPlatform(arg); + Platform.instance = new DesktopPlatform(arg); - Net.setClientProvider(new KryoClient()); - Net.setServerProvider(new KryoServer()); - - try { + Net.setClientProvider(new KryoClient()); + Net.setServerProvider(new KryoServer()); new DesktopLauncher(new Mindustry(), config); }catch (Throwable e){ CrashHandler.handle(e); From bba418c79d190cb274e6c0fe50ef00c501b188b1 Mon Sep 17 00:00:00 2001 From: Anuken Date: Tue, 10 Jul 2018 18:57:50 -0400 Subject: [PATCH 22/47] Added consumers for repair/resupply points --- .../ui/fragments/BlockConsumeFragment.java | 2 +- .../world/blocks/production/GenericCrafter.java | 9 +++++++++ .../mindustry/world/blocks/units/RepairPoint.java | 14 +++++++++----- .../world/blocks/units/ResupplyPoint.java | 8 +++----- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/core/src/io/anuke/mindustry/ui/fragments/BlockConsumeFragment.java b/core/src/io/anuke/mindustry/ui/fragments/BlockConsumeFragment.java index 36560f5366..a2726f483d 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/BlockConsumeFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/BlockConsumeFragment.java @@ -94,7 +94,7 @@ public class BlockConsumeFragment extends Fragment { table.table("inventory", c::buildTooltip).visible(() -> hovered[0]).height(scale * 10 + 6).padBottom(-4).right().update(t -> { if(t.getChildren().size == 0) t.remove(); - }); + }).get().act(0); Table result = table.table(out -> { out.addImage(c.getIcon()).size(10*scale).color(Color.DARK_GRAY).padRight(-10*scale).padBottom(-scale*2); diff --git a/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java b/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java index 8f7f773915..29393b4c8c 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java @@ -22,6 +22,9 @@ import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; +import static io.anuke.mindustry.Vars.control; +import static io.anuke.mindustry.Vars.headless; + public class GenericCrafter extends Block{ protected final int timerDump = timers++; @@ -96,6 +99,12 @@ public class GenericCrafter extends Block{ if(entity.progress >= 1f){ if(consumes.has(ConsumeItem.class)) tile.entity.items.remove(consumes.item(), consumes.itemAmount()); + + //unlock output item + if(!headless){ + control.database().unlockContent(output); + } + offloadNear(tile, output); Effects.effect(craftEffect, tile.drawx(), tile.drawy()); entity.progress = 0f; diff --git a/core/src/io/anuke/mindustry/world/blocks/units/RepairPoint.java b/core/src/io/anuke/mindustry/world/blocks/units/RepairPoint.java index 2d314ddd0a..d592f7817e 100644 --- a/core/src/io/anuke/mindustry/world/blocks/units/RepairPoint.java +++ b/core/src/io/anuke/mindustry/world/blocks/units/RepairPoint.java @@ -26,7 +26,6 @@ public class RepairPoint extends Block{ protected float repairRadius = 50f; protected float repairSpeed = 0.3f; - protected float powerUsage = 0.2f; protected TextureRegion topRegion; @@ -39,6 +38,7 @@ public class RepairPoint extends Block{ layer2 = Layer.laser; hasPower = true; powerCapacity = 20f; + consumes.power(0.06f); } @Override @@ -92,10 +92,7 @@ public class RepairPoint extends Block{ entity.rotation = Mathf.slerpDelta(entity.rotation, entity.angleTo(entity.target), 0.5f); } - float powerUse = Math.min(Timers.delta() * powerUsage, powerCapacity); - - if(entity.target != null && entity.power.amount >= powerUse){ - entity.power.amount -= powerUse; + if(entity.target != null && entity.cons.valid()){ entity.strength = Mathf.lerpDelta(entity.strength, 1f, 0.08f * Timers.delta()); }else{ entity.strength = Mathf.lerpDelta(entity.strength, 0f, 0.07f * Timers.delta()); @@ -108,6 +105,13 @@ public class RepairPoint extends Block{ } } + @Override + public boolean shouldConsume(Tile tile) { + RepairPointEntity entity = tile.entity(); + + return entity.target != null; + } + @Override public TileEntity getEntity() { return new RepairPointEntity(); diff --git a/core/src/io/anuke/mindustry/world/blocks/units/ResupplyPoint.java b/core/src/io/anuke/mindustry/world/blocks/units/ResupplyPoint.java index 7f5053a199..adaaeddb34 100644 --- a/core/src/io/anuke/mindustry/world/blocks/units/ResupplyPoint.java +++ b/core/src/io/anuke/mindustry/world/blocks/units/ResupplyPoint.java @@ -27,7 +27,6 @@ public class ResupplyPoint extends Block{ protected float supplyRadius = 50f; protected float supplyInterval = 10f; - protected float powerUsage = 0.2f; public ResupplyPoint(String name) { super(name); @@ -38,6 +37,8 @@ public class ResupplyPoint extends Block{ hasItems = true; hasPower = true; powerCapacity = 20f; + + consumes.power(0.02f); } @Override @@ -98,10 +99,7 @@ public class ResupplyPoint extends Block{ entity.rotation = Mathf.slerpDelta(entity.rotation, entity.angleTo(entity.target), 0.5f); } - float powerUse = Math.min(Timers.delta() * powerUsage, powerCapacity); - - if(entity.target != null && entity.power.amount >= powerUse){ - entity.power.amount -= powerUse; + if(entity.target != null && entity.cons.valid()){ entity.lastx = entity.target.x; entity.lasty = entity.target.y; entity.strength = Mathf.lerpDelta(entity.strength, 1f, 0.08f * Timers.delta()); From bce01012785766f860bc6364cf0e8f518ea06901 Mon Sep 17 00:00:00 2001 From: Anuken Date: Tue, 10 Jul 2018 21:04:14 -0400 Subject: [PATCH 23/47] Fixed some multiplayer crashes --- core/src/io/anuke/mindustry/core/NetClient.java | 2 +- core/src/io/anuke/mindustry/core/NetServer.java | 4 ++-- core/src/io/anuke/mindustry/entities/Player.java | 4 +++- core/src/io/anuke/mindustry/entities/Unit.java | 1 - .../io/anuke/mindustry/entities/traits/CarryTrait.java | 1 + core/src/io/anuke/mindustry/entities/units/BaseUnit.java | 4 +++- core/src/io/anuke/mindustry/io/TypeIO.java | 3 ++- .../world/blocks/production/GenericCrafter.java | 9 +-------- 8 files changed, 13 insertions(+), 15 deletions(-) diff --git a/core/src/io/anuke/mindustry/core/NetClient.java b/core/src/io/anuke/mindustry/core/NetClient.java index cee76a9eae..3b2110156b 100644 --- a/core/src/io/anuke/mindustry/core/NetClient.java +++ b/core/src/io/anuke/mindustry/core/NetClient.java @@ -250,7 +250,7 @@ public class NetClient extends Module { } @Remote(variants = Variant.one, priority = PacketPriority.low, unreliable = true) - public static void onSnapshot(byte[] chunk, int snapshotID, short chunkID, short totalLength, int base){ + public static void onSnapshot(byte[] chunk, int snapshotID, short chunkID, int totalLength, int base){ if(NetServer.showSnapshotSize) Log.info("Recieved snapshot: len {0} ID {1} chunkID {2} totalLength {3} base {4} client-base {5}", chunk.length, snapshotID, chunkID, totalLength, base, netClient.lastSnapshotBaseID); //skip snapshot IDs that have already been recieved OR snapshots that are too far in front diff --git a/core/src/io/anuke/mindustry/core/NetServer.java b/core/src/io/anuke/mindustry/core/NetServer.java index 22198bc7ae..2abffd3abf 100644 --- a/core/src/io/anuke/mindustry/core/NetServer.java +++ b/core/src/io/anuke/mindustry/core/NetServer.java @@ -449,7 +449,7 @@ public class NetServer extends Module{ /**Sends a raw byte[] snapshot to a client, splitting up into chunks when needed.*/ private static void sendSplitSnapshot(int userid, byte[] bytes, int snapshotID, int base){ if(bytes.length < maxSnapshotSize){ - Call.onSnapshot(userid, bytes, snapshotID, (short)0, (short)bytes.length, base); + Call.onSnapshot(userid, bytes, snapshotID, (short)0, bytes.length, base); }else{ int remaining = bytes.length; int offset = 0; @@ -464,7 +464,7 @@ public class NetServer extends Module{ }else { toSend = Arrays.copyOfRange(bytes, offset, Math.min(offset + maxSnapshotSize, bytes.length)); } - Call.onSnapshot(userid, toSend, snapshotID, (short)chunkid, (short)bytes.length, base); + Call.onSnapshot(userid, toSend, snapshotID, (short)chunkid, bytes.length, base); remaining -= used; offset += used; diff --git a/core/src/io/anuke/mindustry/entities/Player.java b/core/src/io/anuke/mindustry/entities/Player.java index 89aecc1120..4cc3b3cf79 100644 --- a/core/src/io/anuke/mindustry/entities/Player.java +++ b/core/src/io/anuke/mindustry/entities/Player.java @@ -473,7 +473,9 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra updateMech(); } - avoidOthers(8f); + if(isLocal) { + avoidOthers(8f); + } if(!isShooting()) { updateBuilding(this); diff --git a/core/src/io/anuke/mindustry/entities/Unit.java b/core/src/io/anuke/mindustry/entities/Unit.java index 016925e73f..60beccd8e8 100644 --- a/core/src/io/anuke/mindustry/entities/Unit.java +++ b/core/src/io/anuke/mindustry/entities/Unit.java @@ -199,7 +199,6 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ } public void avoidOthers(float avoidRange){ - if(Net.client()) return; EntityPhysics.getNearby(getGroup(), x, y, avoidRange*2f, t -> { if(t == this || (t instanceof Unit && (((Unit) t).isDead() || (((Unit) t).isFlying() != isFlying()) || ((Unit) t).getCarrier() == this) || getCarrier() == t)) return; diff --git a/core/src/io/anuke/mindustry/entities/traits/CarryTrait.java b/core/src/io/anuke/mindustry/entities/traits/CarryTrait.java index 7a3e161ff9..a9150df1bd 100644 --- a/core/src/io/anuke/mindustry/entities/traits/CarryTrait.java +++ b/core/src/io/anuke/mindustry/entities/traits/CarryTrait.java @@ -41,6 +41,7 @@ public interface CarryTrait extends TeamTrait, SolidTrait, TargetTrait{ @Remote(called = Loc.both, targets = Loc.both, forward = true, in = In.entities) static void setCarryOf(Player player, CarryTrait trait, CarriableTrait unit){ + if(trait == null) return; if(player != null){ //when a server recieves this called from a player, set the carrier to the player. trait = player; } diff --git a/core/src/io/anuke/mindustry/entities/units/BaseUnit.java b/core/src/io/anuke/mindustry/entities/units/BaseUnit.java index 85004700cb..c76d49cbeb 100644 --- a/core/src/io/anuke/mindustry/entities/units/BaseUnit.java +++ b/core/src/io/anuke/mindustry/entities/units/BaseUnit.java @@ -283,7 +283,9 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{ return; } - avoidOthers(8f); + if(!Net.client()){ + avoidOthers(8f); + } if(squad != null){ squad.update(); diff --git a/core/src/io/anuke/mindustry/io/TypeIO.java b/core/src/io/anuke/mindustry/io/TypeIO.java index ec7bab7a8c..152a72bc20 100644 --- a/core/src/io/anuke/mindustry/io/TypeIO.java +++ b/core/src/io/anuke/mindustry/io/TypeIO.java @@ -106,6 +106,7 @@ public class TypeIO { public static void writeCarry(ByteBuffer buffer, CarryTrait unit){ if(unit == null){ buffer.put((byte)-1); + return; } buffer.put((byte)unit.getGroup().getID()); buffer.putInt(unit.getID()); @@ -123,7 +124,7 @@ public class TypeIO { @WriteClass(BaseUnit.class) public static void writeBaseUnit(ByteBuffer buffer, BaseUnit unit){ - buffer.put((byte)unit.getGroup().getID()); + buffer.put((byte)unitGroups[unit.getTeam().ordinal()].getID()); buffer.putInt(unit.getID()); } diff --git a/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java b/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java index 29393b4c8c..8a6a67c2fd 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java @@ -27,16 +27,9 @@ import static io.anuke.mindustry.Vars.headless; public class GenericCrafter extends Block{ protected final int timerDump = timers++; - - /**Can be null. If you use this, make sure to set hasItems to true!*/ - //protected ItemStack inputItem; - /**Can be null. If you use this, make sure to set hasLiquids to true!*/ - //protected Liquid inputLiquid; - /**Required.*/ + protected Item output; protected float craftTime = 80; - //protected float powerUse; - //protected float liquidUse; protected Effect craftEffect = BlockFx.purify; protected Effect updateEffect = Fx.none; protected float updateEffectChance = 0.04f; From 2b58bd5bd8499dd542227a8a6596f58311561a5a Mon Sep 17 00:00:00 2001 From: Anuken Date: Wed, 11 Jul 2018 12:19:21 -0400 Subject: [PATCH 24/47] Optimizations --- build.gradle | 2 +- core/assets/shaders/outline.fragment | 31 ++++--------- .../src/io/anuke/mindustry/core/Renderer.java | 6 +-- .../anuke/mindustry/core/ThreadHandler.java | 11 ----- .../io/anuke/mindustry/entities/Player.java | 2 +- .../mindustry/entities/units/FlyingUnit.java | 2 +- .../mindustry/entities/units/types/Drone.java | 2 +- .../mindustry/graphics/BlockRenderer.java | 2 +- .../io/anuke/mindustry/graphics/Trail.java | 7 +-- .../io/anuke/mindustry/ui/MobileButton.java | 3 +- .../io/anuke/kryonet/DefaultThreadImpl.java | 44 ------------------- 11 files changed, 21 insertions(+), 91 deletions(-) diff --git a/build.gradle b/build.gradle index 2181519448..53409d95cd 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ allprojects { gdxVersion = '1.9.8' roboVMVersion = '2.3.0' aiVersion = '1.8.1' - uCoreVersion = 'e1749b8798' + uCoreVersion = '7673041e62' getVersionString = { String buildVersion = getBuildVersion() diff --git a/core/assets/shaders/outline.fragment b/core/assets/shaders/outline.fragment index 1c6fdd5b28..46f6f6872d 100644 --- a/core/assets/shaders/outline.fragment +++ b/core/assets/shaders/outline.fragment @@ -3,6 +3,8 @@ precision mediump float; precision mediump int; #endif +#define SPACE 1.0 + uniform sampler2D u_texture; uniform vec4 u_color; @@ -11,30 +13,15 @@ uniform vec2 u_texsize; varying vec4 v_color; varying vec2 v_texCoord; -bool id(vec4 v){ - return v.a > 0.1; -} - void main() { - - vec2 T = v_texCoord.xy; - vec2 v = vec2(1.0/u_texsize.x, 1.0/u_texsize.y); - bool any = false; + vec4 c = texture2D(u_texture, v_texCoord.xy); - float step = 1.0; - - vec4 c = texture2D(u_texture, T); - - if(texture2D(u_texture, T).a < 0.1 && - (id(texture2D(u_texture, T + vec2(0, step) * v)) || id(texture2D(u_texture, T + vec2(0, -step) * v)) || - id(texture2D(u_texture, T + vec2(step, 0) * v)) || id(texture2D(u_texture, T + vec2(-step, 0) * v)))) - any = true; - - if(any){ - gl_FragColor = u_color; - }else{ - gl_FragColor = c * v_color; - } + gl_FragColor = mix(c * v_color, u_color, + (1.0-step(0.1, texture2D(u_texture, v_texCoord.xy).a)) * + step(0.1, texture2D(u_texture, v_texCoord.xy + vec2(0, SPACE) * v).a + + texture2D(u_texture, v_texCoord.xy + vec2(0, -SPACE) * v).a + + texture2D(u_texture, v_texCoord.xy + vec2(SPACE, 0) * v).a + + texture2D(u_texture, v_texCoord.xy + vec2(-SPACE, 0) * v).a)); } diff --git a/core/src/io/anuke/mindustry/core/Renderer.java b/core/src/io/anuke/mindustry/core/Renderer.java index dfa84d2041..dce53892cf 100644 --- a/core/src/io/anuke/mindustry/core/Renderer.java +++ b/core/src/io/anuke/mindustry/core/Renderer.java @@ -253,7 +253,7 @@ public class Renderer extends RendererModule{ } private void drawFlyerShadows(){ - Graphics.surface(effectSurface); + Graphics.surface(effectSurface, true, false); float trnsX = 12, trnsY = -13; @@ -263,12 +263,12 @@ public class Renderer extends RendererModule{ for(EntityGroup group : unitGroups){ if(!group.isEmpty()){ - drawAndInterpolate(group, Unit::isFlying, Unit::drawShadow); + drawAndInterpolate(group, unit -> unit.isFlying() && !unit.isDead(), Unit::drawShadow); } } if(!playerGroup.isEmpty()){ - drawAndInterpolate(playerGroup, Unit::isFlying, Unit::drawShadow); + drawAndInterpolate(playerGroup, unit -> unit.isFlying() && !unit.isDead(), Unit::drawShadow); } Graphics.end(); diff --git a/core/src/io/anuke/mindustry/core/ThreadHandler.java b/core/src/io/anuke/mindustry/core/ThreadHandler.java index e127b94941..66baa64fa2 100644 --- a/core/src/io/anuke/mindustry/core/ThreadHandler.java +++ b/core/src/io/anuke/mindustry/core/ThreadHandler.java @@ -4,10 +4,6 @@ import com.badlogic.gdx.Gdx; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.TimeUtils; import io.anuke.ucore.core.Timers; -import io.anuke.ucore.entities.Entities; -import io.anuke.ucore.entities.EntityGroup; -import io.anuke.ucore.entities.EntityGroup.ArrayContainer; -import io.anuke.ucore.entities.trait.Entity; import io.anuke.ucore.util.Log; import static io.anuke.mindustry.Vars.control; @@ -89,9 +85,6 @@ public class ThreadHandler { public void setEnabled(boolean enabled){ if(enabled){ logic.doUpdate = false; - for(EntityGroup group : Entities.getAllGroups()){ - impl.switchContainer(group); - } Timers.runTask(2f, () -> { impl.start(this::runLogic); this.enabled = true; @@ -99,9 +92,6 @@ public class ThreadHandler { }else{ this.enabled = false; impl.stop(); - for(EntityGroup group : Entities.getAllGroups()){ - group.setContainer(new ArrayContainer<>()); - } Timers.runTask(2f, () -> { logic.doUpdate = true; }); @@ -175,6 +165,5 @@ public class ThreadHandler { void stop(); void wait(Object object) throws InterruptedException; void notify(Object object); - void switchContainer(EntityGroup group); } } diff --git a/core/src/io/anuke/mindustry/entities/Player.java b/core/src/io/anuke/mindustry/entities/Player.java index 4cc3b3cf79..bd88c5b98c 100644 --- a/core/src/io/anuke/mindustry/entities/Player.java +++ b/core/src/io/anuke/mindustry/entities/Player.java @@ -374,7 +374,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra float wobblyness = 0.6f; trail.update(x + Angles.trnsx(rotation + 180f, 5f) + Mathf.range(wobblyness), y + Angles.trnsy(rotation + 180f, 5f) + Mathf.range(wobblyness)); - trail.draw(mech.trailColor, mech.trailColor, 5f * (isFlying() ? 1f : boostHeat)); + trail.draw(mech.trailColor, 5f * (isFlying() ? 1f : boostHeat)); }else{ trail.clear(); } diff --git a/core/src/io/anuke/mindustry/entities/units/FlyingUnit.java b/core/src/io/anuke/mindustry/entities/units/FlyingUnit.java index 7a65787501..b4353ca108 100644 --- a/core/src/io/anuke/mindustry/entities/units/FlyingUnit.java +++ b/core/src/io/anuke/mindustry/entities/units/FlyingUnit.java @@ -76,7 +76,7 @@ public abstract class FlyingUnit extends BaseUnit implements CarryTrait{ @Override public void drawOver() { - trail.draw(Palette.lightTrail, Palette.lightTrail, 5f); + trail.draw(Palette.lightTrail, 5f); } @Override diff --git a/core/src/io/anuke/mindustry/entities/units/types/Drone.java b/core/src/io/anuke/mindustry/entities/units/types/Drone.java index e814aef0a5..ac004b943f 100644 --- a/core/src/io/anuke/mindustry/entities/units/types/Drone.java +++ b/core/src/io/anuke/mindustry/entities/units/types/Drone.java @@ -149,7 +149,7 @@ public class Drone extends FlyingUnit implements BuilderTrait { @Override public void drawOver() { - trail.draw(Palette.lightTrail, Palette.lightTrail, 3f); + trail.draw(Palette.lightTrail, 3f); TargetTrait entity = target; diff --git a/core/src/io/anuke/mindustry/graphics/BlockRenderer.java b/core/src/io/anuke/mindustry/graphics/BlockRenderer.java index 86e01d8300..7a898eb72f 100644 --- a/core/src/io/anuke/mindustry/graphics/BlockRenderer.java +++ b/core/src/io/anuke/mindustry/graphics/BlockRenderer.java @@ -57,7 +57,7 @@ public class BlockRenderer{ int expandr = 4; - Graphics.surface(renderer.effectSurface); + Graphics.surface(renderer.effectSurface, true, false); int avgx = Mathf.scl(camera.position.x, tilesize); int avgy = Mathf.scl(camera.position.y, tilesize); diff --git a/core/src/io/anuke/mindustry/graphics/Trail.java b/core/src/io/anuke/mindustry/graphics/Trail.java index 04389cd44b..0a21f06d3e 100644 --- a/core/src/io/anuke/mindustry/graphics/Trail.java +++ b/core/src/io/anuke/mindustry/graphics/Trail.java @@ -40,7 +40,8 @@ public class Trail { points.clear(); } - public synchronized void draw(Color start, Color end, float stroke){ + public synchronized void draw(Color color, float stroke){ + Draw.color(color); for(int i = 0; i < points.size - 2; i += 2){ float x = points.get(i); @@ -49,8 +50,6 @@ public class Trail { float y2 = points.get(i + 3); float s = Mathf.clamp((float)(i) / points.size); - Draw.color(start, end, s); - Lines.stroke(s * stroke); Lines.line(x, y, x2, y2); } @@ -59,8 +58,6 @@ public class Trail { Fill.circle(points.get(points.size-2), points.get(points.size-1), stroke/2f); } - Draw.color(start); - Draw.reset(); } } diff --git a/core/src/io/anuke/mindustry/ui/MobileButton.java b/core/src/io/anuke/mindustry/ui/MobileButton.java index 6d7f8211d5..c2d7585c80 100644 --- a/core/src/io/anuke/mindustry/ui/MobileButton.java +++ b/core/src/io/anuke/mindustry/ui/MobileButton.java @@ -1,5 +1,6 @@ package io.anuke.mindustry.ui; +import com.badlogic.gdx.utils.Align; import io.anuke.ucore.function.Listenable; import io.anuke.ucore.scene.ui.ImageButton; @@ -10,6 +11,6 @@ public class MobileButton extends ImageButton { resizeImage(isize); clicked(listener); row(); - add(text).growX().wrap(); + add(text).growX().wrap().center().get().setAlignment(Align.center, Align.center); } } diff --git a/kryonet/src/io/anuke/kryonet/DefaultThreadImpl.java b/kryonet/src/io/anuke/kryonet/DefaultThreadImpl.java index 7e2b8670f6..ff2d80c4b2 100644 --- a/kryonet/src/io/anuke/kryonet/DefaultThreadImpl.java +++ b/kryonet/src/io/anuke/kryonet/DefaultThreadImpl.java @@ -1,14 +1,8 @@ package io.anuke.kryonet; import io.anuke.mindustry.core.ThreadHandler.ThreadProvider; -import io.anuke.ucore.entities.EntityGroup; -import io.anuke.ucore.entities.EntityGroup.EntityContainer; -import io.anuke.ucore.entities.trait.Entity; import io.anuke.ucore.util.Log; -import java.util.Iterator; -import java.util.concurrent.CopyOnWriteArrayList; - public class DefaultThreadImpl implements ThreadProvider { private Thread thread; @@ -54,42 +48,4 @@ public class DefaultThreadImpl implements ThreadProvider { object.notify(); } - @Override - public void switchContainer(EntityGroup group) { - group.setContainer(new ConcurrentContainer<>()); - } - - static class ConcurrentContainer implements EntityContainer{ - private CopyOnWriteArrayList list = new CopyOnWriteArrayList<>(); - - @Override - public int size() { - return list.size(); - } - - @Override - public void add(T item) { - list.add(item); - } - - @Override - public void clear() { - list.clear(); - } - - @Override - public void remove(T item) { - list.remove(item); - } - - @Override - public T get(int index) { - return list.get(index); - } - - @Override - public Iterator iterator() { - return list.iterator(); - } - } } From 73087a92f421953921b2ac33e0e537fd6258b533 Mon Sep 17 00:00:00 2001 From: Anuken Date: Wed, 11 Jul 2018 12:21:46 -0400 Subject: [PATCH 25/47] Fixed build error --- core/src/io/anuke/mindustry/core/Platform.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/src/io/anuke/mindustry/core/Platform.java b/core/src/io/anuke/mindustry/core/Platform.java index d9e87dcd44..c4064fd5fb 100644 --- a/core/src/io/anuke/mindustry/core/Platform.java +++ b/core/src/io/anuke/mindustry/core/Platform.java @@ -4,8 +4,6 @@ import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.utils.Base64Coder; import io.anuke.mindustry.core.ThreadHandler.ThreadProvider; import io.anuke.ucore.core.Settings; -import io.anuke.ucore.entities.EntityGroup; -import io.anuke.ucore.entities.trait.Entity; import io.anuke.ucore.function.Consumer; import io.anuke.ucore.scene.ui.TextField; @@ -87,7 +85,6 @@ public abstract class Platform { @Override public void stop() {} @Override public void notify(Object object) {} @Override public void wait(Object object) {} - @Override public void switchContainer(EntityGroup group) {} }; } From b4a20c8fcbbe956bee56fb5173686e7b530dcc94 Mon Sep 17 00:00:00 2001 From: Anuken Date: Wed, 11 Jul 2018 12:30:07 -0400 Subject: [PATCH 26/47] Fixed fog glitching around during screenshake --- core/src/io/anuke/mindustry/graphics/FogRenderer.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/src/io/anuke/mindustry/graphics/FogRenderer.java b/core/src/io/anuke/mindustry/graphics/FogRenderer.java index 7d1d2048a4..2e8c2ba52b 100644 --- a/core/src/io/anuke/mindustry/graphics/FogRenderer.java +++ b/core/src/io/anuke/mindustry/graphics/FogRenderer.java @@ -1,5 +1,6 @@ package io.anuke.mindustry.graphics; +import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.Pixmap.Format; import com.badlogic.gdx.graphics.Texture; @@ -122,8 +123,8 @@ public class FogRenderer implements Disposable{ renderer.pixelSurface.getBuffer().end(); Graphics.shader(); - Graphics.begin(); - Core.batch.draw(renderer.pixelSurface.texture(), px, py + vh, vw, -vh); + Graphics.setScreen(); + Core.batch.draw(renderer.pixelSurface.texture(), 0, Gdx.graphics.getHeight(), Gdx.graphics.getWidth(), -Gdx.graphics.getHeight()); Graphics.end(); if(Core.batch instanceof ClipSpriteBatch){ From d89feb2c757237da54339e979e2fcddf2281ea83 Mon Sep 17 00:00:00 2001 From: Anuken Date: Wed, 11 Jul 2018 19:57:27 -0400 Subject: [PATCH 27/47] Bugfixes, minor optimizations --- build.gradle | 2 +- .../io/anuke/mindustry/entities/Player.java | 1 + .../io/anuke/mindustry/entities/Units.java | 35 ++++++++++--------- .../anuke/mindustry/graphics/CacheLayer.java | 5 ++- .../mindustry/ui/dialogs/UnlocksDialog.java | 2 +- .../blocks/distribution/OverflowGate.java | 8 +++-- .../world/mapgen/WorldGenerator.java | 8 ++--- gradle.properties | 1 - 8 files changed, 34 insertions(+), 28 deletions(-) diff --git a/build.gradle b/build.gradle index 53409d95cd..0ac560c982 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { dependencies { classpath 'com.mobidevelop.robovm:robovm-gradle-plugin:2.3.0' classpath 'de.richsource.gradle.plugins:gwt-gradle-plugin:0.6' - classpath 'com.android.tools.build:gradle:3.1.0' + classpath 'com.android.tools.build:gradle:3.1.3' classpath "com.badlogicgames.gdx:gdx-tools:1.9.8" } } diff --git a/core/src/io/anuke/mindustry/entities/Player.java b/core/src/io/anuke/mindustry/entities/Player.java index bd88c5b98c..77af915292 100644 --- a/core/src/io/anuke/mindustry/entities/Player.java +++ b/core/src/io/anuke/mindustry/entities/Player.java @@ -662,6 +662,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra /**Resets all values of the player.*/ public void reset(){ + status.clear(); team = Team.blue; inventory.clear(); placeQueue.clear(); diff --git a/core/src/io/anuke/mindustry/entities/Units.java b/core/src/io/anuke/mindustry/entities/Units.java index 86b74a1968..291b80ca8c 100644 --- a/core/src/io/anuke/mindustry/entities/Units.java +++ b/core/src/io/anuke/mindustry/entities/Units.java @@ -19,6 +19,9 @@ import static io.anuke.mindustry.Vars.*; public class Units { private static Rectangle rect = new Rectangle(); private static Rectangle hitrect = new Rectangle(); + private static Unit result; + private static float cdist; + private static boolean boolResult; /**Validates a target. * @param target The target to validate @@ -49,20 +52,20 @@ public class Units { rect.setSize(type.size * tilesize, type.size * tilesize); rect.setCenter(tile.drawx(), tile.drawy()); - boolean[] value = new boolean[1]; + boolResult = false; Units.getNearby(rect, unit -> { - if (value[0]) return; + if (boolResult) return; if (!unit.isFlying()) { unit.getHitbox(hitrect); if (hitrect.overlaps(rect)) { - value[0] = true; + boolResult = true; } } }); - return value[0]; + return boolResult; } /**Returns whether there are any entities on this tile, with the hitbox expanded.*/ @@ -138,8 +141,8 @@ public class Units { /**Returns the closest enemy of this team. Filter by predicate.*/ public static Unit getClosestEnemy(Team team, float x, float y, float range, Predicate predicate){ - Unit[] result = {null}; - float[] cdist = {0}; + result = null; + cdist = 0f; rect.setSize(range*2f).setCenter(x, y); @@ -149,20 +152,20 @@ public class Units { float dist = Vector2.dst(e.x, e.y, x, y); if (dist < range) { - if (result[0] == null || dist < cdist[0]) { - result[0] = e; - cdist[0] = dist; + if (result == null || dist < cdist) { + result = e; + cdist = dist; } } }); - return result[0]; + return result; } /**Returns the closest ally of this team. Filter by predicate.*/ public static Unit getClosest(Team team, float x, float y, float range, Predicate predicate){ - Unit[] result = {null}; - float[] cdist = {0}; + result = null; + cdist = 0f; rect.setSize(range*2f).setCenter(x, y); @@ -172,14 +175,14 @@ public class Units { float dist = Vector2.dst(e.x, e.y, x, y); if (dist < range) { - if (result[0] == null || dist < cdist[0]) { - result[0] = e; - cdist[0] = dist; + if (result == null || dist < cdist) { + result = e; + cdist = dist; } } }); - return result[0]; + return result; } /**Iterates over all units in a rectangle.*/ diff --git a/core/src/io/anuke/mindustry/graphics/CacheLayer.java b/core/src/io/anuke/mindustry/graphics/CacheLayer.java index 623497e912..f1471c2a42 100644 --- a/core/src/io/anuke/mindustry/graphics/CacheLayer.java +++ b/core/src/io/anuke/mindustry/graphics/CacheLayer.java @@ -66,16 +66,15 @@ public enum CacheLayer { protected void beginShader(){ //renderer.getBlocks().endFloor(); - renderer.effectSurface.getBuffer().begin(); + renderer.effectSurface.getBuffer().bind(); Graphics.clear(Color.CLEAR); //renderer.getBlocks().beginFloor(); } public void endShader(Shader shader){ renderer.getBlocks().endFloor(); - renderer.effectSurface.getBuffer().end(); - renderer.pixelSurface.getBuffer().begin(); + renderer.pixelSurface.getBuffer().bind(); Graphics.shader(shader); Graphics.begin(); diff --git a/core/src/io/anuke/mindustry/ui/dialogs/UnlocksDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/UnlocksDialog.java index ce2de1999d..87f667e628 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/UnlocksDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/UnlocksDialog.java @@ -45,7 +45,7 @@ public class UnlocksDialog extends FloatingDialog { table.table(list -> { list.left(); - int maxWidth = UIUtils.portrait() ? 7 : 14; + int maxWidth = UIUtils.portrait() ? 7 : 13; int size = 8*6; int count = 0; diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/OverflowGate.java b/core/src/io/anuke/mindustry/world/blocks/distribution/OverflowGate.java index bb1ba9df14..60dbf0adc7 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/OverflowGate.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/OverflowGate.java @@ -17,8 +17,8 @@ public class OverflowGate extends Splitter { if(dir == -1) return null; Tile to = dest.getNearby(dir); - if(!(to.block().acceptItem(item, to, dest) && - !(to.block().instantTransfer && source.block().instantTransfer))){ + if(!(to.block().acceptItem(item, to, dest) || + (to.block().instantTransfer && source.block().instantTransfer))){ Tile a = dest.getNearby(Mathf.mod(dir - 1, 4)); Tile b = dest.getNearby(Mathf.mod(dir + 1, 4)); boolean ac = !(a.block().instantTransfer && source.block().instantTransfer) && @@ -26,6 +26,10 @@ public class OverflowGate extends Splitter { boolean bc = !(b.block().instantTransfer && source.block().instantTransfer) && b.block().acceptItem(item, b, dest); + if(!ac && !bc){ + return null; + } + if(ac && !bc){ to = a; }else if(bc && !ac){ diff --git a/core/src/io/anuke/mindustry/world/mapgen/WorldGenerator.java b/core/src/io/anuke/mindustry/world/mapgen/WorldGenerator.java index ef546070fb..46643069eb 100644 --- a/core/src/io/anuke/mindustry/world/mapgen/WorldGenerator.java +++ b/core/src/io/anuke/mindustry/world/mapgen/WorldGenerator.java @@ -131,9 +131,9 @@ public class WorldGenerator { for(int i = ores.size-1; i >= 0; i --){ OreEntry entry = ores.get(i); - if(entry.noise.octaveNoise2D(2, 0.7, 1f / (2 + i*2), x, y)/2f + - entry.ridge.getValue(x, y, 1f / (28 + i*4)) >= 2.0f - entry.frequency*4.0f - && entry.ridge.getValue(x+9999, y+9999, 1f/100f) > 0.4){ + if(entry.noise.octaveNoise2D(1, 0.7, 1f / (4 + i*2), x, y)/4f + + Math.abs(0.5f-entry.noise.octaveNoise2D(2, 0.7, 1f / (50 + i*2), x, y)) > 0.5f && + Math.abs(0.5f-entry.noise.octaveNoise2D(1, 1, 1f / (55 + i*4), x, y)) > 0.25f){ tile.setFloor((Floor) OreBlocks.get(tile.floor(), entry.item)); break; } @@ -166,7 +166,7 @@ public class WorldGenerator { Block wall = Blocks.air; double elevation = sim.octaveNoise2D(3, 0.5, 1f/100, x, y) * 4.1 - 1; - double temp = sim3.octaveNoise2D(7, 0.53, 1f/320f, x, y); + double temp = sim3.octaveNoise2D(7, 0.54, 1f/320f, x, y); double r = sim2.octaveNoise2D(1, 0.6, 1f/70, x, y); double edgeDist = Math.max(width/2, height/2) - Math.max(Math.abs(x - width/2), Math.abs(y - height/2)); diff --git a/gradle.properties b/gradle.properties index 93e3c4269f..d8e2a1210f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,5 @@ org.gradle.daemon=true org.gradle.jvmargs=-Xms512m -Xmx1536m -org.gradle.configureondemand=true android.enableAapt2=true android.injected.build.model.only.versioned=3 android.enableD8=true From be2a745d78bf0058ab91ba3a62c2e243e67a282d Mon Sep 17 00:00:00 2001 From: Anuken Date: Wed, 11 Jul 2018 19:58:49 -0400 Subject: [PATCH 28/47] Increased ore generation amount --- core/src/io/anuke/mindustry/world/mapgen/WorldGenerator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/io/anuke/mindustry/world/mapgen/WorldGenerator.java b/core/src/io/anuke/mindustry/world/mapgen/WorldGenerator.java index 46643069eb..453764efe2 100644 --- a/core/src/io/anuke/mindustry/world/mapgen/WorldGenerator.java +++ b/core/src/io/anuke/mindustry/world/mapgen/WorldGenerator.java @@ -132,8 +132,8 @@ public class WorldGenerator { for(int i = ores.size-1; i >= 0; i --){ OreEntry entry = ores.get(i); if(entry.noise.octaveNoise2D(1, 0.7, 1f / (4 + i*2), x, y)/4f + - Math.abs(0.5f-entry.noise.octaveNoise2D(2, 0.7, 1f / (50 + i*2), x, y)) > 0.5f && - Math.abs(0.5f-entry.noise.octaveNoise2D(1, 1, 1f / (55 + i*4), x, y)) > 0.25f){ + Math.abs(0.5f-entry.noise.octaveNoise2D(2, 0.7, 1f / (50 + i*2), x, y)) > 0.48f && + Math.abs(0.5f-entry.noise.octaveNoise2D(1, 1, 1f / (55 + i*4), x, y)) > 0.22f){ tile.setFloor((Floor) OreBlocks.get(tile.floor(), entry.item)); break; } From 6375862a716465c4b4d7faaed9f30a3630415148 Mon Sep 17 00:00:00 2001 From: Anuken Date: Thu, 12 Jul 2018 09:11:28 -0400 Subject: [PATCH 29/47] Fixed deadlock / Crafter fixes / Autotile fixes / Low FPS ship fixes --- .../content/blocks/CraftingBlocks.java | 5 ++-- .../anuke/mindustry/core/ThreadHandler.java | 15 ++++++++---- core/src/io/anuke/mindustry/core/UI.java | 2 +- core/src/io/anuke/mindustry/core/World.java | 23 +++++++++++++++++++ .../io/anuke/mindustry/entities/Player.java | 13 ++++++----- .../mindustry/world/blocks/BuildBlock.java | 2 +- .../world/blocks/distribution/Conduit.java | 12 +++++----- .../blocks/power/ItemLiquidGenerator.java | 4 ++-- .../world/blocks/power/TurbineGenerator.java | 11 +++++++++ .../world/blocks/production/Fracker.java | 5 ++++ .../world/blocks/production/Pump.java | 5 ---- .../world/blocks/production/SolidPump.java | 10 +++++--- .../world/consumers/ConsumeLiquidFilter.java | 3 --- .../world/mapgen/WorldGenerator.java | 2 ++ 14 files changed, 79 insertions(+), 33 deletions(-) diff --git a/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java b/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java index 945a15ef36..ac9ce03f19 100644 --- a/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java @@ -153,7 +153,7 @@ public class CraftingBlocks extends BlockList implements ContentList { melter = new PowerCrafter("melter") {{ health = 200; outputLiquid = Liquids.lava; - outputLiquidAmount = 0.05f; + outputLiquidAmount = 0.75f; itemCapacity = 50; craftTime = 10f; hasLiquids = hasPower = true; @@ -212,7 +212,7 @@ public class CraftingBlocks extends BlockList implements ContentList { itemCapacity = 50; craftTime = 25f; outputLiquid = Liquids.oil; - outputLiquidAmount = 0.1f; + outputLiquidAmount = 0.14f; size = 2; health = 320; hasLiquids = true; @@ -238,6 +238,7 @@ public class CraftingBlocks extends BlockList implements ContentList { liquidCapacity = 21f; craftTime = 14; output = Items.stone; + itemCapacity = 20; health = 80; craftEffect = BlockFx.purifystone; hasLiquids = hasItems = true; diff --git a/core/src/io/anuke/mindustry/core/ThreadHandler.java b/core/src/io/anuke/mindustry/core/ThreadHandler.java index 66baa64fa2..e87203a1a9 100644 --- a/core/src/io/anuke/mindustry/core/ThreadHandler.java +++ b/core/src/io/anuke/mindustry/core/ThreadHandler.java @@ -115,11 +115,18 @@ public class ThreadHandler { while (true) { long time = TimeUtils.nanoTime(); - synchronized (toRun) { - for(Runnable r : toRun){ - r.run(); + while(true){ + Runnable r; + synchronized (toRun){ + if(toRun.size > 0){ + r = toRun.pop(); + }else{ + r = null; + } } - toRun.clear(); + + if(r == null) break; + r.run(); } logic.doUpdate = true; diff --git a/core/src/io/anuke/mindustry/core/UI.java b/core/src/io/anuke/mindustry/core/UI.java index 53e5497a1b..821ea28cff 100644 --- a/core/src/io/anuke/mindustry/core/UI.java +++ b/core/src/io/anuke/mindustry/core/UI.java @@ -225,7 +225,7 @@ public class UI extends SceneModule{ public void loadAnd(String text, Callable call){ loadfrag.show(text); - Timers.runTask(6f, () -> { + Timers.runTask(7f, () -> { call.run(); loadfrag.hide(); }); diff --git a/core/src/io/anuke/mindustry/core/World.java b/core/src/io/anuke/mindustry/core/World.java index 1859dd94b1..73a03e013b 100644 --- a/core/src/io/anuke/mindustry/core/World.java +++ b/core/src/io/anuke/mindustry/core/World.java @@ -10,6 +10,7 @@ import io.anuke.mindustry.content.blocks.Blocks; import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.game.EventType.TileChangeEvent; import io.anuke.mindustry.game.EventType.WorldLoadEvent; +import io.anuke.mindustry.game.Team; import io.anuke.mindustry.io.*; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; @@ -267,6 +268,28 @@ public class World extends Module{ } } + public void setBlock(Tile tile, Block block, Team team){ + tile.setBlock(block); + if (block.isMultiblock()) { + int offsetx = -(block.size - 1) / 2; + int offsety = -(block.size - 1) / 2; + + for (int dx = 0; dx < block.size; dx++) { + for (int dy = 0; dy < block.size; dy++) { + int worldx = dx + offsetx + tile.x; + int worldy = dy + offsety + tile.y; + if (!(worldx == tile.x && worldy == tile.y)) { + Tile toplace = world.tile(worldx, worldy); + if (toplace != null) { + toplace.setLinked((byte) (dx + offsetx), (byte) (dy + offsety)); + toplace.setTeam(team); + } + } + } + } + } + } + /**Raycast, but with world coordinates.*/ public GridPoint2 raycastWorld(float x, float y, float x2, float y2){ return raycast(Mathf.scl2(x, tilesize), Mathf.scl2(y, tilesize), diff --git a/core/src/io/anuke/mindustry/entities/Player.java b/core/src/io/anuke/mindustry/entities/Player.java index 77af915292..2775aa3438 100644 --- a/core/src/io/anuke/mindustry/entities/Player.java +++ b/core/src/io/anuke/mindustry/entities/Player.java @@ -599,18 +599,13 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra } movement.set(targetX - x, targetY - y).limit(mech.speed); - movement.setAngle(Mathf.slerpDelta(movement.angle(), velocity.angle(), 0.05f)); + movement.setAngle(Mathf.slerp(movement.angle(), velocity.angle(), 0.05f)); if(distanceTo(targetX, targetY) < attractDst){ movement.setZero(); } velocity.add(movement); - updateVelocityStatus(mech.drag, mech.maxSpeed); - - //hovering effect - x += Mathf.sin(Timers.time() + id * 999, 25f, 0.08f); - y += Mathf.cos(Timers.time() + id * 999, 25f, 0.08f); if(velocity.len() <= 0.2f){ rotation += Mathf.sin(Timers.time() + id * 99, 10f, 1f); @@ -618,6 +613,12 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra rotation = Mathf.slerpDelta(rotation, velocity.angle(), velocity.len()/10f); } + updateVelocityStatus(mech.drag, mech.maxSpeed); + + //hovering effect + x += Mathf.sin(Timers.time() + id * 999, 25f, 0.08f); + y += Mathf.cos(Timers.time() + id * 999, 25f, 0.08f); + //update shooting if not building, not mining and there's ammo left if(!isBuilding() && inventory.hasAmmo() && getMineTile() == null){ diff --git a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java index ab692b560a..3e30dbedc4 100644 --- a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java @@ -161,7 +161,7 @@ public class BuildBlock extends Block { @Remote(called = Loc.server, in = In.blocks) public static void onConstructFinish(Tile tile, Block block, int builderID, byte rotation, Team team){ - tile.setBlock(block); + world.setBlock(tile, block, team); tile.setRotation(rotation); tile.setTeam(team); Effects.effect(Fx.placeBlock, tile.drawx(), tile.drawy(), block.size); diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java index e5bcdbf84f..7aa7c3eac9 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java @@ -40,15 +40,15 @@ public class Conduit extends LiquidBlock { ConduitEntity entity = tile.entity(); entity.blendbits = 0; - if(blends(tile, 0)){ + if(blends(tile, 1) && blends(tile, 2)) { + entity.blendbits = 2; + }else if(blends(tile, 3) && blends(tile, 2)) { + entity.blendbits = 4; + }else if(blends(tile, 0)){ if(blends(tile, 2) && blends(tile, 1) && blends(tile, 3)) { entity.blendbits = 3; }else if(blends(tile, 1) && blends(tile, 3)) { - entity.blendbits = 6; - }else if(blends(tile, 1) && blends(tile, 2)) { - entity.blendbits = 2; - }else if(blends(tile, 3) && blends(tile, 2)) { - entity.blendbits = 4; + entity.blendbits = 6; }else if(blends(tile, 1)) { entity.blendbits = 5; }else if(blends(tile, 3)) { diff --git a/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java b/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java index cb290d8f25..a5333f80be 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java @@ -31,7 +31,7 @@ public abstract class ItemLiquidGenerator extends ItemGenerator { ItemGeneratorEntity entity = tile.entity(); //liquid takes priority over solids - if(entity.liquids.currentAmount() >= 0.001f){ + if(entity.liquids.currentAmount() >= 0.001f && entity.cons.valid()){ float powerPerLiquid = getLiquidEfficiency(entity.liquids.current())*this.powerPerLiquid; float used = Math.min(entity.liquids.currentAmount(), maxLiquidGenerate * Timers.delta()); used = Math.min(used, (powerCapacity - entity.power.amount)/powerPerLiquid); @@ -47,7 +47,7 @@ public abstract class ItemLiquidGenerator extends ItemGenerator { float maxPower = Math.min(powerCapacity - entity.power.amount, powerOutput * Timers.delta()) * entity.efficiency; float mfract = maxPower / (powerOutput); - if (entity.generateTime > 0f) { + if (entity.generateTime > 0f && entity.cons.valid()) { entity.generateTime -= 1f / itemDuration * mfract; entity.power.amount += maxPower; entity.generateTime = Mathf.clamp(entity.generateTime); diff --git a/core/src/io/anuke/mindustry/world/blocks/power/TurbineGenerator.java b/core/src/io/anuke/mindustry/world/blocks/power/TurbineGenerator.java index 8efd90fbc9..99e679557a 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/TurbineGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/TurbineGenerator.java @@ -1,10 +1,21 @@ package io.anuke.mindustry.world.blocks.power; +import io.anuke.mindustry.type.Liquid; +import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.consumers.ConsumeLiquid; + //TODO public class TurbineGenerator extends BurnerGenerator { public TurbineGenerator(String name) { super(name); singleLiquid = false; + + consumes.require(ConsumeLiquid.class); + } + + @Override + public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){ + return super.acceptLiquid(tile, source, liquid, amount) || liquid == consumes.liquid() && tile.entity.liquids.get(consumes.liquid()) < liquidCapacity; } } diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Fracker.java b/core/src/io/anuke/mindustry/world/blocks/production/Fracker.java index 5a2700bcb1..25507dcb32 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Fracker.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Fracker.java @@ -81,6 +81,11 @@ public class Fracker extends SolidPump { return new FrackerEntity(); } + @Override + public float typeLiquid(Tile tile) { + return tile.entity.liquids.get(result); + } + public static class FrackerEntity extends SolidPumpEntity{ public float accumulator; } diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Pump.java b/core/src/io/anuke/mindustry/world/blocks/production/Pump.java index cbfff62e71..97c6129e32 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Pump.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Pump.java @@ -42,11 +42,6 @@ public class Pump extends LiquidBlock{ stats.add(BlockStat.liquidOutput, 60f*pumpAmount, StatUnit.liquidSecond); } - @Override - public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){ - return false; - } - @Override public void draw(Tile tile){ Draw.rect(name(), tile.drawx(), tile.drawy()); diff --git a/core/src/io/anuke/mindustry/world/blocks/production/SolidPump.java b/core/src/io/anuke/mindustry/world/blocks/production/SolidPump.java index ac852972e0..76993b79ad 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/SolidPump.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/SolidPump.java @@ -74,8 +74,8 @@ public class SolidPump extends Pump { if(isValid(tile)) fraction = 1f; } - if(tile.entity.cons.valid() && tile.entity.liquids.total() < liquidCapacity - 0.001f){ - float maxPump = Math.min(liquidCapacity - tile.entity.liquids.total(), pumpAmount * Timers.delta() * fraction); + if(tile.entity.cons.valid() && typeLiquid(tile) < liquidCapacity - 0.001f){ + float maxPump = Math.min(liquidCapacity - typeLiquid(tile), pumpAmount * Timers.delta() * fraction); tile.entity.liquids.add(result, maxPump); entity.warmup = Mathf.lerpDelta(entity.warmup, 1f, 0.02f); if(Mathf.chance(Timers.delta() * updateEffectChance)) @@ -86,7 +86,7 @@ public class SolidPump extends Pump { entity.pumpTime += entity.warmup * Timers.delta(); - tryDumpLiquid(tile, entity.liquids.current()); + tryDumpLiquid(tile, result); } @Override @@ -113,6 +113,10 @@ public class SolidPump extends Pump { return new SolidPumpEntity(); } + public float typeLiquid(Tile tile){ + return tile.entity.liquids.total(); + } + public static class SolidPumpEntity extends TileEntity{ public float warmup; public float pumpTime; diff --git a/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquidFilter.java b/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquidFilter.java index 8ad414f22a..c6e818dbdc 100644 --- a/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquidFilter.java +++ b/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquidFilter.java @@ -11,7 +11,6 @@ import io.anuke.mindustry.world.meta.values.LiquidFilterValue; import io.anuke.ucore.core.Timers; import io.anuke.ucore.function.Predicate; import io.anuke.ucore.scene.ui.layout.Table; -import io.anuke.ucore.util.Strings; public class ConsumeLiquidFilter extends Consume{ private final Predicate filter; @@ -43,8 +42,6 @@ public class ConsumeLiquidFilter extends Consume{ table.add("/"); } } - - table.add("x" + Strings.toFixed(use * 60f, 1)); } @Override diff --git a/core/src/io/anuke/mindustry/world/mapgen/WorldGenerator.java b/core/src/io/anuke/mindustry/world/mapgen/WorldGenerator.java index 453764efe2..8d53a33c0d 100644 --- a/core/src/io/anuke/mindustry/world/mapgen/WorldGenerator.java +++ b/core/src/io/anuke/mindustry/world/mapgen/WorldGenerator.java @@ -1,6 +1,7 @@ package io.anuke.mindustry.world.mapgen; import com.badlogic.gdx.math.GridPoint2; +import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.IntArray; @@ -144,6 +145,7 @@ public class WorldGenerator { } public static void generateMap(Tile[][] tiles, int seed){ + MathUtils.random.setSeed((long)(Math.random() * 99999999)); Simplex sim = new Simplex(Mathf.random(99999)); Simplex sim2 = new Simplex(Mathf.random(99999)); Simplex sim3 = new Simplex(Mathf.random(99999)); From f41e426b6959b18690e7a5d2f4266567be4622f1 Mon Sep 17 00:00:00 2001 From: Anuken Date: Thu, 12 Jul 2018 10:20:38 -0400 Subject: [PATCH 30/47] Reactor balancing / Fixed generators / Fixed pump crash / Travis update --- .travis.yml | 4 +-- .../io/anuke/mindustry/content/Liquids.java | 4 +-- .../content/blocks/CraftingBlocks.java | 2 +- .../content/blocks/LiquidBlocks.java | 1 + core/src/io/anuke/mindustry/type/Liquid.java | 3 +- .../io/anuke/mindustry/world/BaseBlock.java | 1 + .../mindustry/world/blocks/LiquidBlock.java | 1 + .../world/blocks/distribution/Conduit.java | 12 ++++--- .../blocks/distribution/LiquidBridge.java | 1 + .../distribution/LiquidExtendingBridge.java | 1 + .../world/blocks/power/ItemGenerator.java | 2 +- .../blocks/power/ItemLiquidGenerator.java | 32 ++++++++++++------- .../world/blocks/power/NuclearReactor.java | 23 ++++++++++--- .../world/blocks/production/LiquidMixer.java | 1 + .../world/blocks/production/PowerCrafter.java | 9 ++++++ .../mindustry/desktop/DesktopPlatform.java | 4 +-- 16 files changed, 71 insertions(+), 30 deletions(-) diff --git a/.travis.yml b/.travis.yml index 06a66fa107..5e781b0f7a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,13 +5,13 @@ jdk: android: components: - - android-26 + - android-27 # Additional components - extra-google-google_play_services - extra-google-m2repository - extra-android-m2repository - - addon-google_apis-google-26 + - addon-google_apis-google-27 script: - ./gradlew desktop:dist diff --git a/core/src/io/anuke/mindustry/content/Liquids.java b/core/src/io/anuke/mindustry/content/Liquids.java index 86adf3cf85..b29705bb32 100644 --- a/core/src/io/anuke/mindustry/content/Liquids.java +++ b/core/src/io/anuke/mindustry/content/Liquids.java @@ -41,8 +41,8 @@ public class Liquids implements ContentList { cryofluid = new Liquid("cryofluid", Color.SKY) { { - heatCapacity = 0.75f; - temperature = 0.4f; + heatCapacity = 0.9f; + temperature = 0.25f; tier = 1; effect = StatusEffects.freezing; } diff --git a/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java b/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java index ac9ce03f19..dd97e4a8b9 100644 --- a/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java @@ -121,7 +121,7 @@ public class CraftingBlocks extends BlockList implements ContentList { consumes.power(0.1f); consumes.item(Items.titanium); - consumes.liquid(Liquids.water, 0.2f); + consumes.liquid(Liquids.water, 0.3f); }}; blastMixer = new GenericCrafter("blast-mixer") {{ diff --git a/core/src/io/anuke/mindustry/content/blocks/LiquidBlocks.java b/core/src/io/anuke/mindustry/content/blocks/LiquidBlocks.java index c6330d2e09..621fca803a 100644 --- a/core/src/io/anuke/mindustry/content/blocks/LiquidBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/LiquidBlocks.java @@ -22,6 +22,7 @@ public class LiquidBlocks extends BlockList implements ContentList{ pumpAmount = 0.25f; consumes.power(0.015f); liquidCapacity = 30f; + hasPower = true; size = 2; tier = 1; }}; diff --git a/core/src/io/anuke/mindustry/type/Liquid.java b/core/src/io/anuke/mindustry/type/Liquid.java index 85c65198aa..4672c529d4 100644 --- a/core/src/io/anuke/mindustry/type/Liquid.java +++ b/core/src/io/anuke/mindustry/type/Liquid.java @@ -10,9 +10,10 @@ import io.anuke.mindustry.ui.ContentDisplay; import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.scene.ui.layout.Table; import io.anuke.ucore.util.Bundles; +import io.anuke.ucore.util.ThreadArray; public class Liquid implements UnlockableContent{ - private static final Array liquids = new Array<>(); + private static final Array liquids = new ThreadArray<>(); public final Color color; public final String name; diff --git a/core/src/io/anuke/mindustry/world/BaseBlock.java b/core/src/io/anuke/mindustry/world/BaseBlock.java index 68dbda64a5..875de327d1 100644 --- a/core/src/io/anuke/mindustry/world/BaseBlock.java +++ b/core/src/io/anuke/mindustry/world/BaseBlock.java @@ -20,6 +20,7 @@ public abstract class BaseBlock { public boolean hasLiquids; public boolean hasPower; + public boolean outputsLiquid = false; public boolean singleLiquid = true; public int itemCapacity; diff --git a/core/src/io/anuke/mindustry/world/blocks/LiquidBlock.java b/core/src/io/anuke/mindustry/world/blocks/LiquidBlock.java index 9feefc53b8..11b98f66b3 100644 --- a/core/src/io/anuke/mindustry/world/blocks/LiquidBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/LiquidBlock.java @@ -16,6 +16,7 @@ public class LiquidBlock extends Block{ solid = true; hasLiquids = true; group = BlockGroup.liquids; + outputsLiquid = true; } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java index 7aa7c3eac9..99b8fbfafc 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java @@ -40,14 +40,14 @@ public class Conduit extends LiquidBlock { ConduitEntity entity = tile.entity(); entity.blendbits = 0; - if(blends(tile, 1) && blends(tile, 2)) { + if(blends(tile, 2) && blends(tile, 1) && blends(tile, 3)) { + entity.blendbits = 3; + }else if(blends(tile, 1) && blends(tile, 2)) { entity.blendbits = 2; }else if(blends(tile, 3) && blends(tile, 2)) { entity.blendbits = 4; }else if(blends(tile, 0)){ - if(blends(tile, 2) && blends(tile, 1) && blends(tile, 3)) { - entity.blendbits = 3; - }else if(blends(tile, 1) && blends(tile, 3)) { + if(blends(tile, 1) && blends(tile, 3)) { entity.blendbits = 6; }else if(blends(tile, 1)) { entity.blendbits = 5; @@ -63,7 +63,9 @@ public class Conduit extends LiquidBlock { private boolean blends(Tile tile, int direction){ Tile other = tile.getNearby(Mathf.mod(tile.getRotation() - direction, 4)); - if(other == null || !(other.block().hasLiquids)) return false; + if(other != null) other = other.target(); + + if(other == null || !(other.block().hasLiquids) || !(other.block().outputsLiquid)) return false; return (tile.getNearby(tile.getRotation()) == other) || (!other.block().rotate || other.getNearby(other.getRotation()) == tile); } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidBridge.java b/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidBridge.java index a16c0b6b7f..8562b0735c 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidBridge.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidBridge.java @@ -13,6 +13,7 @@ public class LiquidBridge extends ItemBridge { super(name); hasItems = false; hasLiquids = true; + outputsLiquid = true; } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidExtendingBridge.java b/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidExtendingBridge.java index d8fc185adc..5076806347 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidExtendingBridge.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidExtendingBridge.java @@ -13,6 +13,7 @@ public class LiquidExtendingBridge extends ExtendingItemBridge { super(name); hasItems = false; hasLiquids = true; + outputsLiquid = true; } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/power/ItemGenerator.java b/core/src/io/anuke/mindustry/world/blocks/power/ItemGenerator.java index e1973d64d2..872f03156e 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/ItemGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/ItemGenerator.java @@ -37,7 +37,7 @@ public abstract class ItemGenerator extends PowerGenerator { itemCapacity = 20; hasItems = true; - consumes.add(new ConsumeItemFilter(item -> getItemEfficiency(item) >= minItemEfficiency)).update(false); + consumes.add(new ConsumeItemFilter(item -> getItemEfficiency(item) >= minItemEfficiency)).update(false).optional(true); } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java b/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java index a5333f80be..9e1efada0d 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java @@ -23,31 +23,39 @@ public abstract class ItemLiquidGenerator extends ItemGenerator { hasLiquids = true; liquidCapacity = 10f; - consumes.add(new ConsumeLiquidFilter(liquid -> getLiquidEfficiency(liquid) >= minLiquidEfficiency, 0.001f, true)).update(false); + consumes.add(new ConsumeLiquidFilter(liquid -> getLiquidEfficiency(liquid) >= minLiquidEfficiency, 0.001f, true)).update(false).optional(true); } @Override public void update(Tile tile){ ItemGeneratorEntity entity = tile.entity(); + Liquid liquid = null; + for (Liquid other : Liquid.all()){ + if(entity.liquids.get(other) >= 0.001f && getLiquidEfficiency(other) >= minLiquidEfficiency){ + liquid = other; + break; + } + } + //liquid takes priority over solids - if(entity.liquids.currentAmount() >= 0.001f && entity.cons.valid()){ - float powerPerLiquid = getLiquidEfficiency(entity.liquids.current())*this.powerPerLiquid; - float used = Math.min(entity.liquids.currentAmount(), maxLiquidGenerate * Timers.delta()); + if(liquid != null && entity.liquids.get(liquid) >= 0.001f && entity.cons.valid()){ + float powerPerLiquid = getLiquidEfficiency(liquid)*this.powerPerLiquid; + float used = Math.min(entity.liquids.get(liquid), maxLiquidGenerate * Timers.delta()); used = Math.min(used, (powerCapacity - entity.power.amount)/powerPerLiquid); - entity.liquids.remove(entity.liquids.current(), used); + entity.liquids.remove(liquid, used); entity.power.amount += used * powerPerLiquid; if(used > 0.001f && Mathf.chance(0.05 * Timers.delta())){ Effects.effect(generateEffect, tile.drawx() + Mathf.range(3f), tile.drawy() + Mathf.range(3f)); } - }else { + }else if (entity.cons.valid()){ float maxPower = Math.min(powerCapacity - entity.power.amount, powerOutput * Timers.delta()) * entity.efficiency; float mfract = maxPower / (powerOutput); - if (entity.generateTime > 0f && entity.cons.valid()) { + if (entity.generateTime > 0f) { entity.generateTime -= 1f / itemDuration * mfract; entity.power.amount += maxPower; entity.generateTime = Mathf.clamp(entity.generateTime); @@ -82,14 +90,14 @@ public abstract class ItemLiquidGenerator extends ItemGenerator { Draw.color(); } + @Override + public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){ + return getLiquidEfficiency(liquid) >= minLiquidEfficiency && tile.entity.liquids.get(liquid) < liquidCapacity; + } + public void drawLiquidCenter(Tile tile){ Draw.rect("blank", tile.drawx(), tile.drawy(), 2, 2); } - @Override - public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){ - return getLiquidEfficiency(liquid) >= minLiquidEfficiency && super.acceptLiquid(tile, source, liquid, amount); - } - protected abstract float getLiquidEfficiency(Liquid liquid); } diff --git a/core/src/io/anuke/mindustry/world/blocks/power/NuclearReactor.java b/core/src/io/anuke/mindustry/world/blocks/power/NuclearReactor.java index c5acb2c935..dae8e33dae 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/NuclearReactor.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/NuclearReactor.java @@ -1,6 +1,7 @@ package io.anuke.mindustry.world.blocks.power; import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.TextureRegion; import io.anuke.mindustry.content.Items; import io.anuke.mindustry.content.fx.BlockFx; import io.anuke.mindustry.content.fx.ExplosionFx; @@ -35,14 +36,16 @@ public class NuclearReactor extends PowerGenerator { protected Color hotColor = Color.valueOf("ff9575a3"); protected int fuelUseTime = 120; //time to consume 1 fuel protected float powerMultiplier = 0.45f; //power per frame, depends on full capacity - protected float heating = 0.009f; //heating per frame + protected float heating = 0.013f; //heating per frame protected float coolantPower = 0.015f; //how much heat decreases per coolant unit protected float smokeThreshold = 0.3f; //threshold at which block starts smoking - protected float maxLiquidUse = 4f; //max liquid use per frame + protected float maxLiquidUse = 2f; //max liquid use per frame protected int explosionRadius = 19; protected int explosionDamage = 135; protected float flashThreshold = 0.46f; //heat threshold at which the lights start flashing + protected TextureRegion topRegion, lightsRegion; + public NuclearReactor(String name) { super(name); itemCapacity = 30; @@ -54,6 +57,14 @@ public class NuclearReactor extends PowerGenerator { consumes.item(Items.thorium); } + @Override + public void load() { + super.load(); + + topRegion = Draw.region(name + "-center"); + lightsRegion = Draw.region(name + "-lights"); + } + @Override public void setBars(){ super.setBars(); @@ -88,7 +99,7 @@ public class NuclearReactor extends PowerGenerator { Liquid liquid = entity.liquids.current(); if(liquid.temperature <= 0.5f){ //is coolant - float pow = coolantPower * liquid.heatCapacity; //heat depleted per unit of liquid + float pow = coolantPower * (liquid.heatCapacity + 0.5f/liquid.temperature); //heat depleted per unit of liquid float maxUsed = Math.min(Math.min(entity.liquids.get(liquid), entity.heat / pow), maxLiquidUse * Timers.delta()); //max that can be cooled in terms of liquid entity.heat -= maxUsed * pow; entity.liquids.remove(liquid, maxUsed); @@ -167,13 +178,17 @@ public class NuclearReactor extends PowerGenerator { Draw.color(coolColor, hotColor, entity.heat); Draw.rect("white", tile.drawx(), tile.drawy(), size * tilesize, size * tilesize); + + Draw.color(entity.liquids.current().color); + Draw.alpha(entity.liquids.currentAmount() / liquidCapacity); + Draw.rect(topRegion, tile.drawx(), tile.drawy()); if(entity.heat > flashThreshold){ float flash = 1f + ((entity.heat - flashThreshold) / (1f - flashThreshold)) * 5.4f; entity.flash += flash * Timers.delta(); Draw.color(Color.RED, Color.YELLOW, Mathf.absin(entity.flash, 9f, 1f)); Draw.alpha(0.6f); - Draw.rect(name + "-lights", tile.drawx(), tile.drawy()); + Draw.rect(lightsRegion, tile.drawx(), tile.drawy()); } Draw.reset(); diff --git a/core/src/io/anuke/mindustry/world/blocks/production/LiquidMixer.java b/core/src/io/anuke/mindustry/world/blocks/production/LiquidMixer.java index 36d6b9c0ce..e99fc47c36 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/LiquidMixer.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/LiquidMixer.java @@ -21,6 +21,7 @@ public class LiquidMixer extends LiquidBlock{ hasItems = true; rotate = false; solid = true; + outputsLiquid = true; } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/production/PowerCrafter.java b/core/src/io/anuke/mindustry/world/blocks/production/PowerCrafter.java index bda67dc758..18639a72e1 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/PowerCrafter.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/PowerCrafter.java @@ -27,6 +27,15 @@ public class PowerCrafter extends Block{ hasItems = true; } + @Override + public void init() { + super.init(); + + if(outputLiquid != null){ + outputsLiquid = true; + } + } + @Override public void setStats() { super.setStats(); diff --git a/desktop/src/io/anuke/mindustry/desktop/DesktopPlatform.java b/desktop/src/io/anuke/mindustry/desktop/DesktopPlatform.java index 0f2c27078d..b0c2cc8a74 100644 --- a/desktop/src/io/anuke/mindustry/desktop/DesktopPlatform.java +++ b/desktop/src/io/anuke/mindustry/desktop/DesktopPlatform.java @@ -36,7 +36,7 @@ public class DesktopPlatform extends Platform { public DesktopPlatform(String[] args){ this.args = args; - Vars.testMobile = Array.with(args).contains("-testMobile", false); + Vars.testMobile = isDebug() && Array.with(args).contains("-testMobile", false); if(useDiscord) { DiscordEventHandlers handlers = new DiscordEventHandlers(); @@ -106,7 +106,7 @@ public class DesktopPlatform extends Platform { @Override public boolean isDebug() { - return args.length > 0 && args[0].equalsIgnoreCase("-debug"); + return args.length > 0 && args[0].equalsIgnoreCase("-debug_" + getUUID().hashCode()); } @Override From b5e8e541073cf4cfbaedf97c3389eed2d4d2f21f Mon Sep 17 00:00:00 2001 From: Anuken Date: Thu, 12 Jul 2018 10:24:56 -0400 Subject: [PATCH 31/47] Added build tools to travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 5e781b0f7a..56ba77b664 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,7 @@ android: - extra-google-m2repository - extra-android-m2repository - addon-google_apis-google-27 + - build-tools-27.0.3 script: - ./gradlew desktop:dist From 1f18e7beedee91b994d74e9a7a0f2e68e9ac1704 Mon Sep 17 00:00:00 2001 From: Anuken Date: Thu, 12 Jul 2018 11:38:09 -0400 Subject: [PATCH 32/47] Improved glitchy interpolation / Updated uCore --- build.gradle | 2 +- core/src/io/anuke/mindustry/core/NetClient.java | 3 ++- .../io/anuke/mindustry/core/ThreadHandler.java | 15 +++++++-------- .../src/io/anuke/mindustry/entities/Player.java | 17 +++++++++-------- .../io/anuke/mindustry/net/Interpolator.java | 3 ++- 5 files changed, 21 insertions(+), 19 deletions(-) diff --git a/build.gradle b/build.gradle index 0ac560c982..63cf126d57 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ allprojects { gdxVersion = '1.9.8' roboVMVersion = '2.3.0' aiVersion = '1.8.1' - uCoreVersion = '7673041e62' + uCoreVersion = '05f51b183e' getVersionString = { String buildVersion = getBuildVersion() diff --git a/core/src/io/anuke/mindustry/core/NetClient.java b/core/src/io/anuke/mindustry/core/NetClient.java index 3b2110156b..5b820adf88 100644 --- a/core/src/io/anuke/mindustry/core/NetClient.java +++ b/core/src/io/anuke/mindustry/core/NetClient.java @@ -1,5 +1,6 @@ package io.anuke.mindustry.core; +import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.utils.Base64Coder; import com.badlogic.gdx.utils.IntSet; @@ -175,7 +176,7 @@ public class NetClient extends Module { ui.loadfrag.hide(); ui.join.hide(); Net.setClientLoaded(true); - Call.connectConfirm(); + Gdx.app.postRunnable(Call::connectConfirm); Timers.runTask(40f, Platform.instance::updateRPC); } diff --git a/core/src/io/anuke/mindustry/core/ThreadHandler.java b/core/src/io/anuke/mindustry/core/ThreadHandler.java index e87203a1a9..c90a32131c 100644 --- a/core/src/io/anuke/mindustry/core/ThreadHandler.java +++ b/core/src/io/anuke/mindustry/core/ThreadHandler.java @@ -1,7 +1,7 @@ package io.anuke.mindustry.core; import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.Queue; import com.badlogic.gdx.utils.TimeUtils; import io.anuke.ucore.core.Timers; import io.anuke.ucore.util.Log; @@ -10,7 +10,7 @@ import static io.anuke.mindustry.Vars.control; import static io.anuke.mindustry.Vars.logic; public class ThreadHandler { - private final Array toRun = new Array<>(); + private final Queue toRun = new Queue<>(); private final ThreadProvider impl; private float delta = 1f; private float smoothDelta = 1f; @@ -33,7 +33,7 @@ public class ThreadHandler { public void run(Runnable r){ if(enabled) { synchronized (toRun) { - toRun.add(r); + toRun.addLast(r); } }else{ r.run(); @@ -51,7 +51,7 @@ public class ThreadHandler { public void runDelay(Runnable r){ if(enabled) { synchronized (toRun) { - toRun.add(r); + toRun.addLast(r); } }else{ Gdx.app.postRunnable(r); @@ -103,7 +103,7 @@ public class ThreadHandler { } public boolean doInterpolate(){ - return enabled && Math.abs(Gdx.graphics.getFramesPerSecond() - getTPS()) > 15; + return enabled && Gdx.graphics.getFramesPerSecond() - getTPS() > 20 && getTPS() < 30; } public boolean isOnThread(){ @@ -119,13 +119,12 @@ public class ThreadHandler { Runnable r; synchronized (toRun){ if(toRun.size > 0){ - r = toRun.pop(); + r = toRun.removeFirst(); }else{ - r = null; + break; } } - if(r == null) break; r.run(); } diff --git a/core/src/io/anuke/mindustry/entities/Player.java b/core/src/io/anuke/mindustry/entities/Player.java index 2775aa3438..064377e7d3 100644 --- a/core/src/io/anuke/mindustry/entities/Player.java +++ b/core/src/io/anuke/mindustry/entities/Player.java @@ -542,7 +542,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra pointerY = vec.y; updateShooting(); - movement.limit(speed); + movement.limit(speed * Timers.delta()); if(getCarrier() == null){ velocity.add(movement); @@ -751,13 +751,12 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra public void write(DataOutput buffer) throws IOException { super.writeSave(buffer, !isLocal); buffer.writeUTF(name); //TODO writing strings is very inefficient - buffer.writeBoolean(isAdmin); + buffer.writeByte(Bits.toByte(isAdmin) | (Bits.toByte(dead) << 1) | (Bits.toByte(isBoosting) << 2)); buffer.writeInt(Color.rgba8888(color)); - buffer.writeBoolean(dead); buffer.writeByte(mech.id); - buffer.writeBoolean(isBoosting); buffer.writeInt(mining == null ? -1 : mining.packedPosition()); buffer.writeInt(spawner); + buffer.writeShort((short)(baseRotation * 2)); writeBuilding(buffer); } @@ -767,17 +766,19 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra float lastx = x, lasty = y, lastrot = rotation; super.readSave(buffer); name = buffer.readUTF(); - isAdmin = buffer.readBoolean(); + byte bools = buffer.readByte(); + isAdmin = (bools & 1) != 0; + dead = (bools & 2) != 0; + boolean boosting = (bools & 4) != 0; color.set(buffer.readInt()); - dead = buffer.readBoolean(); mech = Upgrade.getByID(buffer.readByte()); - boolean boosting = buffer.readBoolean(); int mine = buffer.readInt(); spawner = buffer.readInt(); + float baseRotation = buffer.readShort()/2f; readBuilding(buffer, !isLocal); - interpolator.read(lastx, lasty, x, y, time, rotation); + interpolator.read(lastx, lasty, x, y, time, rotation, baseRotation); rotation = lastrot; if(isLocal){ diff --git a/core/src/io/anuke/mindustry/net/Interpolator.java b/core/src/io/anuke/mindustry/net/Interpolator.java index 1c0d4735d6..ed983bbbf0 100644 --- a/core/src/io/anuke/mindustry/net/Interpolator.java +++ b/core/src/io/anuke/mindustry/net/Interpolator.java @@ -38,11 +38,12 @@ public class Interpolator { public void update(){ + /* if(pos.dst(target) > 128){ pos.set(target); lastUpdated = 0; updateSpacing = 16; - } + }*/ if(lastUpdated != 0 && updateSpacing != 0){ float timeSinceUpdate = TimeUtils.timeSinceMillis(lastUpdated); From 8e9adeb4b44b195317c11cf0fe8a2e3a5d789624 Mon Sep 17 00:00:00 2001 From: Anuken Date: Thu, 12 Jul 2018 14:06:50 -0400 Subject: [PATCH 33/47] Fixed web version bugs --- build.gradle | 2 +- .../io/anuke/mindustry/graphics/MinimapRenderer.java | 1 - core/src/io/anuke/mindustry/io/Maps.java | 4 ++-- .../io/anuke/mindustry/world/blocks/BuildBlock.java | 2 +- html/src/io/anuke/mindustry/client/HtmlLauncher.java | 10 ++++++++++ 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index 63cf126d57..8939f41ed4 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ allprojects { gdxVersion = '1.9.8' roboVMVersion = '2.3.0' aiVersion = '1.8.1' - uCoreVersion = '05f51b183e' + uCoreVersion = 'c502931313' getVersionString = { String buildVersion = getBuildVersion() diff --git a/core/src/io/anuke/mindustry/graphics/MinimapRenderer.java b/core/src/io/anuke/mindustry/graphics/MinimapRenderer.java index c3a13df75e..096c4455a7 100644 --- a/core/src/io/anuke/mindustry/graphics/MinimapRenderer.java +++ b/core/src/io/anuke/mindustry/graphics/MinimapRenderer.java @@ -121,7 +121,6 @@ public class MinimapRenderer implements Disposable{ int color = colorFor(world.tile(tile.x, tile.y)); pixmap.drawPixel(tile.x, pixmap.getHeight() - 1 - tile.y, color); - texture.bind(); Pixmaps.drawPixel(texture, tile.x, pixmap.getHeight() - 1 - tile.y, color); } diff --git a/core/src/io/anuke/mindustry/io/Maps.java b/core/src/io/anuke/mindustry/io/Maps.java index e8aa937786..5ea9e0bf38 100644 --- a/core/src/io/anuke/mindustry/io/Maps.java +++ b/core/src/io/anuke/mindustry/io/Maps.java @@ -166,7 +166,7 @@ public class Maps implements Disposable{ for(String name : customMapNames){ try{ - String data = Settings.getString("map-data-" + name); + String data = Settings.getString("map-data-" + name, ""); byte[] bytes = Base64Coder.decode(data); loadMap(name, () -> new ByteArrayInputStream(bytes), true); }catch (Exception e){ @@ -182,7 +182,7 @@ public class Maps implements Disposable{ if(!gwt){ return customMapDirectory.child(name + "." + mapExtension)::read; }else{ - String data = Settings.getString("map-data-" + name); + String data = Settings.getString("map-data-" + name, ""); byte[] bytes = Base64Coder.decode(data); return () -> new ByteArrayInputStream(bytes); } diff --git a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java index 3e30dbedc4..810049b4b6 100644 --- a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java @@ -70,7 +70,7 @@ public class BuildBlock extends Block { @Override public void setBars(){ - bars.replace(new BlockBar(BarType.health, true, tile -> (float)tile.entity().progress)); + bars.replace(new BlockBar(BarType.health, true, tile -> tile.entity().progress)); } @Override diff --git a/html/src/io/anuke/mindustry/client/HtmlLauncher.java b/html/src/io/anuke/mindustry/client/HtmlLauncher.java index 35b1ea0748..208e24b976 100644 --- a/html/src/io/anuke/mindustry/client/HtmlLauncher.java +++ b/html/src/io/anuke/mindustry/client/HtmlLauncher.java @@ -218,6 +218,16 @@ public class HtmlLauncher extends GwtApplication { public InputStream read() { return stream; } + + @Override + public String nameWithoutExtension() { + return "unknown"; + } + + @Override + public String name() { + return "unknown"; + } }); } } From 454267455beaf11da98684b5fb9859a4bb98fcef Mon Sep 17 00:00:00 2001 From: Anuken Date: Thu, 12 Jul 2018 15:44:05 -0400 Subject: [PATCH 34/47] Fixed mobile consume frag / Added ability for ship to mine --- core/src/io/anuke/mindustry/content/Mechs.java | 2 +- .../io/anuke/mindustry/ui/fragments/BlockConsumeFragment.java | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/io/anuke/mindustry/content/Mechs.java b/core/src/io/anuke/mindustry/content/Mechs.java index 27097704dc..b141eabd9b 100644 --- a/core/src/io/anuke/mindustry/content/Mechs.java +++ b/core/src/io/anuke/mindustry/content/Mechs.java @@ -48,7 +48,7 @@ public class Mechs implements ContentList { }}; dart = new Mech("dart-ship", true){{ - drillPower = -1; + drillPower = 1; speed = 0.4f; maxSpeed = 3f; drag = 0.1f; diff --git a/core/src/io/anuke/mindustry/ui/fragments/BlockConsumeFragment.java b/core/src/io/anuke/mindustry/ui/fragments/BlockConsumeFragment.java index a2726f483d..0934f8fb4c 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/BlockConsumeFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/BlockConsumeFragment.java @@ -103,7 +103,9 @@ public class BlockConsumeFragment extends Fragment { }).size(10*scale).get(); result.hovered(() -> hovered[0] = true); - result.exited(() -> hovered[0] = false); + if(!mobile){ + result.exited(() -> hovered[0] = false); + } table.row(); } From 3d536aa28dafca4bde1a5c616eec8502214dfb65 Mon Sep 17 00:00:00 2001 From: Anuken Date: Thu, 12 Jul 2018 18:58:02 -0400 Subject: [PATCH 35/47] Fixed player names not displaying --- core/src/io/anuke/mindustry/core/Renderer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/io/anuke/mindustry/core/Renderer.java b/core/src/io/anuke/mindustry/core/Renderer.java index dce53892cf..095fac27d5 100644 --- a/core/src/io/anuke/mindustry/core/Renderer.java +++ b/core/src/io/anuke/mindustry/core/Renderer.java @@ -245,11 +245,11 @@ public class Renderer extends RendererModule{ fog.draw(); } - batch.begin(); + Graphics.beginCam(); EntityDraw.setClip(false); drawAndInterpolate(playerGroup, p -> !p.isDead() && !p.isLocal, Player::drawName); EntityDraw.setClip(true); - batch.end(); + Graphics.end(); } private void drawFlyerShadows(){ From a3bda3a94140a0e17f09b160f9050a2038e63d54 Mon Sep 17 00:00:00 2001 From: Anuken Date: Thu, 12 Jul 2018 19:00:02 -0400 Subject: [PATCH 36/47] Changed crash report location --- desktop/src/io/anuke/mindustry/desktop/CrashHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java b/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java index 64fcf2da36..af32d6f065 100644 --- a/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java +++ b/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java @@ -54,7 +54,7 @@ public class CrashHandler { //try to write it try{ filename = "crash-report-" + new SimpleDateFormat("dd-MM-yy h.mm.ss").format(new Date()) + ".txt"; - Files.write(Paths.get(filename), result.getBytes()); + Files.write(Paths.get(System.getProperty("user.home"), "mindustry-crash-reports", filename), result.getBytes()); }catch (Throwable i){ i.printStackTrace(); failed = true; From baaeb229cf97205d73fe9fb93c15198e9a5d6194 Mon Sep 17 00:00:00 2001 From: Anuken Date: Thu, 12 Jul 2018 20:37:14 -0400 Subject: [PATCH 37/47] Formatting --- .../io/anuke/mindustry/AndroidLauncher.java | 374 ++--- .../mindustry/AndroidTextFieldDialog.java | 125 +- .../io/anuke/mindustry/DonationsActivity.java | 26 +- .../mindustry/TextFieldDialogListener.java | 80 +- .../src/io/anuke/annotations/Annotations.java | 124 +- .../src/io/anuke/annotations/ClassEntry.java | 10 +- .../src/io/anuke/annotations/IOFinder.java | 32 +- .../src/io/anuke/annotations/MethodEntry.java | 32 +- .../RemoteMethodAnnotationProcessor.java | 42 +- .../annotations/RemoteReadGenerator.java | 33 +- .../annotations/RemoteWriteGenerator.java | 40 +- .../src/io/anuke/annotations/Utils.java | 4 +- core/src/Mindustry.gwt.xml | 38 +- core/src/io/anuke/mindustry/Mindustry.java | 44 +- core/src/io/anuke/mindustry/Vars.java | 277 ++-- .../io/anuke/mindustry/ai/BlockIndexer.java | 131 +- .../src/io/anuke/mindustry/ai/Pathfinder.java | 30 +- .../io/anuke/mindustry/ai/WaveSpawner.java | 56 +- .../io/anuke/mindustry/content/AmmoTypes.java | 58 +- .../src/io/anuke/mindustry/content/Items.java | 34 +- .../io/anuke/mindustry/content/Liquids.java | 14 +- .../src/io/anuke/mindustry/content/Mechs.java | 10 +- .../io/anuke/mindustry/content/Recipes.java | 13 +- .../mindustry/content/StatusEffects.java | 58 +- .../io/anuke/mindustry/content/UnitTypes.java | 6 +- .../io/anuke/mindustry/content/Weapons.java | 24 +- .../mindustry/content/blocks/BlockList.java | 4 +- .../mindustry/content/blocks/Blocks.java | 45 +- .../content/blocks/CraftingBlocks.java | 38 +- .../mindustry/content/blocks/DebugBlocks.java | 55 +- .../content/blocks/DefenseBlocks.java | 34 +- .../content/blocks/DistributionBlocks.java | 70 +- .../content/blocks/LiquidBlocks.java | 20 +- .../mindustry/content/blocks/OreBlocks.java | 17 +- .../mindustry/content/blocks/PowerBlocks.java | 28 +- .../content/blocks/ProductionBlocks.java | 20 +- .../content/blocks/StorageBlocks.java | 12 +- .../content/blocks/TurretBlocks.java | 291 ++-- .../mindustry/content/blocks/UnitBlocks.java | 20 +- .../content/blocks/UpgradeBlocks.java | 4 +- .../content/bullets/ArtilleryBullets.java | 16 +- .../mindustry/content/bullets/BulletList.java | 4 +- .../content/bullets/FlakBullets.java | 12 +- .../content/bullets/MissileBullets.java | 12 +- .../content/bullets/StandardBullets.java | 16 +- .../content/bullets/TurretBullets.java | 66 +- .../content/bullets/WeaponBullets.java | 16 +- .../anuke/mindustry/content/fx/BlockFx.java | 2 +- .../anuke/mindustry/content/fx/BulletFx.java | 4 +- .../mindustry/content/fx/EnvironmentFx.java | 4 +- .../mindustry/content/fx/ExplosionFx.java | 4 +- .../src/io/anuke/mindustry/content/fx/Fx.java | 92 +- .../io/anuke/mindustry/content/fx/FxList.java | 2 +- .../anuke/mindustry/content/fx/ShootFx.java | 14 +- .../io/anuke/mindustry/content/fx/UnitFx.java | 4 +- .../anuke/mindustry/core/ContentLoader.java | 120 +- core/src/io/anuke/mindustry/core/Control.java | 480 +++--- .../io/anuke/mindustry/core/GameState.java | 49 +- core/src/io/anuke/mindustry/core/Logic.java | 25 +- .../io/anuke/mindustry/core/NetClient.java | 375 ++--- .../io/anuke/mindustry/core/NetServer.java | 229 +-- .../src/io/anuke/mindustry/core/Platform.java | 242 ++- .../src/io/anuke/mindustry/core/Renderer.java | 634 ++++---- .../anuke/mindustry/core/ThreadHandler.java | 54 +- core/src/io/anuke/mindustry/core/UI.java | 435 +++--- core/src/io/anuke/mindustry/core/World.java | 562 +++---- .../anuke/mindustry/editor/DrawOperation.java | 100 +- .../io/anuke/mindustry/editor/EditorTool.java | 218 +-- .../io/anuke/mindustry/editor/MapEditor.java | 422 +++--- .../mindustry/editor/MapEditorDialog.java | 1011 ++++++------- .../anuke/mindustry/editor/MapLoadDialog.java | 98 +- .../anuke/mindustry/editor/MapRenderer.java | 52 +- .../mindustry/editor/MapResizeDialog.java | 102 +- .../anuke/mindustry/editor/MapSaveDialog.java | 122 +- .../io/anuke/mindustry/editor/MapView.java | 612 ++++---- .../mindustry/editor/OperationStack.java | 78 +- .../io/anuke/mindustry/entities/Damage.java | 260 ++-- .../io/anuke/mindustry/entities/Player.java | 1338 +++++++++-------- .../io/anuke/mindustry/entities/Predict.java | 55 +- .../mindustry/entities/StatusController.java | 24 +- .../anuke/mindustry/entities/TileEntity.java | 350 ++--- .../src/io/anuke/mindustry/entities/Unit.java | 96 +- .../mindustry/entities/UnitInventory.java | 33 +- .../io/anuke/mindustry/entities/Units.java | 116 +- .../entities/bullet/ArtilleryBulletType.java | 12 +- .../entities/bullet/BasicBulletType.java | 28 +- .../entities/bullet/BombBulletType.java | 4 +- .../mindustry/entities/bullet/Bullet.java | 309 ++-- .../mindustry/entities/bullet/BulletType.java | 124 +- .../entities/bullet/LiquidBulletType.java | 12 +- .../entities/bullet/MissileBulletType.java | 6 +- .../mindustry/entities/effect/Decal.java | 12 +- .../anuke/mindustry/entities/effect/Fire.java | 63 +- .../entities/effect/GroundEffectEntity.java | 38 +- .../mindustry/entities/effect/ItemDrop.java | 64 +- .../entities/effect/ItemTransfer.java | 38 +- .../mindustry/entities/effect/Lightning.java | 47 +- .../mindustry/entities/effect/Puddle.java | 107 +- .../entities/effect/RubbleDecal.java | 6 +- .../entities/effect/ScorchDecal.java | 14 +- .../mindustry/entities/effect/Shield.java | 139 +- .../entities/traits/BelowLiquidTrait.java | 6 +- .../entities/traits/BuilderTrait.java | 138 +- .../entities/traits/CarriableTrait.java | 3 +- .../mindustry/entities/traits/CarryTrait.java | 56 +- .../entities/traits/InventoryTrait.java | 2 +- .../entities/traits/RepairTrait.java | 2 +- .../mindustry/entities/traits/SaveTrait.java | 4 +- .../mindustry/entities/traits/Saveable.java | 3 +- .../entities/traits/ShooterTrait.java | 2 + .../entities/traits/SpawnerTrait.java | 4 +- .../mindustry/entities/traits/SyncTrait.java | 28 +- .../entities/traits/TargetTrait.java | 13 +- .../mindustry/entities/traits/TeamTrait.java | 2 +- .../mindustry/entities/traits/TypeTrait.java | 21 +- .../mindustry/entities/units/BaseUnit.java | 737 ++++----- .../mindustry/entities/units/FlyingUnit.java | 208 ++- .../mindustry/entities/units/GroundUnit.java | 205 ++- .../anuke/mindustry/entities/units/Squad.java | 8 +- .../entities/units/StateMachine.java | 4 +- .../mindustry/entities/units/UnitDrops.java | 4 +- .../mindustry/entities/units/UnitState.java | 13 +- .../mindustry/entities/units/UnitType.java | 37 +- .../mindustry/entities/units/types/Drone.java | 605 ++++---- .../entities/units/types/Fabricator.java | 2 +- .../entities/units/types/Monsoon.java | 2 +- .../mindustry/entities/units/types/Scout.java | 2 +- .../mindustry/entities/units/types/Vtol.java | 2 +- core/src/io/anuke/mindustry/game/Content.java | 32 +- .../anuke/mindustry/game/ContentDatabase.java | 29 +- .../io/anuke/mindustry/game/Difficulty.java | 18 +- .../io/anuke/mindustry/game/EventType.java | 18 +- .../src/io/anuke/mindustry/game/GameMode.java | 22 +- .../io/anuke/mindustry/game/SpawnGroup.java | 174 ++- core/src/io/anuke/mindustry/game/Team.java | 5 +- .../src/io/anuke/mindustry/game/TeamInfo.java | 69 +- .../mindustry/game/UnlockableContent.java | 37 +- .../io/anuke/mindustry/game/WaveCreator.java | 314 ++-- .../mindustry/graphics/BlockRenderer.java | 328 ++-- .../anuke/mindustry/graphics/CacheLayer.java | 6 +- .../mindustry/graphics/FloorRenderer.java | 104 +- .../anuke/mindustry/graphics/FogRenderer.java | 18 +- .../io/anuke/mindustry/graphics/Layer.java | 34 +- .../mindustry/graphics/MinimapRenderer.java | 30 +- .../mindustry/graphics/OverlayRenderer.java | 47 +- .../io/anuke/mindustry/graphics/Palette.java | 2 +- .../io/anuke/mindustry/graphics/Shaders.java | 244 +-- .../io/anuke/mindustry/graphics/Trail.java | 12 +- .../io/anuke/mindustry/input/CursorType.java | 10 +- .../mindustry/input/DefaultKeybinds.java | 86 +- .../anuke/mindustry/input/DesktopInput.java | 141 +- .../anuke/mindustry/input/InputHandler.java | 508 ++++--- .../io/anuke/mindustry/input/MobileInput.java | 266 ++-- .../io/anuke/mindustry/input/PlaceUtils.java | 40 +- .../io/anuke/mindustry/io/BundleLoader.java | 10 +- .../src/io/anuke/mindustry/io/Changelogs.java | 6 +- core/src/io/anuke/mindustry/io/Map.java | 22 +- core/src/io/anuke/mindustry/io/MapIO.java | 48 +- core/src/io/anuke/mindustry/io/MapMeta.java | 4 +- .../io/anuke/mindustry/io/MapTileData.java | 36 +- core/src/io/anuke/mindustry/io/Maps.java | 270 ++-- .../anuke/mindustry/io/SaveFileVersion.java | 3 +- core/src/io/anuke/mindustry/io/SaveIO.java | 258 ++-- core/src/io/anuke/mindustry/io/SaveMeta.java | 2 +- core/src/io/anuke/mindustry/io/Saves.java | 28 +- core/src/io/anuke/mindustry/io/TypeIO.java | 62 +- core/src/io/anuke/mindustry/io/Version.java | 6 +- .../anuke/mindustry/io/versions/Save16.java | 46 +- .../anuke/mindustry/net/Administration.java | 115 +- core/src/io/anuke/mindustry/net/EditLog.java | 30 +- core/src/io/anuke/mindustry/net/Host.java | 2 +- core/src/io/anuke/mindustry/net/In.java | 6 +- .../io/anuke/mindustry/net/Interpolator.java | 4 +- core/src/io/anuke/mindustry/net/Net.java | 590 +++++--- .../io/anuke/mindustry/net/NetConnection.java | 21 +- .../src/io/anuke/mindustry/net/NetEvents.java | 2 +- .../src/io/anuke/mindustry/net/NetworkIO.java | 46 +- core/src/io/anuke/mindustry/net/Packet.java | 10 +- core/src/io/anuke/mindustry/net/Packets.java | 114 +- .../io/anuke/mindustry/net/Registrator.java | 23 +- .../io/anuke/mindustry/net/Streamable.java | 16 +- .../src/io/anuke/mindustry/net/TraceInfo.java | 4 +- .../mindustry/net/ValidateException.java | 6 +- .../io/anuke/mindustry/type/AmmoEntry.java | 6 +- .../src/io/anuke/mindustry/type/AmmoType.java | 86 +- .../src/io/anuke/mindustry/type/Category.java | 4 +- .../io/anuke/mindustry/type/ContentList.java | 14 +- core/src/io/anuke/mindustry/type/Item.java | 160 +- .../io/anuke/mindustry/type/ItemStack.java | 20 +- .../src/io/anuke/mindustry/type/ItemType.java | 14 +- core/src/io/anuke/mindustry/type/Liquid.java | 156 +- core/src/io/anuke/mindustry/type/Mech.java | 86 +- core/src/io/anuke/mindustry/type/Recipe.java | 196 +-- .../io/anuke/mindustry/type/StatusEffect.java | 127 +- core/src/io/anuke/mindustry/type/Upgrade.java | 38 +- core/src/io/anuke/mindustry/type/Weapon.java | 293 ++-- .../io/anuke/mindustry/type/WeatherEvent.java | 22 +- .../io/anuke/mindustry/ui/BorderImage.java | 59 +- .../io/anuke/mindustry/ui/ContentDisplay.java | 26 +- core/src/io/anuke/mindustry/ui/GridImage.java | 59 +- core/src/io/anuke/mindustry/ui/IntFormat.java | 8 +- core/src/io/anuke/mindustry/ui/ItemImage.java | 6 +- core/src/io/anuke/mindustry/ui/Links.java | 20 +- .../src/io/anuke/mindustry/ui/MenuButton.java | 44 +- core/src/io/anuke/mindustry/ui/Minimap.java | 10 +- .../io/anuke/mindustry/ui/MobileButton.java | 4 +- .../mindustry/ui/dialogs/AboutDialog.java | 12 +- .../mindustry/ui/dialogs/AdminsDialog.java | 4 +- .../mindustry/ui/dialogs/BansDialog.java | 4 +- .../mindustry/ui/dialogs/ChangelogDialog.java | 2 +- .../mindustry/ui/dialogs/ColorPickDialog.java | 6 +- .../ui/dialogs/ContentInfoDialog.java | 2 +- .../mindustry/ui/dialogs/ControlsDialog.java | 40 +- .../mindustry/ui/dialogs/DiscordDialog.java | 6 +- .../mindustry/ui/dialogs/FileChooser.java | 551 ++++--- .../mindustry/ui/dialogs/FloatingDialog.java | 70 +- .../mindustry/ui/dialogs/HostDialog.java | 2 +- .../mindustry/ui/dialogs/JoinDialog.java | 44 +- .../mindustry/ui/dialogs/LevelDialog.java | 230 +-- .../mindustry/ui/dialogs/LoadDialog.java | 232 ++- .../ui/dialogs/LocalPlayerDialog.java | 10 +- .../mindustry/ui/dialogs/MapsDialog.java | 16 +- .../mindustry/ui/dialogs/PausedDialog.java | 212 +-- .../mindustry/ui/dialogs/RestartDialog.java | 6 +- .../mindustry/ui/dialogs/RollbackDialog.java | 61 +- .../mindustry/ui/dialogs/SaveDialog.java | 86 +- .../ui/dialogs/SettingsMenuDialog.java | 266 ++-- .../mindustry/ui/dialogs/TraceDialog.java | 2 +- .../mindustry/ui/dialogs/UnlocksDialog.java | 14 +- .../ui/fragments/BackgroundFragment.java | 18 +- .../ui/fragments/BlockConfigFragment.java | 11 +- .../ui/fragments/BlockConsumeFragment.java | 17 +- .../ui/fragments/BlockInventoryFragment.java | 59 +- .../ui/fragments/BlocksFragment.java | 653 ++++---- .../mindustry/ui/fragments/ChatFragment.java | 44 +- .../mindustry/ui/fragments/DebugFragment.java | 218 +-- .../mindustry/ui/fragments/Fragment.java | 2 +- .../mindustry/ui/fragments/HudFragment.java | 504 +++---- .../ui/fragments/LoadingFragment.java | 9 +- .../mindustry/ui/fragments/MenuFragment.java | 260 ++-- .../ui/fragments/OverlayFragment.java | 4 +- .../ui/fragments/PlayerListFragment.java | 12 +- .../src/io/anuke/mindustry/world/BarType.java | 2 +- .../io/anuke/mindustry/world/BaseBlock.java | 86 +- core/src/io/anuke/mindustry/world/Block.java | 830 +++++----- core/src/io/anuke/mindustry/world/Build.java | 76 +- .../io/anuke/mindustry/world/ColorMapper.java | 42 +- core/src/io/anuke/mindustry/world/Edges.java | 39 +- .../io/anuke/mindustry/world/ItemBuffer.java | 8 +- core/src/io/anuke/mindustry/world/Tile.java | 763 +++++----- .../mindustry/world/blocks/BlockPart.java | 122 +- .../mindustry/world/blocks/BuildBlock.java | 108 +- .../anuke/mindustry/world/blocks/Floor.java | 278 ++-- .../mindustry/world/blocks/LiquidBlock.java | 78 +- .../io/anuke/mindustry/world/blocks/Ore.java | 8 +- .../mindustry/world/blocks/OreBlock.java | 6 +- .../mindustry/world/blocks/PowerBlock.java | 16 +- .../io/anuke/mindustry/world/blocks/Rock.java | 20 +- .../world/blocks/SelectionTrait.java | 4 +- .../mindustry/world/blocks/StaticBlock.java | 8 +- .../io/anuke/mindustry/world/blocks/Wall.java | 20 +- .../world/blocks/defense/DeflectorWall.java | 14 +- .../mindustry/world/blocks/defense/Door.java | 142 +- .../world/blocks/defense/PhaseWall.java | 6 +- .../world/blocks/defense/ShieldBlock.java | 120 +- .../blocks/defense/ShieldedWallBlock.java | 120 +- .../defense/turrets/ArtilleryTurret.java | 12 +- .../blocks/defense/turrets/BurstTurret.java | 6 +- .../blocks/defense/turrets/CooledTurret.java | 18 +- .../blocks/defense/turrets/DoubleTurret.java | 6 +- .../blocks/defense/turrets/ItemTurret.java | 34 +- .../blocks/defense/turrets/LaserTurret.java | 6 +- .../blocks/defense/turrets/LiquidTurret.java | 18 +- .../blocks/defense/turrets/PowerTurret.java | 54 +- .../world/blocks/defense/turrets/Turret.java | 446 +++--- .../distribution/BufferedItemBridge.java | 12 +- .../world/blocks/distribution/Conduit.java | 36 +- .../world/blocks/distribution/Conveyor.java | 812 +++++----- .../distribution/ExtendingItemBridge.java | 32 +- .../world/blocks/distribution/ItemBridge.java | 100 +- .../world/blocks/distribution/Junction.java | 185 +-- .../blocks/distribution/LiquidBridge.java | 12 +- .../distribution/LiquidExtendingBridge.java | 12 +- .../blocks/distribution/LiquidJunction.java | 54 +- .../blocks/distribution/LiquidRouter.java | 22 +- .../world/blocks/distribution/MassDriver.java | 145 +- .../blocks/distribution/OverflowGate.java | 8 +- .../world/blocks/distribution/Router.java | 72 +- .../world/blocks/distribution/Sorter.java | 202 +-- .../world/blocks/distribution/Splitter.java | 4 +- .../blocks/distribution/TunnelConduit.java | 32 +- .../world/blocks/distribution/WarpGate.java | 465 +++--- .../world/blocks/effect/EffectCore.java | 4 +- .../world/blocks/effect/MendingCore.java | 14 +- .../world/blocks/logic/LogicBlock.java | 2 +- .../world/blocks/power/BurnerGenerator.java | 8 +- .../world/blocks/power/DecayGenerator.java | 6 +- .../world/blocks/power/FusionReactor.java | 32 +- .../world/blocks/power/ItemGenerator.java | 188 +-- .../blocks/power/ItemLiquidGenerator.java | 22 +- .../world/blocks/power/LiquidGenerator.java | 120 +- .../blocks/power/LiquidHeatGenerator.java | 6 +- .../world/blocks/power/NuclearReactor.java | 348 ++--- .../world/blocks/power/PowerDistributor.java | 12 +- .../world/blocks/power/PowerGenerator.java | 6 +- .../world/blocks/power/PowerNode.java | 439 +++--- .../world/blocks/power/SolarGenerator.java | 8 +- .../world/blocks/power/TurbineGenerator.java | 4 +- .../world/blocks/production/Compressor.java | 14 +- .../world/blocks/production/Cultivator.java | 32 +- .../world/blocks/production/Drill.java | 352 +++-- .../world/blocks/production/Fracker.java | 20 +- .../blocks/production/GenericCrafter.java | 178 +-- .../world/blocks/production/Incinerator.java | 22 +- .../world/blocks/production/LiquidMixer.java | 12 +- .../world/blocks/production/PhaseWeaver.java | 10 +- .../production/PlastaniumCompressor.java | 6 +- .../world/blocks/production/PowerCrafter.java | 20 +- .../world/blocks/production/PowerSmelter.java | 24 +- .../world/blocks/production/Pulverizer.java | 10 +- .../world/blocks/production/Pump.java | 164 +- .../world/blocks/production/Separator.java | 28 +- .../world/blocks/production/Smelter.java | 264 ++-- .../world/blocks/production/SolidPump.java | 22 +- .../world/blocks/storage/CoreBlock.java | 108 +- .../world/blocks/storage/SortedUnloader.java | 18 +- .../world/blocks/storage/StorageBlock.java | 14 +- .../world/blocks/storage/Unloader.java | 13 +- .../mindustry/world/blocks/storage/Vault.java | 12 +- .../world/blocks/units/CommandCenter.java | 4 +- .../world/blocks/units/DropPoint.java | 10 +- .../world/blocks/units/MechFactory.java | 105 +- .../blocks/units/OverdriveProjector.java | 4 +- .../world/blocks/units/Projector.java | 10 +- .../world/blocks/units/Reconstructor.java | 379 ++--- .../world/blocks/units/RepairPoint.java | 22 +- .../world/blocks/units/ResupplyPoint.java | 38 +- .../world/blocks/units/ShieldProjector.java | 4 +- .../world/blocks/units/UnitFactory.java | 86 +- .../world/blocks/units/UnloadPoint.java | 2 +- .../mindustry/world/consumers/Consume.java | 19 +- .../world/consumers/ConsumeItem.java | 22 +- .../world/consumers/ConsumeItemFilter.java | 18 +- .../world/consumers/ConsumeItems.java | 18 +- .../world/consumers/ConsumeLiquid.java | 22 +- .../world/consumers/ConsumeLiquidFilter.java | 20 +- .../world/consumers/ConsumePower.java | 14 +- .../mindustry/world/consumers/Consumers.java | 10 +- .../mindustry/world/mapgen/GenProperties.java | 62 +- .../world/mapgen/WorldGenerator.java | 380 ++--- .../anuke/mindustry/world/meta/BlockBar.java | 4 +- .../anuke/mindustry/world/meta/BlockBars.java | 6 +- .../anuke/mindustry/world/meta/BlockFlag.java | 26 +- .../mindustry/world/meta/BlockGroup.java | 2 +- .../anuke/mindustry/world/meta/BlockStat.java | 12 +- .../mindustry/world/meta/BlockStats.java | 44 +- .../world/meta/ContentStatValue.java | 2 +- .../mindustry/world/meta/StatCategory.java | 6 +- .../anuke/mindustry/world/meta/StatUnit.java | 6 +- .../anuke/mindustry/world/meta/StatValue.java | 12 +- .../world/meta/values/BooleanValue.java | 6 +- .../world/meta/values/ItemFilterValue.java | 10 +- .../world/meta/values/ItemListValue.java | 16 +- .../world/meta/values/ItemValue.java | 10 +- .../world/meta/values/LiquidFilterValue.java | 10 +- .../world/meta/values/LiquidValue.java | 10 +- .../world/meta/values/NumberValue.java | 12 +- .../world/meta/values/StringValue.java | 6 +- .../mindustry/world/modules/BlockModule.java | 3 +- .../world/modules/ConsumeModule.java | 4 +- .../world/modules/InventoryModule.java | 28 +- .../mindustry/world/modules/LiquidModule.java | 26 +- .../mindustry/world/modules/PowerModule.java | 2 +- .../anuke/mindustry/desktop/CrashHandler.java | 16 +- .../mindustry/desktop/DesktopLauncher.java | 42 +- .../mindustry/desktop/DesktopPlatform.java | 25 +- .../mindustry/server/MindustryServer.java | 2 +- .../anuke/mindustry/server/ServerControl.java | 156 +- .../mindustry/server/ServerLauncher.java | 49 +- 379 files changed, 17470 insertions(+), 16215 deletions(-) diff --git a/android/src/io/anuke/mindustry/AndroidLauncher.java b/android/src/io/anuke/mindustry/AndroidLauncher.java index 774e01ae69..d532734999 100644 --- a/android/src/io/anuke/mindustry/AndroidLauncher.java +++ b/android/src/io/anuke/mindustry/AndroidLauncher.java @@ -49,237 +49,237 @@ import java.util.Locale; import static io.anuke.mindustry.Vars.*; public class AndroidLauncher extends AndroidApplication{ - public static final int PERMISSION_REQUEST_CODE = 1; + public static final int PERMISSION_REQUEST_CODE = 1; - boolean doubleScaleTablets = true; - FileChooser chooser; + boolean doubleScaleTablets = true; + FileChooser chooser; - @Override - protected void onCreate(Bundle savedInstanceState){ - super.onCreate(savedInstanceState); + @Override + protected void onCreate(Bundle savedInstanceState){ + super.onCreate(savedInstanceState); - AndroidApplicationConfiguration config = new AndroidApplicationConfiguration(); - config.useImmersiveMode = true; + AndroidApplicationConfiguration config = new AndroidApplicationConfiguration(); + config.useImmersiveMode = true; - Platform.instance = new Platform(){ - DateFormat format = SimpleDateFormat.getDateTimeInstance(); + Platform.instance = new Platform(){ + DateFormat format = SimpleDateFormat.getDateTimeInstance(); - @Override - public boolean hasDiscord() { - return isPackageInstalled("com.discord"); - } + @Override + public boolean hasDiscord(){ + return isPackageInstalled("com.discord"); + } - @Override - public String format(Date date){ - return format.format(date); - } + @Override + public String format(Date date){ + return format.format(date); + } - @Override - public String format(int number){ - return NumberFormat.getIntegerInstance().format(number); - } + @Override + public String format(int number){ + return NumberFormat.getIntegerInstance().format(number); + } - @Override - public void addDialog(TextField field, int length){ - TextFieldDialogListener.add(field, 0, length); - } + @Override + public void addDialog(TextField field, int length){ + TextFieldDialogListener.add(field, 0, length); + } - @Override - public String getLocaleName(Locale locale){ - return locale.getDisplayName(locale); - } + @Override + public String getLocaleName(Locale locale){ + return locale.getDisplayName(locale); + } - @Override - public void openDonations() { - showDonations(); - } + @Override + public void openDonations(){ + showDonations(); + } - @Override - public ThreadProvider getThreadProvider() { - return new DefaultThreadImpl(); - } + @Override + public ThreadProvider getThreadProvider(){ + return new DefaultThreadImpl(); + } - @Override - public boolean isDebug() { - return false; - } + @Override + public boolean isDebug(){ + return false; + } - @Override - public String getUUID() { - try { - String s = Secure.getString(getContext().getContentResolver(), - Secure.ANDROID_ID); + @Override + public String getUUID(){ + try{ + String s = Secure.getString(getContext().getContentResolver(), + Secure.ANDROID_ID); - int len = s.length(); - byte[] data = new byte[len / 2]; - for (int i = 0; i < len; i += 2) { - data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) - + Character.digit(s.charAt(i + 1), 16)); - } + int len = s.length(); + byte[] data = new byte[len / 2]; + for(int i = 0; i < len; i += 2){ + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + + Character.digit(s.charAt(i + 1), 16)); + } - String result = new String(Base64Coder.encode(data)); + String result = new String(Base64Coder.encode(data)); - if(result.equals("AAAAAAAAAOA=")) throw new RuntimeException("Bad UUID."); + if(result.equals("AAAAAAAAAOA=")) throw new RuntimeException("Bad UUID."); - return result; - }catch (Exception e){ - return super.getUUID(); - } - } + return result; + }catch(Exception e){ + return super.getUUID(); + } + } - @Override - public void shareFile(FileHandle file){ + @Override + public void shareFile(FileHandle file){ - } + } - @Override - public void showFileChooser(String text, String content, Consumer cons, boolean open, String filetype) { - chooser = new FileChooser(text, file -> file.extension().equalsIgnoreCase(filetype), open, cons); + @Override + public void showFileChooser(String text, String content, Consumer cons, boolean open, String filetype){ + chooser = new FileChooser(text, file -> file.extension().equalsIgnoreCase(filetype), open, cons); - if(Build.VERSION.SDK_INT < Build.VERSION_CODES.M || (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED && - checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)){ - chooser.show(); - chooser = null; - }else { - ArrayList perms = new ArrayList<>(); + if(Build.VERSION.SDK_INT < Build.VERSION_CODES.M || (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED && + checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)){ + chooser.show(); + chooser = null; + }else{ + ArrayList perms = new ArrayList<>(); - if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { - perms.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); - } + if(checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){ + perms.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); + } - if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { - perms.add(Manifest.permission.READ_EXTERNAL_STORAGE); - } + if(checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){ + perms.add(Manifest.permission.READ_EXTERNAL_STORAGE); + } - requestPermissions(perms.toArray(new String[perms.size()]), PERMISSION_REQUEST_CODE); - } - } + requestPermissions(perms.toArray(new String[perms.size()]), PERMISSION_REQUEST_CODE); + } + } - @Override - public void beginForceLandscape() { - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); - } + @Override + public void beginForceLandscape(){ + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + } - @Override - public void endForceLandscape() { - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR); - } + @Override + public void endForceLandscape(){ + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR); + } - @Override - public boolean canDonate(){ - return true; - } - }; + @Override + public boolean canDonate(){ + return true; + } + }; - try { - ProviderInstaller.installIfNeeded(this); - } catch (GooglePlayServicesRepairableException e) { - GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance(); - apiAvailability.getErrorDialog(this, e.getConnectionStatusCode(), 0).show(); - } catch (GooglePlayServicesNotAvailableException e) { - Log.e("SecurityException", "Google Play Services not available."); - } + try{ + ProviderInstaller.installIfNeeded(this); + }catch(GooglePlayServicesRepairableException e){ + GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance(); + apiAvailability.getErrorDialog(this, e.getConnectionStatusCode(), 0).show(); + }catch(GooglePlayServicesNotAvailableException e){ + Log.e("SecurityException", "Google Play Services not available."); + } - if(doubleScaleTablets && isTablet(this.getContext())){ - Unit.dp.addition = 0.5f; - } - - config.hideStatusBar = true; + if(doubleScaleTablets && isTablet(this.getContext())){ + Unit.dp.addition = 0.5f; + } + + config.hideStatusBar = true; Net.setClientProvider(new KryoClient()); Net.setServerProvider(new KryoServer()); initialize(new Mindustry(), config); - checkFiles(getIntent()); - } + checkFiles(getIntent()); + } - @Override - public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { - if(requestCode == PERMISSION_REQUEST_CODE){ - for(int i : grantResults){ - if(i != PackageManager.PERMISSION_GRANTED) return; - } + @Override + public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults){ + if(requestCode == PERMISSION_REQUEST_CODE){ + for(int i : grantResults){ + if(i != PackageManager.PERMISSION_GRANTED) return; + } - if(chooser != null){ - chooser.show(); - } - } - } + if(chooser != null){ + chooser.show(); + } + } + } - private void checkFiles(Intent intent){ - try { - Uri uri = intent.getData(); - if (uri != null) { - File myFile = null; - String scheme = uri.getScheme(); - if (scheme.equals("file")) { - String fileName = uri.getEncodedPath(); - myFile = new File(fileName); - } else if (!scheme.equals("content")) { - //error - return; - } + private void checkFiles(Intent intent){ + try{ + Uri uri = intent.getData(); + if(uri != null){ + File myFile = null; + String scheme = uri.getScheme(); + if(scheme.equals("file")){ + String fileName = uri.getEncodedPath(); + myFile = new File(fileName); + }else if(!scheme.equals("content")){ + //error + return; + } - boolean save = uri.getPath().endsWith(saveExtension); - boolean map = uri.getPath().endsWith(mapExtension); + boolean save = uri.getPath().endsWith(saveExtension); + boolean map = uri.getPath().endsWith(mapExtension); - InputStream inStream; - if (myFile != null) inStream = new FileInputStream(myFile); - else inStream = getContentResolver().openInputStream(uri); + InputStream inStream; + if(myFile != null) inStream = new FileInputStream(myFile); + else inStream = getContentResolver().openInputStream(uri); - Gdx.app.postRunnable(() -> { + Gdx.app.postRunnable(() -> { - if(save){ //open save - System.out.println("Opening save."); - FileHandle file = Gdx.files.local("temp-save." + saveExtension); - file.write(inStream, false); + if(save){ //open save + System.out.println("Opening save."); + FileHandle file = Gdx.files.local("temp-save." + saveExtension); + file.write(inStream, false); - if(SaveIO.isSaveValid(file)){ - try{ - SaveSlot slot = control.getSaves().importSave(file); - ui.load.runLoadSave(slot); - }catch (IOException e){ - ui.showError(Bundles.format("text.save.import.fail", Strings.parseException(e, false))); - } - }else{ - ui.showError("$text.save.import.invalid"); - } + if(SaveIO.isSaveValid(file)){ + try{ + SaveSlot slot = control.getSaves().importSave(file); + ui.load.runLoadSave(slot); + }catch(IOException e){ + ui.showError(Bundles.format("text.save.import.fail", Strings.parseException(e, false))); + } + }else{ + ui.showError("$text.save.import.invalid"); + } - }else if(map){ //open map - Gdx.app.postRunnable(() -> { - System.out.println("Opening map."); - if (!ui.editor.isShown()) { - ui.editor.show(); - } + }else if(map){ //open map + Gdx.app.postRunnable(() -> { + System.out.println("Opening map."); + if(!ui.editor.isShown()){ + ui.editor.show(); + } - ui.editor.beginEditMap(inStream); - }); - } - }); - } + ui.editor.beginEditMap(inStream); + }); + } + }); + } - }catch (IOException e){ - e.printStackTrace(); - } - } - - private boolean isPackageInstalled(String packagename) { - try { - getPackageManager().getPackageInfo(packagename, 0); - return true; - } catch (Exception e) { - return false; - } - } - - private boolean isTablet(Context context) { - TelephonyManager manager = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); - return manager.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE; - } - - private void showDonations(){ - Intent intent = new Intent(this, DonationsActivity.class); - startActivity(intent); - } + }catch(IOException e){ + e.printStackTrace(); + } + } + + private boolean isPackageInstalled(String packagename){ + try{ + getPackageManager().getPackageInfo(packagename, 0); + return true; + }catch(Exception e){ + return false; + } + } + + private boolean isTablet(Context context){ + TelephonyManager manager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + return manager.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE; + } + + private void showDonations(){ + Intent intent = new Intent(this, DonationsActivity.class); + startActivity(intent); + } } diff --git a/android/src/io/anuke/mindustry/AndroidTextFieldDialog.java b/android/src/io/anuke/mindustry/AndroidTextFieldDialog.java index 7a530e817a..56a1b92c72 100644 --- a/android/src/io/anuke/mindustry/AndroidTextFieldDialog.java +++ b/android/src/io/anuke/mindustry/AndroidTextFieldDialog.java @@ -11,20 +11,20 @@ import android.widget.EditText; import com.badlogic.gdx.Gdx; public class AndroidTextFieldDialog{ - private Activity activity; - private EditText userInput; - private AlertDialog.Builder builder; - private TextPromptListener listener; - private boolean isBuild; + private Activity activity; + private EditText userInput; + private AlertDialog.Builder builder; + private TextPromptListener listener; + private boolean isBuild; - public AndroidTextFieldDialog() { - this.activity = (Activity)Gdx.app; - load(); - } + public AndroidTextFieldDialog(){ + this.activity = (Activity) Gdx.app; + load(); + } - public AndroidTextFieldDialog show() { + public AndroidTextFieldDialog show(){ - activity.runOnUiThread(() -> { + activity.runOnUiThread(() -> { AlertDialog dialog = builder.create(); dialog.getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE); @@ -33,12 +33,12 @@ public class AndroidTextFieldDialog{ }); - return this; - } + return this; + } - private AndroidTextFieldDialog load() { + private AndroidTextFieldDialog load(){ - activity.runOnUiThread(() -> { + activity.runOnUiThread(() -> { AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(activity); LayoutInflater li = LayoutInflater.from(activity); @@ -55,64 +55,65 @@ public class AndroidTextFieldDialog{ isBuild = true; }); - // Wait till TextPrompt is built. - while (!isBuild) { - try { - Thread.sleep(10); - } catch (InterruptedException e) { } - } + // Wait till TextPrompt is built. + while(!isBuild){ + try{ + Thread.sleep(10); + }catch(InterruptedException e){ + } + } - return this; - } + return this; + } - public int getResourceId(String pVariableName, String pVariableType) { - try { - return activity.getResources().getIdentifier(pVariableName, pVariableType, activity.getPackageName()); - } catch (Exception e) { - Gdx.app.error("Android Dialogs", "Cannot find resouce with name: " + pVariableName - + " Did you copy the layouts to /res/layouts and /res/layouts_v14 ?"); - e.printStackTrace(); - return -1; - } - } + public int getResourceId(String pVariableName, String pVariableType){ + try{ + return activity.getResources().getIdentifier(pVariableName, pVariableType, activity.getPackageName()); + }catch(Exception e){ + Gdx.app.error("Android Dialogs", "Cannot find resouce with name: " + pVariableName + + " Did you copy the layouts to /res/layouts and /res/layouts_v14 ?"); + e.printStackTrace(); + return -1; + } + } - public AndroidTextFieldDialog setText(CharSequence value) { - userInput.append(value); - return this; - } + public AndroidTextFieldDialog setText(CharSequence value){ + userInput.append(value); + return this; + } - public AndroidTextFieldDialog setCancelButtonLabel(CharSequence label) { - builder.setNegativeButton(label, (dialog, id) -> dialog.cancel()); - return this; - } + public AndroidTextFieldDialog setCancelButtonLabel(CharSequence label){ + builder.setNegativeButton(label, (dialog, id) -> dialog.cancel()); + return this; + } - public AndroidTextFieldDialog setConfirmButtonLabel(CharSequence label) { - builder.setPositiveButton(label, (dialog, id) -> { - if (listener != null && !userInput.getText().toString().isEmpty()) { + public AndroidTextFieldDialog setConfirmButtonLabel(CharSequence label){ + builder.setPositiveButton(label, (dialog, id) -> { + if(listener != null && !userInput.getText().toString().isEmpty()){ listener.confirm(userInput.getText().toString()); } }); - return this; - } + return this; + } - public AndroidTextFieldDialog setTextPromptListener(TextPromptListener listener) { - this.listener = listener; - return this; - } + public AndroidTextFieldDialog setTextPromptListener(TextPromptListener listener){ + this.listener = listener; + return this; + } - public AndroidTextFieldDialog setInputType(int type) { - userInput.setInputType(type); - return this; - } + public AndroidTextFieldDialog setInputType(int type){ + userInput.setInputType(type); + return this; + } - public AndroidTextFieldDialog setMaxLength(int length) { - userInput.setFilters(new InputFilter[] { new InputFilter.LengthFilter(length) }); - return this; - } - - public interface TextPromptListener{ - void confirm(String text); - } + public AndroidTextFieldDialog setMaxLength(int length){ + userInput.setFilters(new InputFilter[]{new InputFilter.LengthFilter(length)}); + return this; + } + + public interface TextPromptListener{ + void confirm(String text); + } } diff --git a/android/src/io/anuke/mindustry/DonationsActivity.java b/android/src/io/anuke/mindustry/DonationsActivity.java index 690ccc3413..c6e61c61b2 100644 --- a/android/src/io/anuke/mindustry/DonationsActivity.java +++ b/android/src/io/anuke/mindustry/DonationsActivity.java @@ -8,12 +8,9 @@ import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.view.View; import android.widget.Button; - import org.sufficientlysecure.donations.DonationsFragment; -public class DonationsActivity extends FragmentActivity { - DonationsFragment donationsFragment; - +public class DonationsActivity extends FragmentActivity{ /** * Google */ @@ -21,13 +18,14 @@ public class DonationsActivity extends FragmentActivity { private static final String[] GOOGLE_CATALOG = new String[]{ "mindustry.donation.1", "mindustry.donation.2", "mindustry.donation.5", "mindustry.donation.10", "mindustry.donation.15", - "mindustry.donation.25", "mindustry.donation.50" }; + "mindustry.donation.25", "mindustry.donation.50"}; + DonationsFragment donationsFragment; /** * Called when the activity is first created. */ @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setTheme(R.style.GdxTheme); @@ -35,7 +33,7 @@ public class DonationsActivity extends FragmentActivity { setContentView(R.layout.donations_activity); FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); - if (BuildConfig.DONATIONS_GOOGLE) { + if(BuildConfig.DONATIONS_GOOGLE){ donationsFragment = DonationsFragment.newInstance(BuildConfig.DEBUG, true, GOOGLE_PUBKEY, GOOGLE_CATALOG, getResources().getStringArray(R.array.donation_google_catalog_values), false, null, null, null, false, null, null, false, null); @@ -48,9 +46,10 @@ public class DonationsActivity extends FragmentActivity { public void onStart(){ super.onStart(); - Button b = ((Button)findViewById(org.sufficientlysecure.donations.R.id.donations__google_android_market_donate_button)); - b.setOnClickListener(new View.OnClickListener() { - @Override public void onClick(View view) { + Button b = ((Button) findViewById(org.sufficientlysecure.donations.R.id.donations__google_android_market_donate_button)); + b.setOnClickListener(new View.OnClickListener(){ + @Override + public void onClick(View view){ donationsFragment.donateGoogleOnClick(donationsFragment.getView()); b.setEnabled(false); } @@ -58,20 +57,19 @@ public class DonationsActivity extends FragmentActivity { } - /** * Needed for Google Play In-app Billing. It uses startIntentSenderForResult(). The result is not propagated to * the Fragment like in startActivityForResult(). Thus we need to propagate manually to our Fragment. */ @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { + protected void onActivityResult(int requestCode, int resultCode, Intent data){ super.onActivityResult(requestCode, resultCode, data); - Button b = ((Button)findViewById(org.sufficientlysecure.donations.R.id.donations__google_android_market_donate_button)); + Button b = ((Button) findViewById(org.sufficientlysecure.donations.R.id.donations__google_android_market_donate_button)); b.setEnabled(true); FragmentManager fragmentManager = getSupportFragmentManager(); Fragment fragment = fragmentManager.findFragmentByTag("donationsFragment"); - if (fragment != null) { + if(fragment != null){ fragment.onActivityResult(requestCode, resultCode, data); //TODO donation event, set settings? } diff --git a/android/src/io/anuke/mindustry/TextFieldDialogListener.java b/android/src/io/anuke/mindustry/TextFieldDialogListener.java index c6f328d785..be1cc6d924 100644 --- a/android/src/io/anuke/mindustry/TextFieldDialogListener.java +++ b/android/src/io/anuke/mindustry/TextFieldDialogListener.java @@ -11,57 +11,57 @@ import io.anuke.ucore.scene.event.InputListener; import io.anuke.ucore.scene.ui.TextField; public class TextFieldDialogListener extends ClickListener{ - private TextField field; - private int type; - private int max; + private TextField field; + private int type; + private int max; - public static void add(TextField field, int type, int max){ - field.addListener(new TextFieldDialogListener(field, type, max)); - field.addListener(new InputListener(){ - public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) { - Gdx.input.setOnscreenKeyboardVisible(false); - return false; - } - }); - } + //type - 0 is text, 1 is numbers, 2 is decimals + public TextFieldDialogListener(TextField field, int type, int max){ + this.field = field; + this.type = type; + this.max = max; + } - public static void add(TextField field){ - add(field, 0, 16); - } + public static void add(TextField field, int type, int max){ + field.addListener(new TextFieldDialogListener(field, type, max)); + field.addListener(new InputListener(){ + public boolean touchDown(InputEvent event, float x, float y, int pointer, int button){ + Gdx.input.setOnscreenKeyboardVisible(false); + return false; + } + }); + } - //type - 0 is text, 1 is numbers, 2 is decimals - public TextFieldDialogListener(TextField field, int type, int max){ - this.field = field; - this.type = type; - this.max = max; - } + public static void add(TextField field){ + add(field, 0, 16); + } - public void clicked(final InputEvent event, float x, float y){ - - if(Gdx.app.getType() == ApplicationType.Desktop) return; - - AndroidTextFieldDialog dialog = new AndroidTextFieldDialog(); + public void clicked(final InputEvent event, float x, float y){ - dialog.setTextPromptListener(text -> { + if(Gdx.app.getType() == ApplicationType.Desktop) return; + + AndroidTextFieldDialog dialog = new AndroidTextFieldDialog(); + + dialog.setTextPromptListener(text -> { field.clearText(); field.appendText(text); field.fire(new ChangeListener.ChangeEvent()); Gdx.graphics.requestRendering(); }); - if(type == 0){ - dialog.setInputType(InputType.TYPE_CLASS_TEXT); - }else if(type == 1){ - dialog.setInputType(InputType.TYPE_CLASS_NUMBER); - }else if(type == 2){ - dialog.setInputType(InputType.TYPE_NUMBER_FLAG_DECIMAL); - } + if(type == 0){ + dialog.setInputType(InputType.TYPE_CLASS_TEXT); + }else if(type == 1){ + dialog.setInputType(InputType.TYPE_CLASS_NUMBER); + }else if(type == 2){ + dialog.setInputType(InputType.TYPE_NUMBER_FLAG_DECIMAL); + } - dialog.setConfirmButtonLabel("OK").setText(field.getText()); - dialog.setCancelButtonLabel("Cancel"); - dialog.setMaxLength(max); - dialog.show(); - event.cancel(); + dialog.setConfirmButtonLabel("OK").setText(field.getText()); + dialog.setCancelButtonLabel("Cancel"); + dialog.setMaxLength(max); + dialog.show(); + event.cancel(); - } + } } diff --git a/annotations/src/io/anuke/annotations/Annotations.java b/annotations/src/io/anuke/annotations/Annotations.java index d685729921..cc60478fee 100644 --- a/annotations/src/io/anuke/annotations/Annotations.java +++ b/annotations/src/io/anuke/annotations/Annotations.java @@ -9,70 +9,31 @@ import java.lang.annotation.Target; * Goal: To create a system to send events to the server from the client and vice versa, without creating a new packet type each time.
* These events may optionally also trigger on the caller client/server as well.
*/ -public class Annotations { +public class Annotations{ - /**Marks a method as invokable remotely across a server/client connection.*/ - @Target(ElementType.METHOD) - @Retention(RetentionPolicy.CLASS) - public @interface Remote { - /**Specifies the locations from which this method can be invoked.*/ - Loc targets() default Loc.server; - /**Specifies which methods are generated. Only affects server-to-client methods.*/ - Variant variants() default Variant.all; - /**The local locations where this method is called locally, when invoked.*/ - Loc called() default Loc.none; - /**Whether to forward this packet to all other clients upon recieval. Client only.*/ - boolean forward() default false; - /**Whether the packet for this method is sent with UDP instead of TCP. - * UDP is faster, but is prone to packet loss and duplication.*/ - boolean unreliable() default false; - /**The simple class name where this method is placed.*/ - String in() default "Call"; - /**Priority of this event.*/ - PacketPriority priority() default PacketPriority.normal; - } - - /**Specifies that this method will be used to write classes of the type returned by {@link #value()}.
- * This method must return void and have two parameters, the first being of type {@link java.nio.ByteBuffer} and the second - * being the type returned by {@link #value()}.*/ - @Target(ElementType.METHOD) - @Retention(RetentionPolicy.CLASS) - public @interface WriteClass { - Class value(); - } - - /**Specifies that this method will be used to read classes of the type returned by {@link #value()}.
- * This method must return the type returned by {@link #value()}, - * and have one parameter, being of type {@link java.nio.ByteBuffer}.*/ - @Target(ElementType.METHOD) - @Retention(RetentionPolicy.CLASS) - public @interface ReadClass { - Class value(); - } - - public enum PacketPriority { - /**Gets put in a queue and processed if not connected.*/ + public enum PacketPriority{ + /** Gets put in a queue and processed if not connected. */ normal, - /**Gets handled immediately, regardless of connection status.*/ + /** Gets handled immediately, regardless of connection status. */ high, - /**Does not get handled unless client is connected.*/ + /** Does not get handled unless client is connected. */ low } - /**A set of two booleans, one specifying server and one specifying client.*/ - public enum Loc { - /**Method can only be invoked on the client from the server.*/ + /** A set of two booleans, one specifying server and one specifying client. */ + public enum Loc{ + /** Method can only be invoked on the client from the server. */ server(true, false), - /**Method can only be invoked on the server from the client.*/ + /** Method can only be invoked on the server from the client. */ client(false, true), - /**Method can be invoked from anywhere*/ + /** Method can be invoked from anywhere */ both(true, true), - /**Neither server nor client.*/ + /** Neither server nor client. */ none(false, false); - /**If true, this method can be invoked ON clients FROM servers.*/ + /** If true, this method can be invoked ON clients FROM servers. */ public final boolean isServer; - /**If true, this method can be invoked ON servers FROM clients.*/ + /** If true, this method can be invoked ON servers FROM clients. */ public final boolean isClient; Loc(boolean server, boolean client){ @@ -81,12 +42,12 @@ public class Annotations { } } - public enum Variant { - /**Method can only be invoked targeting one player.*/ + public enum Variant{ + /** Method can only be invoked targeting one player. */ one(true, false), - /**Method can only be invoked targeting all players.*/ + /** Method can only be invoked targeting all players. */ all(false, true), - /**Method targets both one player and all players.*/ + /** Method targets both one player and all players. */ both(true, true); public final boolean isOne, isAll; @@ -96,4 +57,55 @@ public class Annotations { this.isAll = isAll; } } + + /** Marks a method as invokable remotely across a server/client connection. */ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.CLASS) + public @interface Remote{ + /** Specifies the locations from which this method can be invoked. */ + Loc targets() default Loc.server; + + /** Specifies which methods are generated. Only affects server-to-client methods. */ + Variant variants() default Variant.all; + + /** The local locations where this method is called locally, when invoked. */ + Loc called() default Loc.none; + + /** Whether to forward this packet to all other clients upon recieval. Client only. */ + boolean forward() default false; + + /** + * Whether the packet for this method is sent with UDP instead of TCP. + * UDP is faster, but is prone to packet loss and duplication. + */ + boolean unreliable() default false; + + /** The simple class name where this method is placed. */ + String in() default "Call"; + + /** Priority of this event. */ + PacketPriority priority() default PacketPriority.normal; + } + + /** + * Specifies that this method will be used to write classes of the type returned by {@link #value()}.
+ * This method must return void and have two parameters, the first being of type {@link java.nio.ByteBuffer} and the second + * being the type returned by {@link #value()}. + */ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.CLASS) + public @interface WriteClass{ + Class value(); + } + + /** + * Specifies that this method will be used to read classes of the type returned by {@link #value()}.
+ * This method must return the type returned by {@link #value()}, + * and have one parameter, being of type {@link java.nio.ByteBuffer}. + */ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.CLASS) + public @interface ReadClass{ + Class value(); + } } diff --git a/annotations/src/io/anuke/annotations/ClassEntry.java b/annotations/src/io/anuke/annotations/ClassEntry.java index 3997eb6029..a9be2ec134 100644 --- a/annotations/src/io/anuke/annotations/ClassEntry.java +++ b/annotations/src/io/anuke/annotations/ClassEntry.java @@ -2,14 +2,14 @@ package io.anuke.annotations; import java.util.ArrayList; -/**Represents a class witha list method entries to include in it.*/ -public class ClassEntry { - /**All methods in this generated class.*/ +/** Represents a class witha list method entries to include in it. */ +public class ClassEntry{ + /** All methods in this generated class. */ public final ArrayList methods = new ArrayList<>(); - /**Simple class name.*/ + /** Simple class name. */ public final String name; - public ClassEntry(String name) { + public ClassEntry(String name){ this.name = name; } } diff --git a/annotations/src/io/anuke/annotations/IOFinder.java b/annotations/src/io/anuke/annotations/IOFinder.java index c2dd8e3567..9276b9b887 100644 --- a/annotations/src/io/anuke/annotations/IOFinder.java +++ b/annotations/src/io/anuke/annotations/IOFinder.java @@ -10,12 +10,16 @@ import javax.tools.Diagnostic.Kind; import java.util.HashMap; import java.util.Set; -/**This class finds reader and writer methods annotated by the {@link io.anuke.annotations.Annotations.WriteClass} - * and {@link io.anuke.annotations.Annotations.ReadClass} annotations.*/ -public class IOFinder { +/** + * This class finds reader and writer methods annotated by the {@link io.anuke.annotations.Annotations.WriteClass} + * and {@link io.anuke.annotations.Annotations.ReadClass} annotations. + */ +public class IOFinder{ - /**Finds all class serializers for all types and returns them. Logs errors when necessary. - * Maps fully qualified class names to their serializers.*/ + /** + * Finds all class serializers for all types and returns them. Logs errors when necessary. + * Maps fully qualified class names to their serializers. + */ public HashMap findSerializers(RoundEnvironment env){ HashMap result = new HashMap<>(); @@ -51,33 +55,33 @@ public class IOFinder { } private String getValue(WriteClass write){ - try { + try{ Class type = write.value(); return type.getName(); - }catch (MirroredTypeException e){ + }catch(MirroredTypeException e){ return e.getTypeMirror().toString(); } } private String getValue(ReadClass read){ - try { + try{ Class type = read.value(); return type.getName(); - }catch (MirroredTypeException e){ + }catch(MirroredTypeException e){ return e.getTypeMirror().toString(); } } - /**Information about read/write methods for a specific class type.*/ + /** Information about read/write methods for a specific class type. */ public static class ClassSerializer{ - /**Fully qualified method name of the reader.*/ + /** Fully qualified method name of the reader. */ public final String readMethod; - /**Fully qualified method name of the writer.*/ + /** Fully qualified method name of the writer. */ public final String writeMethod; - /**Fully qualified class type name.*/ + /** Fully qualified class type name. */ public final String classType; - public ClassSerializer(String readMethod, String writeMethod, String classType) { + public ClassSerializer(String readMethod, String writeMethod, String classType){ this.readMethod = readMethod; this.writeMethod = writeMethod; this.classType = classType; diff --git a/annotations/src/io/anuke/annotations/MethodEntry.java b/annotations/src/io/anuke/annotations/MethodEntry.java index ee47fccb4a..3566516372 100644 --- a/annotations/src/io/anuke/annotations/MethodEntry.java +++ b/annotations/src/io/anuke/annotations/MethodEntry.java @@ -6,32 +6,34 @@ import io.anuke.annotations.Annotations.Variant; import javax.lang.model.element.ExecutableElement; -/**Class that repesents a remote method to be constructed and put into a class.*/ -public class MethodEntry { - /**Simple target class name.*/ +/** Class that repesents a remote method to be constructed and put into a class. */ +public class MethodEntry{ + /** Simple target class name. */ public final String className; - /**Fully qualified target method to call.*/ + /** Fully qualified target method to call. */ public final String targetMethod; - /**Whether this method can be called on a client/server.*/ + /** Whether this method can be called on a client/server. */ public final Loc where; - /**Whether an additional 'one' and 'all' method variant is generated. At least one of these must be true. - * Only applicable to client (server-invoked) methods.*/ + /** + * Whether an additional 'one' and 'all' method variant is generated. At least one of these must be true. + * Only applicable to client (server-invoked) methods. + */ public final Variant target; - /**Whether this method is called locally as well as remotely.*/ + /** Whether this method is called locally as well as remotely. */ public final Loc local; - /**Whether this method is unreliable and uses UDP.*/ + /** Whether this method is unreliable and uses UDP. */ public final boolean unreliable; - /**Whether to forward this method call to all other clients when a client invokes it. Server only.*/ + /** Whether to forward this method call to all other clients when a client invokes it. Server only. */ public final boolean forward; - /**Unique method ID.*/ + /** Unique method ID. */ public final int id; - /**The element method associated with this entry.*/ + /** The element method associated with this entry. */ public final ExecutableElement element; - /**The assigned packet priority. Only used in clients.*/ + /** The assigned packet priority. Only used in clients. */ public final PacketPriority priority; public MethodEntry(String className, String targetMethod, Loc where, Variant target, - Loc local, boolean unreliable, boolean forward, int id, ExecutableElement element, PacketPriority priority) { + Loc local, boolean unreliable, boolean forward, int id, ExecutableElement element, PacketPriority priority){ this.className = className; this.forward = forward; this.targetMethod = targetMethod; @@ -45,7 +47,7 @@ public class MethodEntry { } @Override - public int hashCode() { + public int hashCode(){ return targetMethod.hashCode(); } } diff --git a/annotations/src/io/anuke/annotations/RemoteMethodAnnotationProcessor.java b/annotations/src/io/anuke/annotations/RemoteMethodAnnotationProcessor.java index 037ec8129a..b0402b2c55 100644 --- a/annotations/src/io/anuke/annotations/RemoteMethodAnnotationProcessor.java +++ b/annotations/src/io/anuke/annotations/RemoteMethodAnnotationProcessor.java @@ -18,25 +18,25 @@ import java.util.*; import java.util.stream.Collectors; -/**The annotation processor for generating remote method call code.*/ +/** The annotation processor for generating remote method call code. */ @SupportedSourceVersion(SourceVersion.RELEASE_8) @SupportedAnnotationTypes({ - "io.anuke.annotations.Annotations.Remote", - "io.anuke.annotations.Annotations.WriteClass", - "io.anuke.annotations.Annotations.ReadClass", + "io.anuke.annotations.Annotations.Remote", + "io.anuke.annotations.Annotations.WriteClass", + "io.anuke.annotations.Annotations.ReadClass", }) -public class RemoteMethodAnnotationProcessor extends AbstractProcessor { - /**Maximum size of each event packet.*/ +public class RemoteMethodAnnotationProcessor extends AbstractProcessor{ + /** Maximum size of each event packet. */ public static final int maxPacketSize = 4096; - /**Name of the base package to put all the generated classes.*/ + /** Name of the base package to put all the generated classes. */ private static final String packageName = "io.anuke.mindustry.gen"; - /**Name of class that handles reading and invoking packets on the server.*/ + /** Name of class that handles reading and invoking packets on the server. */ private static final String readServerName = "RemoteReadServer"; - /**Name of class that handles reading and invoking packets on the client.*/ + /** Name of class that handles reading and invoking packets on the client. */ private static final String readClientName = "RemoteReadClient"; - /**Processing round number.*/ + /** Processing round number. */ private int round; //class serializers @@ -51,7 +51,7 @@ public class RemoteMethodAnnotationProcessor extends AbstractProcessor { private ArrayList classes; @Override - public synchronized void init(ProcessingEnvironment processingEnv) { + public synchronized void init(ProcessingEnvironment processingEnv){ super.init(processingEnv); //put all relevant utils into utils class Utils.typeUtils = processingEnv.getTypeUtils(); @@ -61,15 +61,15 @@ public class RemoteMethodAnnotationProcessor extends AbstractProcessor { } @Override - public boolean process(Set annotations, RoundEnvironment roundEnv) { + public boolean process(Set annotations, RoundEnvironment roundEnv){ if(round > 1) return false; //only process 2 rounds - round ++; + round++; - try { + try{ //round 1: find all annotations, generate *writers* - if(round == 1) { + if(round == 1){ //get serializers serializers = new IOFinder().findSerializers(roundEnv); @@ -88,21 +88,21 @@ public class RemoteMethodAnnotationProcessor extends AbstractProcessor { orderedElements.sort(Comparator.comparing(Object::toString)); //create methods - for (Element element : orderedElements) { + for(Element element : orderedElements){ Remote annotation = element.getAnnotation(Remote.class); //check for static - if (!element.getModifiers().contains(Modifier.STATIC) || !element.getModifiers().contains(Modifier.PUBLIC)) { + if(!element.getModifiers().contains(Modifier.STATIC) || !element.getModifiers().contains(Modifier.PUBLIC)){ Utils.messager.printMessage(Kind.ERROR, "All @Remote methods must be public and static: ", element); } //can't generate none methods - if (annotation.targets() == Loc.none) { + if(annotation.targets() == Loc.none){ Utils.messager.printMessage(Kind.ERROR, "A @Remote method's targets() cannot be equal to 'none':", element); } //get and create class entry if needed - if (!classMap.containsKey(annotation.in())) { + if(!classMap.containsKey(annotation.in())){ ClassEntry clas = new ClassEntry(annotation.in()); classMap.put(annotation.in(), clas); classes.add(clas); @@ -127,7 +127,7 @@ public class RemoteMethodAnnotationProcessor extends AbstractProcessor { writegen.generateFor(classes, packageName); return true; - }else if(round == 2) { //round 2: generate all *readers* + }else if(round == 2){ //round 2: generate all *readers* RemoteReadGenerator readgen = new RemoteReadGenerator(serializers); //generate server readers @@ -147,7 +147,7 @@ public class RemoteMethodAnnotationProcessor extends AbstractProcessor { return true; } - }catch (Exception e){ + }catch(Exception e){ e.printStackTrace(); throw new RuntimeException(e); } diff --git a/annotations/src/io/anuke/annotations/RemoteReadGenerator.java b/annotations/src/io/anuke/annotations/RemoteReadGenerator.java index ae2ab83de7..5898e369a1 100644 --- a/annotations/src/io/anuke/annotations/RemoteReadGenerator.java +++ b/annotations/src/io/anuke/annotations/RemoteReadGenerator.java @@ -14,22 +14,25 @@ import java.nio.ByteBuffer; import java.util.HashMap; import java.util.List; -/**Generates code for reading remote invoke packets on the client and server.*/ -public class RemoteReadGenerator { +/** Generates code for reading remote invoke packets on the client and server. */ +public class RemoteReadGenerator{ private final HashMap serializers; - /**Creates a read generator that uses the supplied serializer setup.*/ - public RemoteReadGenerator(HashMap serializers) { + /** Creates a read generator that uses the supplied serializer setup. */ + public RemoteReadGenerator(HashMap serializers){ this.serializers = serializers; } - /**Generates a class for reading remote invoke packets. + /** + * Generates a class for reading remote invoke packets. + * * @param entries List of methods to use/ * @param className Simple target class name. * @param packageName Full target package name. - * @param needsPlayer Whether this read method requires a reference to the player sender.*/ + * @param needsPlayer Whether this read method requires a reference to the player sender. + */ public void generateFor(List entries, String className, String packageName, boolean needsPlayer) - throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException, IOException { + throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException, IOException{ TypeSpec.Builder classBuilder = TypeSpec.classBuilder(className).addModifiers(Modifier.PUBLIC); @@ -69,10 +72,10 @@ public class RemoteReadGenerator { StringBuilder varResult = new StringBuilder(); //go through each parameter - for(int i = 0; i < entry.element.getParameters().size(); i ++){ + for(int i = 0; i < entry.element.getParameters().size(); i++){ VariableElement var = entry.element.getParameters().get(i); - if(!needsPlayer || i != 0) { //if client, skip first parameter since it's always of type player and doesn't need to be read + if(!needsPlayer || i != 0){ //if client, skip first parameter since it's always of type player and doesn't need to be read //full type name of parameter String typeName = var.asType().toString(); //name of parameter @@ -81,17 +84,17 @@ public class RemoteReadGenerator { String capName = typeName.equals("byte") ? "" : Character.toUpperCase(typeName.charAt(0)) + typeName.substring(1); //write primitives automatically - if (Utils.isPrimitive(typeName)) { - if (typeName.equals("boolean")) { + if(Utils.isPrimitive(typeName)){ + if(typeName.equals("boolean")){ readBlock.addStatement("boolean " + varName + " = buffer.get() == 1"); - } else { + }else{ readBlock.addStatement(typeName + " " + varName + " = buffer.get" + capName + "()"); } - } else { + }else{ //else, try and find a serializer ClassSerializer ser = serializers.get(typeName); - if (ser == null) { //make sure a serializer exists! + if(ser == null){ //make sure a serializer exists! Utils.messager.printMessage(Kind.ERROR, "No @ReadClass method to read class type: '" + typeName + "'", var); return; } @@ -121,7 +124,7 @@ public class RemoteReadGenerator { } readBlock.nextControlFlow("catch (java.lang.Exception e)"); - readBlock.addStatement("throw new java.lang.RuntimeException(\"Failed to to read remote method '"+entry.element.getSimpleName() +"'!\", e)"); + readBlock.addStatement("throw new java.lang.RuntimeException(\"Failed to to read remote method '" + entry.element.getSimpleName() + "'!\", e)"); readBlock.endControlFlow(); } diff --git a/annotations/src/io/anuke/annotations/RemoteWriteGenerator.java b/annotations/src/io/anuke/annotations/RemoteWriteGenerator.java index 03be5844ac..86502f96a3 100644 --- a/annotations/src/io/anuke/annotations/RemoteWriteGenerator.java +++ b/annotations/src/io/anuke/annotations/RemoteWriteGenerator.java @@ -14,17 +14,17 @@ import java.nio.ByteBuffer; import java.util.HashMap; import java.util.List; -/**Generates code for writing remote invoke packets on the client and server.*/ -public class RemoteWriteGenerator { +/** Generates code for writing remote invoke packets on the client and server. */ +public class RemoteWriteGenerator{ private final HashMap serializers; - /**Creates a write generator that uses the supplied serializer setup.*/ - public RemoteWriteGenerator(HashMap serializers) { + /** Creates a write generator that uses the supplied serializer setup. */ + public RemoteWriteGenerator(HashMap serializers){ this.serializers = serializers; } - /**Generates all classes in this list.*/ - public void generateFor(List entries, String packageName) throws IOException { + /** Generates all classes in this list. */ + public void generateFor(List entries, String packageName) throws IOException{ for(ClassEntry entry : entries){ //create builder @@ -58,7 +58,7 @@ public class RemoteWriteGenerator { } } - /**Creates a specific variant for a method entry.*/ + /** Creates a specific variant for a method entry. */ private void writeMethodVariant(TypeSpec.Builder classBuilder, MethodEntry methodEntry, boolean toAll, boolean forwarded){ ExecutableElement elem = methodEntry.element; @@ -99,7 +99,7 @@ public class RemoteWriteGenerator { if(!forwarded && methodEntry.local != Loc.none){ //add in local checks if(methodEntry.local != Loc.both){ - method.beginControlFlow("if("+getCheckString(methodEntry.local) + " || !io.anuke.mindustry.net.Net.active())"); + method.beginControlFlow("if(" + getCheckString(methodEntry.local) + " || !io.anuke.mindustry.net.Net.active())"); } //concatenate parameters @@ -109,16 +109,16 @@ public class RemoteWriteGenerator { //special case: calling local-only methods uses the local player if(index == 0 && methodEntry.where == Loc.client){ results.append("io.anuke.mindustry.Vars.players[0]"); - }else { + }else{ results.append(var.getSimpleName()); } if(index != elem.getParameters().size() - 1) results.append(", "); - index ++; + index++; } //add the statement to call it method.addStatement("$N." + elem.getSimpleName() + "(" + results.toString() + ")", - ((TypeElement)elem.getEnclosingElement()).getQualifiedName().toString()); + ((TypeElement) elem.getEnclosingElement()).getQualifiedName().toString()); if(methodEntry.local != Loc.both){ method.endControlFlow(); @@ -126,7 +126,7 @@ public class RemoteWriteGenerator { } //start control flow to check if it's actually client/server so no netcode is called - method.beginControlFlow("if("+getCheckString(methodEntry.where)+")"); + method.beginControlFlow("if(" + getCheckString(methodEntry.where) + ")"); //add statement to create packet from pool method.addStatement("$1N packet = $2N.obtain($1N.class)", "io.anuke.mindustry.net.Packets.InvokePacket", "io.anuke.ucore.util.Pooling"); @@ -139,7 +139,7 @@ public class RemoteWriteGenerator { //rewind buffer method.addStatement("TEMP_BUFFER.position(0)"); - for(int i = 0; i < elem.getParameters().size(); i ++){ + for(int i = 0; i < elem.getParameters().size(); i++){ //first argument is skipped as it is always the player caller if((!methodEntry.where.isServer/* || methodEntry.mode == Loc.both*/) && i == 0){ continue; @@ -164,7 +164,7 @@ public class RemoteWriteGenerator { method.beginControlFlow("if(io.anuke.mindustry.net.Net.server())"); } - if(Utils.isPrimitive(typeName)) { //check if it's a primitive, and if so write it + if(Utils.isPrimitive(typeName)){ //check if it's a primitive, and if so write it if(typeName.equals("boolean")){ //booleans are special method.addStatement("TEMP_BUFFER.put(" + varName + " ? (byte)1 : 0)"); }else{ @@ -181,7 +181,7 @@ public class RemoteWriteGenerator { } //add statement for writing it - method.addStatement(ser.writeMethod + "(TEMP_BUFFER, " + varName +")"); + method.addStatement(ser.writeMethod + "(TEMP_BUFFER, " + varName + ")"); } if(writePlayerSkipCheck){ //write end check @@ -197,7 +197,7 @@ public class RemoteWriteGenerator { if(forwarded){ //forward packet if(!methodEntry.local.isClient){ //if the client doesn't get it called locally, forward it back after validation sendString = "send("; - }else { + }else{ sendString = "sendExcept(exceptSenderID, "; } }else if(toAll){ //send to all players / to server @@ -207,8 +207,8 @@ public class RemoteWriteGenerator { } //send the actual packet - method.addStatement("io.anuke.mindustry.net.Net." + sendString + "packet, "+ - (methodEntry.unreliable ? "io.anuke.mindustry.net.Net.SendMode.udp" : "io.anuke.mindustry.net.Net.SendMode.tcp")+")"); + method.addStatement("io.anuke.mindustry.net.Net." + sendString + "packet, " + + (methodEntry.unreliable ? "io.anuke.mindustry.net.Net.SendMode.udp" : "io.anuke.mindustry.net.Net.SendMode.tcp") + ")"); //end check for server/client @@ -220,7 +220,7 @@ public class RemoteWriteGenerator { private String getCheckString(Loc loc){ return loc.isClient && loc.isServer ? "io.anuke.mindustry.net.Net.server() || io.anuke.mindustry.net.Net.client()" : - loc.isClient ? "io.anuke.mindustry.net.Net.client()" : - loc.isServer ? "io.anuke.mindustry.net.Net.server()" : "false"; + loc.isClient ? "io.anuke.mindustry.net.Net.client()" : + loc.isServer ? "io.anuke.mindustry.net.Net.server()" : "false"; } } diff --git a/annotations/src/io/anuke/annotations/Utils.java b/annotations/src/io/anuke/annotations/Utils.java index d379a2d257..551d953bc4 100644 --- a/annotations/src/io/anuke/annotations/Utils.java +++ b/annotations/src/io/anuke/annotations/Utils.java @@ -7,14 +7,14 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; -public class Utils { +public class Utils{ public static Types typeUtils; public static Elements elementUtils; public static Filer filer; public static Messager messager; public static String getMethodName(Element element){ - return ((TypeElement)element.getEnclosingElement()).getQualifiedName().toString() + "." + element.getSimpleName(); + return ((TypeElement) element.getEnclosingElement()).getQualifiedName().toString() + "." + element.getSimpleName(); } public static boolean isPrimitive(String type){ diff --git a/core/src/Mindustry.gwt.xml b/core/src/Mindustry.gwt.xml index 9f3caa7c8c..95e2e371aa 100644 --- a/core/src/Mindustry.gwt.xml +++ b/core/src/Mindustry.gwt.xml @@ -1,22 +1,22 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/src/io/anuke/mindustry/Mindustry.java b/core/src/io/anuke/mindustry/Mindustry.java index 86cc421e96..804baca4e4 100644 --- a/core/src/io/anuke/mindustry/Mindustry.java +++ b/core/src/io/anuke/mindustry/Mindustry.java @@ -8,35 +8,35 @@ import io.anuke.ucore.util.Log; import static io.anuke.mindustry.Vars.*; -public class Mindustry extends ModuleCore { +public class Mindustry extends ModuleCore{ - @Override - public void init(){ - Timers.mark(); + @Override + public void init(){ + Timers.mark(); - Vars.init(); + Vars.init(); - debug = Platform.instance.isDebug(); + debug = Platform.instance.isDebug(); - Log.setUseColors(false); - BundleLoader.load(); - ContentLoader.load(); + Log.setUseColors(false); + BundleLoader.load(); + ContentLoader.load(); - module(logic = new Logic()); - module(world = new World()); - module(control = new Control()); - module(renderer = new Renderer()); - module(ui = new UI()); - module(netServer = new NetServer()); - module(netClient = new NetClient()); + module(logic = new Logic()); + module(world = new World()); + module(control = new Control()); + module(renderer = new Renderer()); + module(ui = new UI()); + module(netServer = new NetServer()); + module(netClient = new NetClient()); Log.info("Time to load [total]: {0}", Timers.elapsed()); - } + } - @Override - public void render(){ - super.render(); - threads.handleRender(); - } + @Override + public void render(){ + super.render(); + threads.handleRender(); + } } diff --git a/core/src/io/anuke/mindustry/Vars.java b/core/src/io/anuke/mindustry/Vars.java index d98a9c7d50..ba03f4c21f 100644 --- a/core/src/io/anuke/mindustry/Vars.java +++ b/core/src/io/anuke/mindustry/Vars.java @@ -19,173 +19,156 @@ import io.anuke.mindustry.io.Version; import io.anuke.mindustry.net.Net; import io.anuke.ucore.entities.Entities; import io.anuke.ucore.entities.EntityGroup; -import io.anuke.ucore.entities.trait.DrawTrait; import io.anuke.ucore.entities.impl.EffectEntity; +import io.anuke.ucore.entities.trait.DrawTrait; import io.anuke.ucore.scene.ui.layout.Unit; import io.anuke.ucore.util.OS; import java.util.Locale; public class Vars{ - public static boolean testMobile; - //shorthand for whether or not this is running on android or ios - public static boolean mobile; - public static boolean ios; - public static boolean android; - //shorthand for whether or not this is running on GWT - public static boolean gwt; - - //respawn time in frames - public static final float respawnduration = 60*4; - //time between waves in frames (on normal mode) - public static final float wavespace = 60*60*2f; - //waves can last no longer than 3 minutes, otherwise the next one spawns - public static final float maxwavespace = 60*60*4f; - - //set ridiculously high for now - public static final float coreBuildRange = 800999f; - //discord group URL - public static final String discordURL = "https://discord.gg/BKADYds"; - - public static final String releasesURL = "https://api.github.com/repos/Anuken/Mindustry/releases"; - - //directory for user-created map data - public static FileHandle customMapDirectory; - //save file directory - public static FileHandle saveDirectory; - public static String mapExtension = "mmap"; - public static String saveExtension = "msav"; - //scale of the font - public static float fontScale; - //camera zoom displayed on startup - public static int baseCameraScale; - //if true, player speed will be increased, massive amounts of resources will be given on start, and other debug options will be available - public static boolean debug = false; - public static boolean console = false; - //whether the player can clip through walls - public static boolean noclip = false; - //whether turrets have infinite ammo (only with debug) - public static boolean infiniteAmmo = true; - //whether to show paths of enemies - public static boolean showPaths = false; - //if false, player is always hidden - public static boolean showPlayer = true; - //whether to hide ui, only on debug - public static boolean showUI = true; - //whether to show block debug - public static boolean showBlockDebug = false; - - public static boolean showFog = true; - + //respawn time in frames + public static final float respawnduration = 60 * 4; + //time between waves in frames (on normal mode) + public static final float wavespace = 60 * 60 * 2f; + //waves can last no longer than 3 minutes, otherwise the next one spawns + public static final float maxwavespace = 60 * 60 * 4f; + //set ridiculously high for now + public static final float coreBuildRange = 800999f; + //discord group URL + public static final String discordURL = "https://discord.gg/BKADYds"; + public static final String releasesURL = "https://api.github.com/repos/Anuken/Mindustry/releases"; public static final int maxTextLength = 150; public static final int maxNameLength = 40; public static final int maxCharNameLength = 20; + public static final int saveSlots = 64; + public static final float itemSize = 5f; + public static final int tilesize = 8; + public static final Locale[] locales = {new Locale("en"), new Locale("fr"), new Locale("ru"), new Locale("uk", "UA"), new Locale("pl"), + new Locale("de"), new Locale("pt", "BR"), new Locale("ko"), new Locale("in", "ID"), new Locale("ita"), new Locale("es")}; + public static final Color[] playerColors = { + Color.valueOf("82759a"), + Color.valueOf("c0c1c5"), + Color.valueOf("fff0e7"), + Color.valueOf("7d2953"), + Color.valueOf("ff074e"), + Color.valueOf("ff072a"), + Color.valueOf("ff76a6"), + Color.valueOf("a95238"), + Color.valueOf("ffa108"), + Color.valueOf("feeb2c"), + Color.valueOf("ffcaa8"), + Color.valueOf("008551"), + Color.valueOf("00e339"), + Color.valueOf("423c7b"), + Color.valueOf("4b5ef1"), + Color.valueOf("2cabfe"), + }; + //server port + public static final int port = 6567; + public static final int webPort = 6568; + public static boolean testMobile; + //shorthand for whether or not this is running on android or ios + public static boolean mobile; + public static boolean ios; + public static boolean android; + //shorthand for whether or not this is running on GWT + public static boolean gwt; + //directory for user-created map data + public static FileHandle customMapDirectory; + //save file directory + public static FileHandle saveDirectory; + public static String mapExtension = "mmap"; + public static String saveExtension = "msav"; + //scale of the font + public static float fontScale; + //camera zoom displayed on startup + public static int baseCameraScale; + //if true, player speed will be increased, massive amounts of resources will be given on start, and other debug options will be available + public static boolean debug = false; + public static boolean console = false; + //whether the player can clip through walls + public static boolean noclip = false; + //whether turrets have infinite ammo (only with debug) + public static boolean infiniteAmmo = true; + //whether to show paths of enemies + public static boolean showPaths = false; + //if false, player is always hidden + public static boolean showPlayer = true; + //whether to hide ui, only on debug + public static boolean showUI = true; + //whether to show block debug + public static boolean showBlockDebug = false; + public static boolean showFog = true; + public static boolean headless = false; + public static float controllerMin = 0.25f; + public static float baseControllerSpeed = 11f; + //only if smoothCamera + public static boolean snapCamera = true; + public static GameState state; + public static ThreadHandler threads; - public static boolean headless = false; + public static Control control; + public static Logic logic; + public static Renderer renderer; + public static UI ui; + public static World world; + public static NetServer netServer; + public static NetClient netClient; - public static float controllerMin = 0.25f; + public static Player[] players = {}; - public static float baseControllerSpeed = 11f; + public static EntityGroup playerGroup; + public static EntityGroup tileGroup; + public static EntityGroup bulletGroup; + public static EntityGroup shieldGroup; + public static EntityGroup effectGroup; + public static EntityGroup groundEffectGroup; + public static EntityGroup itemGroup; - public static final int saveSlots = 64; + public static EntityGroup puddleGroup; + public static EntityGroup fireGroup; + public static EntityGroup[] unitGroups; - public static final float itemSize = 5f; + public static void init(){ + Version.init(); - //only if smoothCamera - public static boolean snapCamera = true; - - public static final int tilesize = 8; + playerGroup = Entities.addGroup(Player.class).enableMapping(); + tileGroup = Entities.addGroup(TileEntity.class, false); + bulletGroup = Entities.addGroup(Bullet.class).enableMapping(); + shieldGroup = Entities.addGroup(Shield.class, false); + effectGroup = Entities.addGroup(EffectEntity.class, false); + groundEffectGroup = Entities.addGroup(DrawTrait.class, false); + puddleGroup = Entities.addGroup(Puddle.class, false).enableMapping(); + itemGroup = Entities.addGroup(ItemDrop.class).enableMapping(); + fireGroup = Entities.addGroup(Fire.class, false).enableMapping(); + unitGroups = new EntityGroup[Team.all.length]; - public static final Locale[] locales = {new Locale("en"), new Locale("fr"), new Locale("ru"), new Locale("uk", "UA"), new Locale("pl"), - new Locale("de"), new Locale("pt", "BR"), new Locale("ko"), new Locale("in", "ID"), new Locale("ita"), new Locale("es")}; + for(Team team : Team.all){ + unitGroups[team.ordinal()] = Entities.addGroup(BaseUnit.class).enableMapping(); + } - public static final Color[] playerColors = { - Color.valueOf("82759a"), - Color.valueOf("c0c1c5"), - Color.valueOf("fff0e7"), - Color.valueOf("7d2953"), - Color.valueOf("ff074e"), - Color.valueOf("ff072a"), - Color.valueOf("ff76a6"), - Color.valueOf("a95238"), - Color.valueOf("ffa108"), - Color.valueOf("feeb2c"), - Color.valueOf("ffcaa8"), - Color.valueOf("008551"), - Color.valueOf("00e339"), - Color.valueOf("423c7b"), - Color.valueOf("4b5ef1"), - Color.valueOf("2cabfe"), - }; + for(EntityGroup group : Entities.getAllGroups()){ + group.setRemoveListener(entity -> { + if(entity instanceof SyncTrait && Net.client()){ + netClient.addRemovedEntity((entity).getID()); + } + }); + } - //server port - public static final int port = 6567; - public static final int webPort = 6568; + threads = new ThreadHandler(Platform.instance.getThreadProvider()); - public static GameState state; - public static ThreadHandler threads; + mobile = Gdx.app.getType() == ApplicationType.Android || Gdx.app.getType() == ApplicationType.iOS || testMobile; + ios = Gdx.app.getType() == ApplicationType.iOS; + android = Gdx.app.getType() == ApplicationType.Android; + gwt = Gdx.app.getType() == ApplicationType.WebGL; - public static Control control; - public static Logic logic; - public static Renderer renderer; - public static UI ui; - public static World world; - public static NetServer netServer; - public static NetClient netClient; - - public static Player[] players = {}; + if(!gwt){ + customMapDirectory = OS.getAppDataDirectory("Mindustry").child("maps/"); + saveDirectory = OS.getAppDataDirectory("Mindustry").child("saves/"); + } - public static EntityGroup playerGroup; - public static EntityGroup tileGroup; - public static EntityGroup bulletGroup; - public static EntityGroup shieldGroup; - public static EntityGroup effectGroup; - public static EntityGroup groundEffectGroup; - public static EntityGroup itemGroup; - - public static EntityGroup puddleGroup; - public static EntityGroup fireGroup; - public static EntityGroup[] unitGroups; - - public static void init(){ - Version.init(); - - playerGroup = Entities.addGroup(Player.class).enableMapping(); - tileGroup = Entities.addGroup(TileEntity.class, false); - bulletGroup = Entities.addGroup(Bullet.class).enableMapping(); - shieldGroup = Entities.addGroup(Shield.class, false); - effectGroup = Entities.addGroup(EffectEntity.class, false); - groundEffectGroup = Entities.addGroup(DrawTrait.class, false); - puddleGroup = Entities.addGroup(Puddle.class, false).enableMapping(); - itemGroup = Entities.addGroup(ItemDrop.class).enableMapping(); - fireGroup = Entities.addGroup(Fire.class, false).enableMapping(); - unitGroups = new EntityGroup[Team.all.length]; - - for(Team team : Team.all){ - unitGroups[team.ordinal()] = Entities.addGroup(BaseUnit.class).enableMapping(); - } - - for(EntityGroup group : Entities.getAllGroups()){ - group.setRemoveListener(entity -> { - if(entity instanceof SyncTrait && Net.client()){ - netClient.addRemovedEntity((entity).getID()); - } - }); - } - - threads = new ThreadHandler(Platform.instance.getThreadProvider()); - - mobile = Gdx.app.getType() == ApplicationType.Android || Gdx.app.getType() == ApplicationType.iOS || testMobile; - ios = Gdx.app.getType() == ApplicationType.iOS; - android = Gdx.app.getType() == ApplicationType.Android; - gwt = Gdx.app.getType() == ApplicationType.WebGL; - - if(!gwt) { - customMapDirectory = OS.getAppDataDirectory("Mindustry").child("maps/"); - saveDirectory = OS.getAppDataDirectory("Mindustry").child("saves/"); - } - - fontScale = Math.max(Unit.dp.scl(1f)/2f, 0.5f); - baseCameraScale = Math.round(Unit.dp.scl(4)); - } + fontScale = Math.max(Unit.dp.scl(1f) / 2f, 0.5f); + baseCameraScale = Math.round(Unit.dp.scl(4)); + } } diff --git a/core/src/io/anuke/mindustry/ai/BlockIndexer.java b/core/src/io/anuke/mindustry/ai/BlockIndexer.java index 38a3f8309f..ae21cd4e13 100644 --- a/core/src/io/anuke/mindustry/ai/BlockIndexer.java +++ b/core/src/io/anuke/mindustry/ai/BlockIndexer.java @@ -26,32 +26,53 @@ import static io.anuke.mindustry.Vars.*; //TODO consider using quadtrees for finding specific types of blocks within an area //TODO maybe use Arrays instead of ObjectSets? -/**Class used for indexing special target blocks for AI.*/ -public class BlockIndexer { - /**Size of one ore quadrant.*/ + +/** + * Class used for indexing special target blocks for AI. + */ +public class BlockIndexer{ + /** + * Size of one ore quadrant. + */ private final static int oreQuadrantSize = 20; - /**Size of one structure quadrant.*/ + /** + * Size of one structure quadrant. + */ private final static int structQuadrantSize = 12; - /**Set of all ores that are being scanned.*/ + /** + * Set of all ores that are being scanned. + */ private final ObjectSet scanOres = ObjectSet.with(Items.tungsten, Items.coal, Items.lead, Items.thorium, Items.titanium); - /**Stores all ore quadtrants on the map.*/ - private ObjectMap> ores; - private final ObjectSet itemSet = new ObjectSet<>(); - - /**Tags all quadrants.*/ + /** + * Stores all ore quadtrants on the map. + */ + private ObjectMap> ores; + /** + * Tags all quadrants. + */ private Bits[] structQuadrants; - /**Maps teams to a map of flagged tiles by type.*/ + /** + * Maps teams to a map of flagged tiles by type. + */ private ObjectMap> enemyMap = new ObjectMap<>(); - /**Maps teams to a map of flagged tiles by type.*/ + /** + * Maps teams to a map of flagged tiles by type. + */ private ObjectMap> allyMap = new ObjectMap<>(); - /**Empty map for invalid teams.*/ + /** + * Empty map for invalid teams. + */ private ObjectMap> emptyMap = new ObjectMap<>(); - /**Maps tile positions to their last known tile index data.*/ + /** + * Maps tile positions to their last known tile index data. + */ private IntMap typeMap = new IntMap<>(); - /**Empty array used for returning.*/ + /** + * Empty array used for returning. + */ private ObjectSet emptyArray = new ObjectSet<>(); public BlockIndexer(){ @@ -74,18 +95,18 @@ public class BlockIndexer { //create bitset for each team type that contains each quadrant structQuadrants = new Bits[Team.all.length]; - for(int i = 0; i < Team.all.length; i ++){ - structQuadrants[i] = new Bits(Mathf.ceil(world.width() / (float)structQuadrantSize) * Mathf.ceil(world.height() / (float)structQuadrantSize)); + for(int i = 0; i < Team.all.length; i++){ + structQuadrants[i] = new Bits(Mathf.ceil(world.width() / (float) structQuadrantSize) * Mathf.ceil(world.height() / (float) structQuadrantSize)); } - for(int x = 0; x < world.width(); x ++){ - for (int y = 0; y < world.height(); y++) { + for(int x = 0; x < world.width(); x++){ + for(int y = 0; y < world.height(); y++){ process(world.tile(x, y)); } } - for (int x = 0; x < quadWidth(); x++) { - for (int y = 0; y < quadHeight(); y++) { + for(int x = 0; x < quadWidth(); x++){ + for(int y = 0; y < quadHeight(); y++){ updateQuadrant(world.tile(x * structQuadrantSize, y * structQuadrantSize)); } } @@ -94,12 +115,16 @@ public class BlockIndexer { }); } - /**Get all allied blocks with a flag.*/ + /** + * Get all allied blocks with a flag. + */ public ObjectSet getAllied(Team team, BlockFlag type){ return (state.teams.get(team).ally ? allyMap : enemyMap).get(type, emptyArray); } - /**Get all enemy blocks with a flag.*/ + /** + * Get all enemy blocks with a flag. + */ public ObjectSet getEnemy(Team team, BlockFlag type){ return (!state.teams.get(team).ally ? allyMap : enemyMap).get(type, emptyArray); } @@ -108,13 +133,13 @@ public class BlockIndexer { Entity closest = null; float dst = 0; - for(int rx = Math.max((int)((x-range)/tilesize/structQuadrantSize), 0); rx <= (int)((x+range)/tilesize/structQuadrantSize) && rx < quadWidth(); rx ++){ - for(int ry = Math.max((int)((y-range)/tilesize/structQuadrantSize), 0); ry <= (int)((y+range)/tilesize/structQuadrantSize) && ry < quadHeight(); ry ++){ + for(int rx = Math.max((int) ((x - range) / tilesize / structQuadrantSize), 0); rx <= (int) ((x + range) / tilesize / structQuadrantSize) && rx < quadWidth(); rx++){ + for(int ry = Math.max((int) ((y - range) / tilesize / structQuadrantSize), 0); ry <= (int) ((y + range) / tilesize / structQuadrantSize) && ry < quadHeight(); ry++){ if(!getQuad(team, rx, ry)) continue; - for(int tx = rx * structQuadrantSize; tx < (rx + 1) * structQuadrantSize && tx < world.width(); tx ++){ - for(int ty = ry * structQuadrantSize; ty < (ry + 1) * structQuadrantSize && ty < world.height(); ty ++ ){ + for(int tx = rx * structQuadrantSize; tx < (rx + 1) * structQuadrantSize && tx < world.width(); tx++){ + for(int ty = ry * structQuadrantSize; ty < (ry + 1) * structQuadrantSize && ty < world.height(); ty++){ Tile other = world.tile(tx, ty); if(other == null || other.entity == null || !pred.test(other)) continue; @@ -134,22 +159,26 @@ public class BlockIndexer { return (TileEntity) closest; } - /**Returns a set of tiles that have ores of the specified type nearby. + /** + * Returns a set of tiles that have ores of the specified type nearby. * While each tile in the set is not guaranteed to have an ore directly on it, * each tile will at least have an ore within {@link #oreQuadrantSize} / 2 blocks of it. - * Only specific ore types are scanned. See {@link #scanOres}.*/ + * Only specific ore types are scanned. See {@link #scanOres}. + */ public ObjectSet getOrePositions(Item item){ return ores.get(item, emptyArray); } - /**Find the closest ore block relative to a position.*/ + /** + * Find the closest ore block relative to a position. + */ public Tile findClosestOre(float xp, float yp, Item item){ - Tile tile = Geometry.findClosest(xp, yp, world.indexer().getOrePositions(item)); + Tile tile = Geometry.findClosest(xp, yp, world.indexer().getOrePositions(item)); if(tile == null) return null; - for (int x = Math.max(0, tile.x - oreQuadrantSize/2); x < tile.x + oreQuadrantSize/2 && x < world.width(); x++) { - for (int y = Math.max(0, tile.y - oreQuadrantSize/2); y < tile.y + oreQuadrantSize/2 && y < world.height(); y++) { + for(int x = Math.max(0, tile.x - oreQuadrantSize / 2); x < tile.x + oreQuadrantSize / 2 && x < world.width(); x++){ + for(int y = Math.max(0, tile.y - oreQuadrantSize / 2); y < tile.y + oreQuadrantSize / 2 && y < world.height(); y++){ Tile res = world.tile(x, y); if(res.block() == Blocks.air && res.floor().drops != null && res.floor().drops.item == item){ return res; @@ -186,12 +215,12 @@ public class BlockIndexer { int quadrantY = tile.y / oreQuadrantSize; itemSet.clear(); - Tile rounded = world.tile(Mathf.clamp(quadrantX * oreQuadrantSize + oreQuadrantSize /2, 0, world.width() - 1), - Mathf.clamp(quadrantY * oreQuadrantSize + oreQuadrantSize /2, 0, world.height() - 1)); + Tile rounded = world.tile(Mathf.clamp(quadrantX * oreQuadrantSize + oreQuadrantSize / 2, 0, world.width() - 1), + Mathf.clamp(quadrantY * oreQuadrantSize + oreQuadrantSize / 2, 0, world.height() - 1)); //find all items that this quadrant contains - for (int x = quadrantX * structQuadrantSize; x < world.width() && x < (quadrantX + 1) * structQuadrantSize; x++) { - for (int y = quadrantY * structQuadrantSize; y < world.height() && y < (quadrantY + 1) * structQuadrantSize; y++) { + for(int x = quadrantX * structQuadrantSize; x < world.width() && x < (quadrantX + 1) * structQuadrantSize; x++){ + for(int y = quadrantY * structQuadrantSize; y < world.height() && y < (quadrantY + 1) * structQuadrantSize; y++){ Tile result = world.tile(x, y); if(result.block().drops == null || !scanOres.contains(result.block().drops.item)) continue; @@ -200,7 +229,7 @@ public class BlockIndexer { } //update quadrant at this position - for (Item item : scanOres){ + for(Item item : scanOres){ ObjectSet set = ores.get(item); //update quadrant status depending on whether the item is in it @@ -219,7 +248,7 @@ public class BlockIndexer { int index = quadrantX + quadrantY * quadWidth(); //Log.info("Updating quadrant: {0} {1}", quadrantX, quadrantY); - for(TeamData data : state.teams.getTeams()) { + for(TeamData data : state.teams.getTeams()){ //fast-set this quadrant to 'occupied' if the tile just placed is already of this team if(tile.getTeam() == data.team && tile.entity != null){ @@ -230,8 +259,8 @@ public class BlockIndexer { structQuadrants[data.team.ordinal()].clear(index); outer: - for (int x = quadrantX * structQuadrantSize; x < world.width() && x < (quadrantX + 1) * structQuadrantSize; x++) { - for (int y = quadrantY * structQuadrantSize; y < world.height() && y < (quadrantY + 1) * structQuadrantSize; y++) { + for(int x = quadrantX * structQuadrantSize; x < world.width() && x < (quadrantX + 1) * structQuadrantSize; x++){ + for(int y = quadrantY * structQuadrantSize; y < world.height() && y < (quadrantY + 1) * structQuadrantSize; y++){ Tile result = world.tile(x, y); //when a targetable block is found, mark this quadrant as occupied and stop searching if(result.entity != null && result.getTeam() == data.team){ @@ -244,16 +273,16 @@ public class BlockIndexer { } private boolean getQuad(Team team, int quadrantX, int quadrantY){ - int index = quadrantX + quadrantY * Mathf.ceil(world.width() / (float)structQuadrantSize); + int index = quadrantX + quadrantY * Mathf.ceil(world.width() / (float) structQuadrantSize); return structQuadrants[team.ordinal()].get(index); } private int quadWidth(){ - return Mathf.ceil(world.width() / (float)structQuadrantSize); + return Mathf.ceil(world.width() / (float) structQuadrantSize); } private int quadHeight(){ - return Mathf.ceil(world.height() / (float)structQuadrantSize); + return Mathf.ceil(world.height() / (float) structQuadrantSize); } private ObjectMap> getMap(Team team){ @@ -269,10 +298,10 @@ public class BlockIndexer { ores.put(item, new ObjectSet<>()); } - for(int x = 0; x < world.width(); x ++){ - for (int y = 0; y < world.height(); y++) { - int qx = (x/ oreQuadrantSize); - int qy = (y/ oreQuadrantSize); + for(int x = 0; x < world.width(); x++){ + for(int y = 0; y < world.height(); y++){ + int qx = (x / oreQuadrantSize); + int qy = (y / oreQuadrantSize); Tile tile = world.tile(x, y); @@ -280,8 +309,8 @@ public class BlockIndexer { if(tile.floor().drops != null && scanOres.contains(tile.floor().drops.item) && tile.block() == Blocks.air){ ores.get(tile.floor().drops.item).add(world.tile( //make sure to clamp quadrant middle position, since it might go off bounds - Mathf.clamp(qx * oreQuadrantSize + oreQuadrantSize /2, 0, world.width() - 1), - Mathf.clamp(qy * oreQuadrantSize + oreQuadrantSize /2, 0, world.height() - 1))); + Mathf.clamp(qx * oreQuadrantSize + oreQuadrantSize / 2, 0, world.width() - 1), + Mathf.clamp(qy * oreQuadrantSize + oreQuadrantSize / 2, 0, world.height() - 1))); } } } @@ -291,7 +320,7 @@ public class BlockIndexer { public final EnumSet flags; public final Team team; - public TileIndex(EnumSet flags, Team team) { + public TileIndex(EnumSet flags, Team team){ this.flags = flags; this.team = team; } diff --git a/core/src/io/anuke/mindustry/ai/Pathfinder.java b/core/src/io/anuke/mindustry/ai/Pathfinder.java index 81c6cf01c9..f3290674be 100644 --- a/core/src/io/anuke/mindustry/ai/Pathfinder.java +++ b/core/src/io/anuke/mindustry/ai/Pathfinder.java @@ -20,7 +20,7 @@ import io.anuke.ucore.util.Log; import static io.anuke.mindustry.Vars.state; import static io.anuke.mindustry.Vars.world; -public class Pathfinder { +public class Pathfinder{ private long maxUpdate = TimeUtils.millisToNanos(4); private PathData[] paths; private IntArray blocked = new IntArray(); @@ -56,7 +56,7 @@ public class Pathfinder { Tile target = null; float tl = 0f; - for(GridPoint2 point : Geometry.d8) { + for(GridPoint2 point : Geometry.d8){ int dx = tile.x + point.x, dy = tile.y + point.y; Tile other = world.tile(dx, dy); @@ -89,16 +89,16 @@ public class Pathfinder { } private void update(Tile tile, Team team){ - if(paths[team.ordinal()] != null) { + if(paths[team.ordinal()] != null){ PathData path = paths[team.ordinal()]; if(!passable(tile, team)){ path.weights[tile.x][tile.y] = Float.MAX_VALUE; } - path.search ++; + path.search++; - if(path.lastSearchTime + 1000/60*3 > TimeUtils.millis()){ + if(path.lastSearchTime + 1000 / 60 * 3 > TimeUtils.millis()){ path.frontier.clear(); } @@ -115,17 +115,17 @@ public class Pathfinder { private void createFor(Team team){ PathData path = new PathData(); - path.search ++; + path.search++; path.frontier.ensureCapacity((world.width() + world.height()) * 3); paths[team.ordinal()] = path; - for (int x = 0; x < world.width(); x++) { - for (int y = 0; y < world.height(); y++) { + for(int x = 0; x < world.width(); x++){ + for(int y = 0; y < world.height(); y++){ Tile tile = world.tile(x, y); - if (tile.block().flags != null && state.teams.areEnemies(tile.getTeam(), team) - && tile.block().flags.contains(BlockFlag.target)) { + if(tile.block().flags != null && state.teams.areEnemies(tile.getTeam(), team) + && tile.block().flags.contains(BlockFlag.target)){ path.frontier.addFirst(tile); path.weights[x][y] = 0; path.searches[x][y] = path.search; @@ -143,20 +143,20 @@ public class Pathfinder { long start = TimeUtils.nanoTime(); - while (path.frontier.size > 0 && (nsToRun < 0 || TimeUtils.timeSinceNanos(start) <= nsToRun)) { + while(path.frontier.size > 0 && (nsToRun < 0 || TimeUtils.timeSinceNanos(start) <= nsToRun)){ Tile tile = path.frontier.removeLast(); float cost = path.weights[tile.x][tile.y]; - if (cost < Float.MAX_VALUE) { - for (GridPoint2 point : Geometry.d4) { + if(cost < Float.MAX_VALUE){ + for(GridPoint2 point : Geometry.d4){ int dx = tile.x + point.x, dy = tile.y + point.y; Tile other = world.tile(dx, dy); - if (other != null && (path.weights[dx][dy] > cost + 1 || path.searches[dx][dy] < path.search) + if(other != null && (path.weights[dx][dy] > cost + 1 || path.searches[dx][dy] < path.search) && passable(other, team)){ path.frontier.addFirst(world.tile(dx, dy)); - path.weights[dx][dy] = cost + other.cost/2f; + path.weights[dx][dy] = cost + other.cost / 2f; path.searches[dx][dy] = path.search; } } diff --git a/core/src/io/anuke/mindustry/ai/WaveSpawner.java b/core/src/io/anuke/mindustry/ai/WaveSpawner.java index ed9b8dd60d..96635c2576 100644 --- a/core/src/io/anuke/mindustry/ai/WaveSpawner.java +++ b/core/src/io/anuke/mindustry/ai/WaveSpawner.java @@ -18,7 +18,7 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.*; -public class WaveSpawner { +public class WaveSpawner{ private static final int quadsize = 4; private Bits quadrants; @@ -34,28 +34,28 @@ public class WaveSpawner { public void write(DataOutput output) throws IOException{ output.writeShort(flySpawns.size); - for (FlyerSpawn spawn : flySpawns){ + for(FlyerSpawn spawn : flySpawns){ output.writeFloat(spawn.angle); } output.writeShort(groundSpawns.size); - for (GroundSpawn spawn : groundSpawns){ - output.writeShort((short)spawn.x); - output.writeShort((short)spawn.y); + for(GroundSpawn spawn : groundSpawns){ + output.writeShort((short) spawn.x); + output.writeShort((short) spawn.y); } } public void read(DataInput input) throws IOException{ short flya = input.readShort(); - - for (int i = 0; i < flya; i++) { + + for(int i = 0; i < flya; i++){ FlyerSpawn spawn = new FlyerSpawn(); spawn.angle = input.readFloat(); flySpawns.add(spawn); } - + short grounda = input.readShort(); - for (int i = 0; i < grounda; i++) { + for(int i = 0; i < grounda; i++){ GroundSpawn spawn = new GroundSpawn(); spawn.x = input.readShort(); spawn.y = input.readShort(); @@ -80,13 +80,13 @@ public class WaveSpawner { int addGround = groundGroups - groundSpawns.size, addFly = flyGroups - flySpawns.size; //add extra groups if the total exceeds it - for (int i = 0; i < addGround; i++) { + for(int i = 0; i < addGround; i++){ GroundSpawn spawn = new GroundSpawn(); findLocation(spawn); groundSpawns.add(spawn); } - for (int i = 0; i < addFly; i++) { + for(int i = 0; i < addFly; i++){ FlyerSpawn spawn = new FlyerSpawn(); findLocation(spawn); flySpawns.add(spawn); @@ -99,7 +99,7 @@ public class WaveSpawner { int groups = group.getGroupsSpawned(state.wave); int spawned = group.getUnitsSpawned(state.wave); - for (int i = 0; i < groups; i++) { + for(int i = 0; i < groups; i++){ Squad squad = new Squad(); float spawnX, spawnY; float spread; @@ -109,11 +109,11 @@ public class WaveSpawner { //TODO verify flyer spawn float margin = 40f; //how far away from the edge flying units spawn - spawnX = world.width() *tilesize/2f + Mathf.sqrwavex(spawn.angle) * (world.width()/2f*tilesize + margin); - spawnY = world.height() * tilesize/2f + Mathf.sqrwavey(spawn.angle) * (world.height()/2f*tilesize + margin); + spawnX = world.width() * tilesize / 2f + Mathf.sqrwavex(spawn.angle) * (world.width() / 2f * tilesize + margin); + spawnY = world.height() * tilesize / 2f + Mathf.sqrwavey(spawn.angle) * (world.height() / 2f * tilesize + margin); spread = margin / 1.5f; - flyCount ++; + flyCount++; }else{ GroundSpawn spawn = groundSpawns.get(groundCount); checkQuadrant(spawn.x, spawn.y); @@ -121,14 +121,14 @@ public class WaveSpawner { findLocation(spawn); } - spawnX = spawn.x * quadsize * tilesize + quadsize * tilesize/2f; - spawnY = spawn.y * quadsize * tilesize + quadsize * tilesize/2f; - spread = quadsize*tilesize/3f; + spawnX = spawn.x * quadsize * tilesize + quadsize * tilesize / 2f; + spawnY = spawn.y * quadsize * tilesize + quadsize * tilesize / 2f; + spread = quadsize * tilesize / 3f; - groundCount ++; + groundCount++; } - for (int j = 0; j < spawned; j++) { + for(int j = 0; j < spawned; j++){ BaseUnit unit = group.createUnit(Team.red); unit.setWave(); unit.setSquad(squad); @@ -140,19 +140,19 @@ public class WaveSpawner { } public void checkAllQuadrants(){ - for(int x = 0; x < quadWidth(); x ++){ - for(int y = 0; y < quadHeight(); y ++){ + for(int x = 0; x < quadWidth(); x++){ + for(int y = 0; y < quadHeight(); y++){ checkQuadrant(x, y); } } } - + private void checkQuadrant(int quadx, int quady){ setQuad(quadx, quady, true); outer: - for (int x = quadx * quadsize; x < world.width() && x < (quadx + 1)*quadsize; x++) { - for (int y = quady * quadsize; y < world.height() && y < (quady + 1)*quadsize; y++) { + for(int x = quadx * quadsize; x < world.width() && x < (quadx + 1) * quadsize; x++){ + for(int y = quady * quadsize; y < world.height() && y < (quady + 1) * quadsize; y++){ Tile tile = world.tile(x, y); if(tile == null || tile.solid() || world.pathfinder().getValueforTeam(Team.red, x, y) == Float.MAX_VALUE){ @@ -190,7 +190,7 @@ public class WaveSpawner { spawn.x = -1; spawn.y = -1; - int shellWidth = quadWidth()*2 + quadHeight() * 2 * 6; + int shellWidth = quadWidth() * 2 + quadHeight() * 2 * 6; shellWidth = Math.min(quadWidth() * quadHeight() / 4, shellWidth); Mathf.traverseSpiral(quadWidth(), quadHeight(), Mathf.random(shellWidth), (x, y) -> { @@ -210,11 +210,11 @@ public class WaveSpawner { } private int quadWidth(){ - return Mathf.ceil(world.width() / (float)quadsize); + return Mathf.ceil(world.width() / (float) quadsize); } private int quadHeight(){ - return Mathf.ceil(world.height() / (float)quadsize); + return Mathf.ceil(world.height() / (float) quadsize); } private class FlyerSpawn{ diff --git a/core/src/io/anuke/mindustry/content/AmmoTypes.java b/core/src/io/anuke/mindustry/content/AmmoTypes.java index eb8fefb1a0..1c784a4736 100644 --- a/core/src/io/anuke/mindustry/content/AmmoTypes.java +++ b/core/src/io/anuke/mindustry/content/AmmoTypes.java @@ -9,7 +9,7 @@ import io.anuke.mindustry.game.Content; import io.anuke.mindustry.type.AmmoType; import io.anuke.mindustry.type.ContentList; -public class AmmoTypes implements ContentList { +public class AmmoTypes implements ContentList{ public static AmmoType bulletTungsten, bulletLead, bulletCarbide, bulletThorium, bulletSilicon, bulletPyratite, shotgunTungsten, bombExplosive, bombIncendiary, bombOil, shellCarbide, flamerThermite, weaponMissile, flakLead, flakExplosive, flakPlastic, flakSurge, missileExplosive, missileIncindiary, missileSurge, @@ -17,41 +17,41 @@ public class AmmoTypes implements ContentList { basicFlame, lancerLaser, lightning, spectreLaser, meltdownLaser, fuseShotgun, oil, water, lava, cryofluid; @Override - public void load() { + public void load(){ //weapon specific - shotgunTungsten = new AmmoType(Items.tungsten, WeaponBullets.tungstenShotgun, 2) {{ + shotgunTungsten = new AmmoType(Items.tungsten, WeaponBullets.tungstenShotgun, 2){{ shootEffect = ShootFx.shootBig; smokeEffect = ShootFx.shootBigSmoke; recoil = 1f; }}; - shellCarbide = new AmmoType(Items.carbide, WeaponBullets.shellCarbide, 2) {{ + shellCarbide = new AmmoType(Items.carbide, WeaponBullets.shellCarbide, 2){{ shootEffect = ShootFx.shootBig; smokeEffect = ShootFx.shootBigSmoke; }}; - bombExplosive = new AmmoType(Items.blastCompound, WeaponBullets.bombExplosive, 3) {{ + bombExplosive = new AmmoType(Items.blastCompound, WeaponBullets.bombExplosive, 3){{ shootEffect = Fx.none; smokeEffect = Fx.none; }}; - bombIncendiary = new AmmoType(Items.pyratite, WeaponBullets.bombIncendiary, 3) {{ + bombIncendiary = new AmmoType(Items.pyratite, WeaponBullets.bombIncendiary, 3){{ shootEffect = Fx.none; smokeEffect = Fx.none; }}; - bombOil = new AmmoType(Items.coal, WeaponBullets.bombOil, 3) {{ + bombOil = new AmmoType(Items.coal, WeaponBullets.bombOil, 3){{ shootEffect = Fx.none; smokeEffect = Fx.none; }}; - flamerThermite = new AmmoType(Items.pyratite, TurretBullets.basicFlame, 3) {{ + flamerThermite = new AmmoType(Items.pyratite, TurretBullets.basicFlame, 3){{ shootEffect = ShootFx.shootSmallFlame; }}; - weaponMissile = new AmmoType(Items.carbide, MissileBullets.javelin, 2) {{ + weaponMissile = new AmmoType(Items.carbide, MissileBullets.javelin, 2){{ shootEffect = BulletFx.hitBulletSmall; smokeEffect = Fx.none; reloadMultiplier = 1.2f; @@ -59,37 +59,37 @@ public class AmmoTypes implements ContentList { //bullets - bulletLead = new AmmoType(Items.lead, StandardBullets.lead, 5) {{ + bulletLead = new AmmoType(Items.lead, StandardBullets.lead, 5){{ shootEffect = ShootFx.shootSmall; smokeEffect = ShootFx.shootSmallSmoke; reloadMultiplier = 1.6f; inaccuracy = 5f; }}; - bulletTungsten = new AmmoType(Items.tungsten, StandardBullets.tungsten, 2) {{ + bulletTungsten = new AmmoType(Items.tungsten, StandardBullets.tungsten, 2){{ shootEffect = ShootFx.shootSmall; smokeEffect = ShootFx.shootSmallSmoke; reloadMultiplier = 0.8f; }}; - bulletCarbide = new AmmoType(Items.carbide, StandardBullets.carbide, 2) {{ + bulletCarbide = new AmmoType(Items.carbide, StandardBullets.carbide, 2){{ shootEffect = ShootFx.shootSmall; smokeEffect = ShootFx.shootSmallSmoke; reloadMultiplier = 0.6f; }}; - bulletThorium = new AmmoType(Items.thorium, StandardBullets.thorium, 2) {{ + bulletThorium = new AmmoType(Items.thorium, StandardBullets.thorium, 2){{ shootEffect = ShootFx.shootBig; smokeEffect = ShootFx.shootBigSmoke; }}; - bulletSilicon = new AmmoType(Items.silicon, StandardBullets.homing, 5) {{ + bulletSilicon = new AmmoType(Items.silicon, StandardBullets.homing, 5){{ shootEffect = ShootFx.shootSmall; smokeEffect = ShootFx.shootSmallSmoke; reloadMultiplier = 1.4f; }}; - bulletPyratite = new AmmoType(Items.pyratite, StandardBullets.tracer, 3) {{ + bulletPyratite = new AmmoType(Items.pyratite, StandardBullets.tracer, 3){{ shootEffect = ShootFx.shootSmall; smokeEffect = ShootFx.shootSmallSmoke; inaccuracy = 3f; @@ -97,71 +97,71 @@ public class AmmoTypes implements ContentList { //flak - flakLead = new AmmoType(Items.lead, FlakBullets.lead, 5) {{ + flakLead = new AmmoType(Items.lead, FlakBullets.lead, 5){{ shootEffect = ShootFx.shootSmall; smokeEffect = ShootFx.shootSmallSmoke; }}; - flakExplosive = new AmmoType(Items.blastCompound, FlakBullets.explosive, 5) {{ + flakExplosive = new AmmoType(Items.blastCompound, FlakBullets.explosive, 5){{ shootEffect = ShootFx.shootSmall; smokeEffect = ShootFx.shootSmallSmoke; }}; - flakPlastic = new AmmoType(Items.plastanium, FlakBullets.plastic, 5) {{ + flakPlastic = new AmmoType(Items.plastanium, FlakBullets.plastic, 5){{ shootEffect = ShootFx.shootSmall; smokeEffect = ShootFx.shootSmallSmoke; }}; - flakSurge = new AmmoType(Items.surgealloy, FlakBullets.surge, 5) {{ + flakSurge = new AmmoType(Items.surgealloy, FlakBullets.surge, 5){{ shootEffect = ShootFx.shootSmall; smokeEffect = ShootFx.shootSmallSmoke; }}; //missiles - missileExplosive = new AmmoType(Items.blastCompound, MissileBullets.explosive, 1) {{ + missileExplosive = new AmmoType(Items.blastCompound, MissileBullets.explosive, 1){{ shootEffect = ShootFx.shootBig2; smokeEffect = ShootFx.shootBigSmoke2; reloadMultiplier = 1.2f; }}; - missileIncindiary = new AmmoType(Items.pyratite, MissileBullets.incindiary, 1) {{ + missileIncindiary = new AmmoType(Items.pyratite, MissileBullets.incindiary, 1){{ shootEffect = ShootFx.shootBig2; smokeEffect = ShootFx.shootBigSmoke2; reloadMultiplier = 1.0f; }}; - missileSurge = new AmmoType(Items.surgealloy, MissileBullets.surge, 1) {{ + missileSurge = new AmmoType(Items.surgealloy, MissileBullets.surge, 1){{ shootEffect = ShootFx.shootBig2; smokeEffect = ShootFx.shootBigSmoke2; }}; //artillery - artilleryCarbide = new AmmoType(Items.carbide, ArtilleryBullets.carbide, 2) {{ + artilleryCarbide = new AmmoType(Items.carbide, ArtilleryBullets.carbide, 2){{ shootEffect = ShootFx.shootBig2; smokeEffect = ShootFx.shootBigSmoke2; }}; - artilleryPlastic = new AmmoType(Items.plastanium, ArtilleryBullets.plastic, 2) {{ + artilleryPlastic = new AmmoType(Items.plastanium, ArtilleryBullets.plastic, 2){{ shootEffect = ShootFx.shootBig2; smokeEffect = ShootFx.shootBigSmoke2; reloadMultiplier = 1.4f; }}; - artilleryHoming = new AmmoType(Items.silicon, ArtilleryBullets.homing, 1) {{ + artilleryHoming = new AmmoType(Items.silicon, ArtilleryBullets.homing, 1){{ shootEffect = ShootFx.shootBig2; smokeEffect = ShootFx.shootBigSmoke2; reloadMultiplier = 0.9f; }}; - artilleryIncindiary = new AmmoType(Items.pyratite, ArtilleryBullets.incindiary, 2) {{ + artilleryIncindiary = new AmmoType(Items.pyratite, ArtilleryBullets.incindiary, 2){{ shootEffect = ShootFx.shootBig2; smokeEffect = ShootFx.shootBigSmoke2; reloadMultiplier = 1.2f; }}; - artilleryExplosive = new AmmoType(Items.blastCompound, ArtilleryBullets.explosive, 1) {{ + artilleryExplosive = new AmmoType(Items.blastCompound, ArtilleryBullets.explosive, 1){{ shootEffect = ShootFx.shootBig2; smokeEffect = ShootFx.shootBigSmoke2; reloadMultiplier = 1.6f; @@ -169,7 +169,7 @@ public class AmmoTypes implements ContentList { //flame - basicFlame = new AmmoType(Liquids.oil, TurretBullets.basicFlame, 0.3f) {{ + basicFlame = new AmmoType(Liquids.oil, TurretBullets.basicFlame, 0.3f){{ shootEffect = ShootFx.shootSmallFlame; }}; @@ -198,7 +198,7 @@ public class AmmoTypes implements ContentList { } @Override - public Array getAll() { + public Array getAll(){ return AmmoType.all(); } } diff --git a/core/src/io/anuke/mindustry/content/Items.java b/core/src/io/anuke/mindustry/content/Items.java index eabd520782..d9de2c5513 100644 --- a/core/src/io/anuke/mindustry/content/Items.java +++ b/core/src/io/anuke/mindustry/content/Items.java @@ -12,41 +12,41 @@ public class Items implements ContentList{ biomatter, sand, blastCompound, pyratite; @Override - public void load() { + public void load(){ - stone = new Item("stone", Color.valueOf("777777")) {{ + stone = new Item("stone", Color.valueOf("777777")){{ hardness = 3; }}; - tungsten = new Item("tungsten", Color.valueOf("a0b0c8")) {{ + tungsten = new Item("tungsten", Color.valueOf("a0b0c8")){{ type = ItemType.material; hardness = 1; cost = 0.75f; }}; - lead = new Item("lead", Color.valueOf("8e85a2")) {{ + lead = new Item("lead", Color.valueOf("8e85a2")){{ type = ItemType.material; hardness = 1; cost = 0.6f; }}; - coal = new Item("coal", Color.valueOf("272727")) {{ + coal = new Item("coal", Color.valueOf("272727")){{ explosiveness = 0.2f; flammability = 0.5f; hardness = 2; }}; - carbide = new Item("carbide", Color.valueOf("e2e2e2")) {{ + carbide = new Item("carbide", Color.valueOf("e2e2e2")){{ type = ItemType.material; }}; - titanium = new Item("titanium", Color.valueOf("8da1e3")) {{ + titanium = new Item("titanium", Color.valueOf("8da1e3")){{ type = ItemType.material; hardness = 3; cost = 1.1f; }}; - thorium = new Item("thorium", Color.valueOf("f9a3c7")) {{ + thorium = new Item("thorium", Color.valueOf("f9a3c7")){{ type = ItemType.material; explosiveness = 0.1f; hardness = 4; @@ -54,49 +54,49 @@ public class Items implements ContentList{ cost = 1.2f; }}; - silicon = new Item("silicon", Color.valueOf("53565c")) {{ + silicon = new Item("silicon", Color.valueOf("53565c")){{ type = ItemType.material; cost = 0.9f; }}; - plastanium = new Item("plastanium", Color.valueOf("e9ead3")) {{ + plastanium = new Item("plastanium", Color.valueOf("e9ead3")){{ type = ItemType.material; flammability = 0.1f; explosiveness = 0.1f; cost = 1.5f; }}; - phasematter = new Item("phase-matter", Color.valueOf("f4ba6e")) {{ + phasematter = new Item("phase-matter", Color.valueOf("f4ba6e")){{ type = ItemType.material; cost = 1.5f; }}; - surgealloy = new Item("surge-alloy", Color.valueOf("b4d5c7")) {{ + surgealloy = new Item("surge-alloy", Color.valueOf("b4d5c7")){{ type = ItemType.material; }}; - biomatter = new Item("biomatter", Color.valueOf("648b55")) {{ + biomatter = new Item("biomatter", Color.valueOf("648b55")){{ flammability = 0.4f; fluxiness = 0.2f; }}; - sand = new Item("sand", Color.valueOf("e3d39e")) {{ + sand = new Item("sand", Color.valueOf("e3d39e")){{ fluxiness = 0.5f; }}; - blastCompound = new Item("blast-compound", Color.valueOf("ff795e")) {{ + blastCompound = new Item("blast-compound", Color.valueOf("ff795e")){{ flammability = 0.2f; explosiveness = 0.6f; }}; - pyratite = new Item("pyratite", Color.valueOf("ffaa5f")) {{ + pyratite = new Item("pyratite", Color.valueOf("ffaa5f")){{ flammability = 0.7f; explosiveness = 0.2f; }}; } @Override - public Array getAll() { + public Array getAll(){ return Item.all(); } } diff --git a/core/src/io/anuke/mindustry/content/Liquids.java b/core/src/io/anuke/mindustry/content/Liquids.java index b29705bb32..e1311d4a78 100644 --- a/core/src/io/anuke/mindustry/content/Liquids.java +++ b/core/src/io/anuke/mindustry/content/Liquids.java @@ -6,13 +6,13 @@ import io.anuke.mindustry.game.Content; import io.anuke.mindustry.type.ContentList; import io.anuke.mindustry.type.Liquid; -public class Liquids implements ContentList { +public class Liquids implements ContentList{ public static Liquid water, lava, oil, cryofluid; @Override - public void load() { + public void load(){ - water = new Liquid("water", Color.valueOf("486acd")) { + water = new Liquid("water", Color.valueOf("486acd")){ { heatCapacity = 0.4f; tier = 0; @@ -20,7 +20,7 @@ public class Liquids implements ContentList { } }; - lava = new Liquid("lava", Color.valueOf("e37341")) { + lava = new Liquid("lava", Color.valueOf("e37341")){ { temperature = 0.8f; viscosity = 0.8f; @@ -29,7 +29,7 @@ public class Liquids implements ContentList { } }; - oil = new Liquid("oil", Color.valueOf("313131")) { + oil = new Liquid("oil", Color.valueOf("313131")){ { viscosity = 0.7f; flammability = 0.6f; @@ -39,7 +39,7 @@ public class Liquids implements ContentList { } }; - cryofluid = new Liquid("cryofluid", Color.SKY) { + cryofluid = new Liquid("cryofluid", Color.SKY){ { heatCapacity = 0.9f; temperature = 0.25f; @@ -50,7 +50,7 @@ public class Liquids implements ContentList { } @Override - public Array getAll() { + public Array getAll(){ return Liquid.all(); } } diff --git a/core/src/io/anuke/mindustry/content/Mechs.java b/core/src/io/anuke/mindustry/content/Mechs.java index b141eabd9b..c946060560 100644 --- a/core/src/io/anuke/mindustry/content/Mechs.java +++ b/core/src/io/anuke/mindustry/content/Mechs.java @@ -8,14 +8,16 @@ import io.anuke.mindustry.type.ContentList; import io.anuke.mindustry.type.Mech; import io.anuke.mindustry.type.Upgrade; -public class Mechs implements ContentList { +public class Mechs implements ContentList{ public static Mech alpha, delta, tau, omega, dart, javelin, trident, halberd; - /**These are not new mechs, just re-assignments for convenience.*/ + /** + * These are not new mechs, just re-assignments for convenience. + */ public static Mech starterDesktop, starterMobile; @Override - public void load() { + public void load(){ alpha = new Mech("alpha-mech", false){{ drillPower = 1; @@ -85,7 +87,7 @@ public class Mechs implements ContentList { } @Override - public Array getAll() { + public Array getAll(){ return Upgrade.all(); } } diff --git a/core/src/io/anuke/mindustry/content/Recipes.java b/core/src/io/anuke/mindustry/content/Recipes.java index 43aa5e602b..9bdae7e9ef 100644 --- a/core/src/io/anuke/mindustry/content/Recipes.java +++ b/core/src/io/anuke/mindustry/content/Recipes.java @@ -12,19 +12,19 @@ import static io.anuke.mindustry.type.Category.*; public class Recipes implements ContentList{ @Override - public void load (){ + public void load(){ //WALLS new Recipe(defense, DefenseBlocks.tungstenWall, new ItemStack(Items.tungsten, 12)); - new Recipe(defense, DefenseBlocks.tungstenWallLarge, new ItemStack(Items.tungsten, 12*4)); + new Recipe(defense, DefenseBlocks.tungstenWallLarge, new ItemStack(Items.tungsten, 12 * 4)); new Recipe(defense, DefenseBlocks.carbideWall, new ItemStack(Items.carbide, 12)); - new Recipe(defense, DefenseBlocks.carbideWallLarge, new ItemStack(Items.carbide, 12*4)); + new Recipe(defense, DefenseBlocks.carbideWallLarge, new ItemStack(Items.carbide, 12 * 4)); new Recipe(defense, DefenseBlocks.thoriumWall, new ItemStack(Items.thorium, 12)); - new Recipe(defense, DefenseBlocks.thoriumWallLarge, new ItemStack(Items.thorium, 12*4)); + new Recipe(defense, DefenseBlocks.thoriumWallLarge, new ItemStack(Items.thorium, 12 * 4)); new Recipe(defense, DefenseBlocks.door, new ItemStack(Items.carbide, 12), new ItemStack(Items.silicon, 8)); - new Recipe(defense, DefenseBlocks.doorLarge, new ItemStack(Items.carbide, 12*4), new ItemStack(Items.silicon, 8*4)); + new Recipe(defense, DefenseBlocks.doorLarge, new ItemStack(Items.carbide, 12 * 4), new ItemStack(Items.silicon, 8 * 4)); //TURRETS new Recipe(weapon, TurretBlocks.duo, new ItemStack(Items.tungsten, 40)); @@ -171,7 +171,6 @@ public class Recipes implements ContentList{ new Recipe(production, ProductionBlocks.oilextractor, new ItemStack(Items.titanium, 40), new ItemStack(Items.surgealloy, 40));*/ - //new Recipe(distribution, DistributionBlocks.massDriver, new ItemStack(Items.carbide, 1)); @@ -282,7 +281,7 @@ public class Recipes implements ContentList{ } @Override - public Array getAll() { + public Array getAll(){ return Recipe.all(); } } diff --git a/core/src/io/anuke/mindustry/content/StatusEffects.java b/core/src/io/anuke/mindustry/content/StatusEffects.java index 91e46ce74e..f17656c2e3 100644 --- a/core/src/io/anuke/mindustry/content/StatusEffects.java +++ b/core/src/io/anuke/mindustry/content/StatusEffects.java @@ -3,30 +3,30 @@ package io.anuke.mindustry.content; import com.badlogic.gdx.utils.Array; import io.anuke.mindustry.content.fx.EnvironmentFx; import io.anuke.mindustry.entities.StatusController.StatusEntry; -import io.anuke.mindustry.game.Content; -import io.anuke.mindustry.type.StatusEffect; import io.anuke.mindustry.entities.Unit; +import io.anuke.mindustry.game.Content; import io.anuke.mindustry.type.ContentList; +import io.anuke.mindustry.type.StatusEffect; import io.anuke.ucore.core.Effects; import io.anuke.ucore.core.Timers; import io.anuke.ucore.util.Mathf; -public class StatusEffects implements ContentList { +public class StatusEffects implements ContentList{ public static StatusEffect none, burning, freezing, wet, melting, tarred, overdrive, shielded; @Override - public void load() { + public void load(){ none = new StatusEffect(0); - burning = new StatusEffect(4 * 60f) { + burning = new StatusEffect(4 * 60f){ { oppositeScale = 0.5f; } @Override - public StatusEntry getTransition(Unit unit, StatusEffect to, float time, float newTime, StatusEntry result) { - if (to == tarred) { + public StatusEntry getTransition(Unit unit, StatusEffect to, float time, float newTime, StatusEntry result){ + if(to == tarred){ unit.damage(1f); Effects.effect(EnvironmentFx.burning, unit.x + Mathf.range(unit.getSize() / 2f), unit.y + Mathf.range(unit.getSize() / 2f)); return result.set(this, Math.min(time + newTime, baseDuration + tarred.baseDuration)); @@ -36,45 +36,45 @@ public class StatusEffects implements ContentList { } @Override - public void update(Unit unit, float time) { + public void update(Unit unit, float time){ unit.damagePeriodic(0.04f); - if (Mathf.chance(Timers.delta() * 0.2f)) { + if(Mathf.chance(Timers.delta() * 0.2f)){ Effects.effect(EnvironmentFx.burning, unit.x + Mathf.range(unit.getSize() / 2f), unit.y + Mathf.range(unit.getSize() / 2f)); } } }; - freezing = new StatusEffect(5 * 60f) { + freezing = new StatusEffect(5 * 60f){ { oppositeScale = 0.4f; speedMultiplier = 0.7f; } @Override - public void update(Unit unit, float time) { + public void update(Unit unit, float time){ - if (Mathf.chance(Timers.delta() * 0.15f)) { + if(Mathf.chance(Timers.delta() * 0.15f)){ Effects.effect(EnvironmentFx.freezing, unit.x + Mathf.range(unit.getSize() / 2f), unit.y + Mathf.range(unit.getSize() / 2f)); } } }; - wet = new StatusEffect(3 * 60f) { + wet = new StatusEffect(3 * 60f){ { oppositeScale = 0.5f; speedMultiplier = 0.999f; } @Override - public void update(Unit unit, float time) { - if (Mathf.chance(Timers.delta() * 0.15f)) { + public void update(Unit unit, float time){ + if(Mathf.chance(Timers.delta() * 0.15f)){ Effects.effect(EnvironmentFx.wet, unit.x + Mathf.range(unit.getSize() / 2f), unit.y + Mathf.range(unit.getSize() / 2f)); } } }; - melting = new StatusEffect(5 * 60f) { + melting = new StatusEffect(5 * 60f){ { oppositeScale = 0.2f; speedMultiplier = 0.8f; @@ -82,8 +82,8 @@ public class StatusEffects implements ContentList { } @Override - public StatusEntry getTransition(Unit unit, StatusEffect to, float time, float newTime, StatusEntry result) { - if (to == tarred) { + public StatusEntry getTransition(Unit unit, StatusEffect to, float time, float newTime, StatusEntry result){ + if(to == tarred){ return result.set(this, Math.min(time + newTime / 2f, baseDuration)); } @@ -91,30 +91,30 @@ public class StatusEffects implements ContentList { } @Override - public void update(Unit unit, float time) { + public void update(Unit unit, float time){ unit.damagePeriodic(0.3f); - if (Mathf.chance(Timers.delta() * 0.2f)) { + if(Mathf.chance(Timers.delta() * 0.2f)){ Effects.effect(EnvironmentFx.melting, unit.x + Mathf.range(unit.getSize() / 2f), unit.y + Mathf.range(unit.getSize() / 2f)); } } }; - tarred = new StatusEffect(4 * 60f) { + tarred = new StatusEffect(4 * 60f){ { speedMultiplier = 0.6f; } @Override - public void update(Unit unit, float time) { - if (Mathf.chance(Timers.delta() * 0.15f)) { + public void update(Unit unit, float time){ + if(Mathf.chance(Timers.delta() * 0.15f)){ Effects.effect(EnvironmentFx.oily, unit.x + Mathf.range(unit.getSize() / 2f), unit.y + Mathf.range(unit.getSize() / 2f)); } } @Override - public StatusEntry getTransition(Unit unit, StatusEffect to, float time, float newTime, StatusEntry result) { - if (to == melting || to == burning) { + public StatusEntry getTransition(Unit unit, StatusEffect to, float time, float newTime, StatusEntry result){ + if(to == melting || to == burning){ return result.set(to, newTime + time); } @@ -122,7 +122,7 @@ public class StatusEffects implements ContentList { } }; - overdrive = new StatusEffect(6f) { + overdrive = new StatusEffect(6f){ { armorMultiplier = 0.95f; speedMultiplier = 1.05f; @@ -130,13 +130,13 @@ public class StatusEffects implements ContentList { } @Override - public void update(Unit unit, float time) { + public void update(Unit unit, float time){ //idle regen boosted unit.health += 0.01f * Timers.delta(); } }; - shielded = new StatusEffect(6f) { + shielded = new StatusEffect(6f){ { armorMultiplier = 3f; } @@ -149,7 +149,7 @@ public class StatusEffects implements ContentList { } @Override - public Array getAll() { + public Array getAll(){ return StatusEffect.all(); } } diff --git a/core/src/io/anuke/mindustry/content/UnitTypes.java b/core/src/io/anuke/mindustry/content/UnitTypes.java index 8f95a7c3c4..e17da55df6 100644 --- a/core/src/io/anuke/mindustry/content/UnitTypes.java +++ b/core/src/io/anuke/mindustry/content/UnitTypes.java @@ -6,11 +6,11 @@ import io.anuke.mindustry.entities.units.types.*; import io.anuke.mindustry.game.Content; import io.anuke.mindustry.type.ContentList; -public class UnitTypes implements ContentList { +public class UnitTypes implements ContentList{ public static UnitType drone, scout, vtol, monsoon, titan, fabricator; @Override - public void load() { + public void load(){ drone = new UnitType("drone", Drone.class, Drone::new){{ isFlying = true; drag = 0.01f; @@ -73,7 +73,7 @@ public class UnitTypes implements ContentList { } @Override - public Array getAll() { + public Array getAll(){ return UnitType.all(); } } diff --git a/core/src/io/anuke/mindustry/content/Weapons.java b/core/src/io/anuke/mindustry/content/Weapons.java index e3b2321dfa..5ccc7456fc 100644 --- a/core/src/io/anuke/mindustry/content/Weapons.java +++ b/core/src/io/anuke/mindustry/content/Weapons.java @@ -8,13 +8,13 @@ import io.anuke.mindustry.type.ContentList; import io.anuke.mindustry.type.Upgrade; import io.anuke.mindustry.type.Weapon; -public class Weapons implements ContentList { +public class Weapons implements ContentList{ public static Weapon blaster, chainBlaster, shockgun, sapper, swarmer, bomber, flakgun, flamethrower, missiles; @Override - public void load() { + public void load(){ - blaster = new Weapon("blaster") {{ + blaster = new Weapon("blaster"){{ length = 1.5f; reload = 15f; roundrobin = true; @@ -22,7 +22,7 @@ public class Weapons implements ContentList { setAmmo(AmmoTypes.bulletLead); }}; - missiles = new Weapon("missiles") {{ + missiles = new Weapon("missiles"){{ length = 1.5f; reload = 40f; shots = 2; @@ -33,7 +33,7 @@ public class Weapons implements ContentList { setAmmo(AmmoTypes.weaponMissile); }}; - chainBlaster = new Weapon("chain-blaster") {{ + chainBlaster = new Weapon("chain-blaster"){{ length = 1.5f; reload = 30f; roundrobin = true; @@ -41,7 +41,7 @@ public class Weapons implements ContentList { setAmmo(AmmoTypes.bulletLead, AmmoTypes.bulletCarbide, AmmoTypes.bulletTungsten, AmmoTypes.bulletSilicon, AmmoTypes.bulletThorium); }}; - shockgun = new Weapon("shockgun") {{ + shockgun = new Weapon("shockgun"){{ length = 1f; reload = 50f; roundrobin = true; @@ -53,7 +53,7 @@ public class Weapons implements ContentList { setAmmo(AmmoTypes.shotgunTungsten); }}; - flakgun = new Weapon("flakgun") {{ + flakgun = new Weapon("flakgun"){{ length = 1f; reload = 70f; roundrobin = true; @@ -65,7 +65,7 @@ public class Weapons implements ContentList { setAmmo(AmmoTypes.shellCarbide); }}; - flamethrower = new Weapon("flamethrower") {{ + flamethrower = new Weapon("flamethrower"){{ length = 1f; reload = 14f; roundrobin = true; @@ -74,7 +74,7 @@ public class Weapons implements ContentList { setAmmo(AmmoTypes.flamerThermite); }}; - sapper = new Weapon("sapper") {{ + sapper = new Weapon("sapper"){{ length = 1.5f; reload = 12f; roundrobin = true; @@ -82,7 +82,7 @@ public class Weapons implements ContentList { setAmmo(AmmoTypes.bulletCarbide); }}; - swarmer = new Weapon("swarmer") {{ + swarmer = new Weapon("swarmer"){{ length = 1.5f; reload = 10f; roundrobin = true; @@ -90,7 +90,7 @@ public class Weapons implements ContentList { setAmmo(AmmoTypes.bulletPyratite); }}; - bomber = new Weapon("bomber") {{ + bomber = new Weapon("bomber"){{ length = 0f; width = 2f; reload = 5f; @@ -103,7 +103,7 @@ public class Weapons implements ContentList { } @Override - public Array getAll() { + public Array getAll(){ return Upgrade.all(); } } diff --git a/core/src/io/anuke/mindustry/content/blocks/BlockList.java b/core/src/io/anuke/mindustry/content/blocks/BlockList.java index 9bf7eb92ee..e1b7819220 100644 --- a/core/src/io/anuke/mindustry/content/blocks/BlockList.java +++ b/core/src/io/anuke/mindustry/content/blocks/BlockList.java @@ -5,10 +5,10 @@ import io.anuke.mindustry.game.Content; import io.anuke.mindustry.type.ContentList; import io.anuke.mindustry.world.Block; -public abstract class BlockList implements ContentList { +public abstract class BlockList implements ContentList{ @Override - public Array getAll() { + public Array getAll(){ return Block.all(); } } diff --git a/core/src/io/anuke/mindustry/content/blocks/Blocks.java b/core/src/io/anuke/mindustry/content/blocks/Blocks.java index 86a239c042..1268255a74 100644 --- a/core/src/io/anuke/mindustry/content/blocks/Blocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/Blocks.java @@ -15,26 +15,31 @@ public class Blocks extends BlockList implements ContentList{ public static Block air, spawn, blockpart, space, metalfloor, deepwater, water, lava, oil, stone, blackstone, dirt, sand, ice, snow, grass, shrub, rock, icerock, blackrock; - @Override - public void load() { - air = new Floor("air") { + public void load(){ + air = new Floor("air"){ { blend = false; } + //don't draw - public void draw(Tile tile) {} - public void load() {} - public void init() {} + public void draw(Tile tile){ + } + + public void load(){ + } + + public void init(){ + } }; blockpart = new BlockPart(); - for(int i = 1; i <= 6; i ++){ + for(int i = 1; i <= 6; i++){ new BuildBlock("build" + i); } - space = new Floor("space") {{ + space = new Floor("space"){{ placeableOn = false; variants = 0; cacheLayer = CacheLayer.space; @@ -43,11 +48,11 @@ public class Blocks extends BlockList implements ContentList{ minimapColor = Color.valueOf("000001"); }}; - metalfloor = new Floor("metalfloor") {{ + metalfloor = new Floor("metalfloor"){{ variants = 6; }}; - deepwater = new Floor("deepwater") {{ + deepwater = new Floor("deepwater"){{ liquidColor = Color.valueOf("546bb3"); speedMultiplier = 0.2f; variants = 0; @@ -60,7 +65,7 @@ public class Blocks extends BlockList implements ContentList{ minimapColor = Color.valueOf("465a96"); }}; - water = new Floor("water") {{ + water = new Floor("water"){{ liquidColor = Color.valueOf("546bb3"); speedMultiplier = 0.5f; variants = 0; @@ -72,7 +77,7 @@ public class Blocks extends BlockList implements ContentList{ minimapColor = Color.valueOf("506eb4"); }}; - lava = new Floor("lava") {{ + lava = new Floor("lava"){{ liquidColor = Color.valueOf("ed5334"); speedMultiplier = 0.2f; damageTaken = 0.5f; @@ -85,7 +90,7 @@ public class Blocks extends BlockList implements ContentList{ minimapColor = Color.valueOf("ed5334"); }}; - oil = new Floor("oil") {{ + oil = new Floor("oil"){{ liquidColor = Color.valueOf("292929"); status = StatusEffects.tarred; statusIntensity = 1f; @@ -97,7 +102,7 @@ public class Blocks extends BlockList implements ContentList{ minimapColor = Color.valueOf("292929"); }}; - stone = new Floor("stone") {{ + stone = new Floor("stone"){{ hasOres = true; drops = new ItemStack(Items.stone, 1); blends = block -> block != this && !(block instanceof Ore); @@ -105,7 +110,7 @@ public class Blocks extends BlockList implements ContentList{ playerUnmineable = true; }}; - blackstone = new Floor("blackstone") {{ + blackstone = new Floor("blackstone"){{ drops = new ItemStack(Items.stone, 1); minimapColor = Color.valueOf("252525"); playerUnmineable = true; @@ -115,14 +120,14 @@ public class Blocks extends BlockList implements ContentList{ minimapColor = Color.valueOf("6e501e"); }}; - sand = new Floor("sand") {{ + sand = new Floor("sand"){{ drops = new ItemStack(Items.sand, 1); minimapColor = Color.valueOf("988a67"); hasOres = true; playerUnmineable = true; }}; - ice = new Floor("ice") {{ + ice = new Floor("ice"){{ dragMultiplier = 0.3f; speedMultiplier = 0.4f; minimapColor = Color.valueOf("c4e3e7"); @@ -143,15 +148,15 @@ public class Blocks extends BlockList implements ContentList{ shadow = "shrubshadow"; }}; - rock = new Rock("rock") {{ + rock = new Rock("rock"){{ variants = 2; }}; - icerock = new Rock("icerock") {{ + icerock = new Rock("icerock"){{ variants = 2; }}; - blackrock = new Rock("blackrock") {{ + blackrock = new Rock("blackrock"){{ variants = 1; }}; } diff --git a/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java b/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java index dd97e4a8b9..67ab558fdd 100644 --- a/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java @@ -10,14 +10,14 @@ import io.anuke.mindustry.type.ItemStack; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.blocks.production.*; -public class CraftingBlocks extends BlockList implements ContentList { +public class CraftingBlocks extends BlockList implements ContentList{ public static Block smelter, arcsmelter, siliconsmelter, plastaniumCompressor, phaseWeaver, alloysmelter, alloyfuser, pyratiteMixer, blastMixer, cryofluidmixer, melter, separator, centrifuge, biomatterCompressor, pulverizer, solidifier, incinerator; @Override - public void load() { - smelter = new Smelter("smelter") {{ + public void load(){ + smelter = new Smelter("smelter"){{ health = 70; result = Items.carbide; craftTime = 45f; @@ -28,7 +28,7 @@ public class CraftingBlocks extends BlockList implements ContentList { consumes.item(Items.coal); }}; - arcsmelter = new PowerSmelter("arc-smelter") {{ + arcsmelter = new PowerSmelter("arc-smelter"){{ health = 90; craftEffect = BlockFx.smeltsmoke; result = Items.carbide; @@ -42,7 +42,7 @@ public class CraftingBlocks extends BlockList implements ContentList { consumes.power(0.1f); }}; - siliconsmelter = new PowerSmelter("silicon-smelter") {{ + siliconsmelter = new PowerSmelter("silicon-smelter"){{ health = 90; craftEffect = BlockFx.smeltsmoke; result = Items.silicon; @@ -55,7 +55,7 @@ public class CraftingBlocks extends BlockList implements ContentList { consumes.power(0.05f); }}; - plastaniumCompressor = new PlastaniumCompressor("plastanium-compressor") {{ + plastaniumCompressor = new PlastaniumCompressor("plastanium-compressor"){{ hasItems = true; liquidCapacity = 60f; craftTime = 80f; @@ -72,7 +72,7 @@ public class CraftingBlocks extends BlockList implements ContentList { consumes.item(Items.titanium, 2); }}; - phaseWeaver = new PhaseWeaver("phase-weaver") {{ + phaseWeaver = new PhaseWeaver("phase-weaver"){{ health = 90; craftEffect = BlockFx.smeltsmoke; result = Items.phasematter; @@ -83,7 +83,7 @@ public class CraftingBlocks extends BlockList implements ContentList { consumes.power(0.5f); }}; - alloysmelter = new PowerSmelter("alloy-smelter") {{ + alloysmelter = new PowerSmelter("alloy-smelter"){{ health = 90; craftEffect = BlockFx.smeltsmoke; result = Items.surgealloy; @@ -97,7 +97,7 @@ public class CraftingBlocks extends BlockList implements ContentList { consumes.items(new ItemStack[]{new ItemStack(Items.titanium, 2), new ItemStack(Items.lead, 4), new ItemStack(Items.silicon, 3), new ItemStack(Items.plastanium, 2)}); }}; - alloyfuser = new PowerSmelter("alloy-fuser") {{ + alloyfuser = new PowerSmelter("alloy-fuser"){{ health = 90; craftEffect = BlockFx.smeltsmoke; result = Items.surgealloy; @@ -111,7 +111,7 @@ public class CraftingBlocks extends BlockList implements ContentList { consumes.power(0.4f); }}; - cryofluidmixer = new LiquidMixer("cryofluidmixer") {{ + cryofluidmixer = new LiquidMixer("cryofluidmixer"){{ health = 200; outputLiquid = Liquids.cryofluid; liquidPerItem = 50f; @@ -124,7 +124,7 @@ public class CraftingBlocks extends BlockList implements ContentList { consumes.liquid(Liquids.water, 0.3f); }}; - blastMixer = new GenericCrafter("blast-mixer") {{ + blastMixer = new GenericCrafter("blast-mixer"){{ itemCapacity = 20; hasItems = true; hasPower = true; @@ -137,7 +137,7 @@ public class CraftingBlocks extends BlockList implements ContentList { consumes.power(0.04f); }}; - pyratiteMixer = new PowerSmelter("pyratite-mixer") {{ + pyratiteMixer = new PowerSmelter("pyratite-mixer"){{ flameColor = Color.CLEAR; itemCapacity = 20; hasItems = true; @@ -150,7 +150,7 @@ public class CraftingBlocks extends BlockList implements ContentList { consumes.items(new ItemStack[]{new ItemStack(Items.coal, 1), new ItemStack(Items.lead, 2), new ItemStack(Items.sand, 2)}); }}; - melter = new PowerCrafter("melter") {{ + melter = new PowerCrafter("melter"){{ health = 200; outputLiquid = Liquids.lava; outputLiquidAmount = 0.75f; @@ -162,7 +162,7 @@ public class CraftingBlocks extends BlockList implements ContentList { consumes.item(Items.stone, 2); }}; - separator = new Separator("separator") {{ + separator = new Separator("separator"){{ results = new Item[]{ null, null, null, null, null, null, null, null, null, null, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, @@ -180,7 +180,7 @@ public class CraftingBlocks extends BlockList implements ContentList { consumes.liquid(Liquids.water, 0.3f); }}; - centrifuge = new Separator("centrifuge") {{ + centrifuge = new Separator("centrifuge"){{ results = new Item[]{ null, null, null, null, null, null, null, null, null, null, null, null, null, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, @@ -207,7 +207,7 @@ public class CraftingBlocks extends BlockList implements ContentList { consumes.liquid(Liquids.water, 0.5f); }}; - biomatterCompressor = new Compressor("biomattercompressor") {{ + biomatterCompressor = new Compressor("biomattercompressor"){{ liquidCapacity = 60f; itemCapacity = 50; craftTime = 25f; @@ -221,7 +221,7 @@ public class CraftingBlocks extends BlockList implements ContentList { consumes.power(0.06f); }}; - pulverizer = new Pulverizer("pulverizer") {{ + pulverizer = new Pulverizer("pulverizer"){{ itemCapacity = 40; output = Items.sand; health = 80; @@ -234,7 +234,7 @@ public class CraftingBlocks extends BlockList implements ContentList { consumes.power(0.2f); }}; - solidifier = new GenericCrafter("solidifer") {{ + solidifier = new GenericCrafter("solidifer"){{ liquidCapacity = 21f; craftTime = 14; output = Items.stone; @@ -246,7 +246,7 @@ public class CraftingBlocks extends BlockList implements ContentList { consumes.liquid(Liquids.lava, 1f); }}; - incinerator = new Incinerator("incinerator") {{ + incinerator = new Incinerator("incinerator"){{ health = 90; }}; } diff --git a/core/src/io/anuke/mindustry/content/blocks/DebugBlocks.java b/core/src/io/anuke/mindustry/content/blocks/DebugBlocks.java index ff875423f9..b23a545823 100644 --- a/core/src/io/anuke/mindustry/content/blocks/DebugBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/DebugBlocks.java @@ -28,45 +28,52 @@ import java.io.IOException; public class DebugBlocks extends BlockList implements ContentList{ public static Block powerVoid, powerInfinite, itemSource, liquidSource, itemVoid; + @Remote(targets = Loc.both, called = Loc.both, in = In.blocks, forward = true) + public static void setLiquidSourceLiquid(Player player, Tile tile, Liquid liquid){ + LiquidSourceEntity entity = tile.entity(); + entity.source = liquid; + } + @Override - public void load() { - powerVoid = new PowerBlock("powervoid") { + public void load(){ + powerVoid = new PowerBlock("powervoid"){ { powerCapacity = Float.MAX_VALUE; } }; - powerInfinite = new PowerNode("powerinfinite") { + powerInfinite = new PowerNode("powerinfinite"){ { powerCapacity = 10000f; powerSpeed = 100f; } @Override - public void update(Tile tile) { + public void update(Tile tile){ super.update(tile); tile.entity.power.amount = powerCapacity; } }; - itemSource = new Sorter("itemsource") { + itemSource = new Sorter("itemsource"){ { hasItems = true; } + @Override - public void update(Tile tile) { + public void update(Tile tile){ SorterEntity entity = tile.entity(); entity.items.set(entity.sortItem, 1); tryDump(tile, entity.sortItem); } @Override - public boolean acceptItem(Item item, Tile tile, Tile source) { + public boolean acceptItem(Item item, Tile tile, Tile source){ return false; } }; - liquidSource = new Block("liquidsource") { + liquidSource = new Block("liquidsource"){ { update = true; solid = true; @@ -76,7 +83,7 @@ public class DebugBlocks extends BlockList implements ContentList{ } @Override - public void update(Tile tile) { + public void update(Tile tile){ LiquidSourceEntity entity = tile.entity(); tile.entity.liquids.add(entity.source, liquidCapacity); @@ -84,7 +91,7 @@ public class DebugBlocks extends BlockList implements ContentList{ } @Override - public void draw(Tile tile) { + public void draw(Tile tile){ super.draw(tile); LiquidSourceEntity entity = tile.entity(); @@ -95,7 +102,7 @@ public class DebugBlocks extends BlockList implements ContentList{ } @Override - public void buildTable(Tile tile, Table table) { + public void buildTable(Tile tile, Table table){ LiquidSourceEntity entity = tile.entity(); Array items = Liquid.all(); @@ -103,8 +110,8 @@ public class DebugBlocks extends BlockList implements ContentList{ ButtonGroup group = new ButtonGroup<>(); Table cont = new Table(); - for (int i = 0; i < items.size; i++) { - if (i == 0) continue; + for(int i = 0; i < items.size; i++){ + if(i == 0) continue; final int f = i; ImageButton button = cont.addImageButton("white", "toggle", 24, () -> { CallBlocks.setLiquidSourceLiquid(null, tile, items.get(f)); @@ -112,7 +119,7 @@ public class DebugBlocks extends BlockList implements ContentList{ button.getStyle().imageUpColor = items.get(i).color; button.setChecked(entity.source.id == f); - if (i % 4 == 3) { + if(i % 4 == 3){ cont.row(); } } @@ -121,43 +128,37 @@ public class DebugBlocks extends BlockList implements ContentList{ } @Override - public TileEntity getEntity() { + public TileEntity getEntity(){ return new LiquidSourceEntity(); } }; - itemVoid = new Block("itemvoid") { + itemVoid = new Block("itemvoid"){ { update = solid = true; } @Override - public void handleItem(Item item, Tile tile, Tile source) { + public void handleItem(Item item, Tile tile, Tile source){ } @Override - public boolean acceptItem(Item item, Tile tile, Tile source) { + public boolean acceptItem(Item item, Tile tile, Tile source){ return true; } }; } - @Remote(targets = Loc.both, called = Loc.both, in = In.blocks, forward = true) - public static void setLiquidSourceLiquid(Player player, Tile tile, Liquid liquid){ - LiquidSourceEntity entity = tile.entity(); - entity.source = liquid; - } - - class LiquidSourceEntity extends TileEntity { + class LiquidSourceEntity extends TileEntity{ public Liquid source = Liquids.water; @Override - public void write(DataOutputStream stream) throws IOException { + public void write(DataOutputStream stream) throws IOException{ stream.writeByte(source.id); } @Override - public void read(DataInputStream stream) throws IOException { + public void read(DataInputStream stream) throws IOException{ source = Liquid.getByID(stream.readByte()); } } diff --git a/core/src/io/anuke/mindustry/content/blocks/DefenseBlocks.java b/core/src/io/anuke/mindustry/content/blocks/DefenseBlocks.java index 761f05b7b8..ca40928d80 100644 --- a/core/src/io/anuke/mindustry/content/blocks/DefenseBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/DefenseBlocks.java @@ -8,65 +8,65 @@ import io.anuke.mindustry.world.blocks.defense.DeflectorWall; import io.anuke.mindustry.world.blocks.defense.Door; import io.anuke.mindustry.world.blocks.defense.PhaseWall; -public class DefenseBlocks extends BlockList implements ContentList { +public class DefenseBlocks extends BlockList implements ContentList{ public static Block tungstenWall, tungstenWallLarge, carbideWall, carbideWallLarge, thoriumWall, thoriumWallLarge, door, doorLarge, deflectorwall, deflectorwalllarge, - phasewall, phasewalllarge; + phasewall, phasewalllarge; @Override - public void load() { + public void load(){ int wallHealthMultiplier = 4; - tungstenWall = new Wall("tungsten-wall") {{ + tungstenWall = new Wall("tungsten-wall"){{ health = 80 * wallHealthMultiplier; }}; - tungstenWallLarge = new Wall("tungsten-wall-large") {{ + tungstenWallLarge = new Wall("tungsten-wall-large"){{ health = 80 * 4 * wallHealthMultiplier; size = 2; }}; - carbideWall = new Wall("carbide-wall") {{ + carbideWall = new Wall("carbide-wall"){{ health = 110 * wallHealthMultiplier; }}; - carbideWallLarge = new Wall("carbide-wall-large") {{ - health = 110 * wallHealthMultiplier*4; + carbideWallLarge = new Wall("carbide-wall-large"){{ + health = 110 * wallHealthMultiplier * 4; size = 2; }}; - thoriumWall = new Wall("thorium-wall") {{ + thoriumWall = new Wall("thorium-wall"){{ health = 200 * wallHealthMultiplier; }}; - thoriumWallLarge = new Wall("thorium-wall-large") {{ - health = 200 * wallHealthMultiplier*4; + thoriumWallLarge = new Wall("thorium-wall-large"){{ + health = 200 * wallHealthMultiplier * 4; size = 2; }}; - deflectorwall = new DeflectorWall("deflector-wall") {{ + deflectorwall = new DeflectorWall("deflector-wall"){{ health = 150 * wallHealthMultiplier; }}; - deflectorwalllarge = new DeflectorWall("deflector-wall-large") {{ + deflectorwalllarge = new DeflectorWall("deflector-wall-large"){{ health = 150 * 4 * wallHealthMultiplier; size = 2; }}; - phasewall = new PhaseWall("phase-wall") {{ + phasewall = new PhaseWall("phase-wall"){{ health = 150 * wallHealthMultiplier; }}; - phasewalllarge = new PhaseWall("phase-wall-large") {{ + phasewalllarge = new PhaseWall("phase-wall-large"){{ health = 150 * 4 * wallHealthMultiplier; size = 2; regenSpeed = 0.5f; }}; - door = new Door("door") {{ + door = new Door("door"){{ health = 100 * wallHealthMultiplier; }}; - doorLarge = new Door("door-large") {{ + doorLarge = new Door("door-large"){{ openfx = BlockFx.dooropenlarge; closefx = BlockFx.doorcloselarge; health = 100 * 4 * wallHealthMultiplier; diff --git a/core/src/io/anuke/mindustry/content/blocks/DistributionBlocks.java b/core/src/io/anuke/mindustry/content/blocks/DistributionBlocks.java index cda352e0f6..f045133968 100644 --- a/core/src/io/anuke/mindustry/content/blocks/DistributionBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/DistributionBlocks.java @@ -5,50 +5,50 @@ import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.blocks.distribution.*; public class DistributionBlocks extends BlockList implements ContentList{ - public static Block conveyor, titaniumconveyor, distributor, junction, - bridgeConveyor, phaseConveyor, sorter, splitter, overflowGate, massDriver; + public static Block conveyor, titaniumconveyor, distributor, junction, + bridgeConveyor, phaseConveyor, sorter, splitter, overflowGate, massDriver; - @Override - public void load() { + @Override + public void load(){ - conveyor = new Conveyor("conveyor") {{ - health = 45; - speed = 0.03f; - }}; + conveyor = new Conveyor("conveyor"){{ + health = 45; + speed = 0.03f; + }}; - titaniumconveyor = new Conveyor("titanium-conveyor") {{ - health = 65; - speed = 0.07f; - }}; + titaniumconveyor = new Conveyor("titanium-conveyor"){{ + health = 65; + speed = 0.07f; + }}; - junction = new Junction("junction") {{ - speed = 26; - capacity = 32; - }}; + junction = new Junction("junction"){{ + speed = 26; + capacity = 32; + }}; - bridgeConveyor = new BufferedItemBridge("bridge-conveyor") {{ - range = 3; - hasPower = false; - }}; + bridgeConveyor = new BufferedItemBridge("bridge-conveyor"){{ + range = 3; + hasPower = false; + }}; - phaseConveyor = new ItemBridge("phase-conveyor") {{ - range = 7; - }}; + phaseConveyor = new ItemBridge("phase-conveyor"){{ + range = 7; + }}; - sorter = new Sorter("sorter"); + sorter = new Sorter("sorter"); - splitter = new Splitter("splitter"); + splitter = new Splitter("splitter"); - distributor = new Splitter("distributor") {{ - size = 2; - }}; + distributor = new Splitter("distributor"){{ + size = 2; + }}; - overflowGate = new OverflowGate("overflow-gate"); + overflowGate = new OverflowGate("overflow-gate"); - massDriver = new MassDriver("mass-driver"){{ - size = 3; - itemCapacity = 80; - range = 300f; - }}; - } + massDriver = new MassDriver("mass-driver"){{ + size = 3; + itemCapacity = 80; + range = 300f; + }}; + } } diff --git a/core/src/io/anuke/mindustry/content/blocks/LiquidBlocks.java b/core/src/io/anuke/mindustry/content/blocks/LiquidBlocks.java index 621fca803a..30205142f4 100644 --- a/core/src/io/anuke/mindustry/content/blocks/LiquidBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/LiquidBlocks.java @@ -9,15 +9,15 @@ public class LiquidBlocks extends BlockList implements ContentList{ public static Block mechanicalPump, rotaryPump, thermalPump, conduit, pulseConduit, liquidRouter, liquidtank, liquidJunction, bridgeConduit, phaseConduit; @Override - public void load() { + public void load(){ - mechanicalPump = new Pump("mechanical-pump") {{ + mechanicalPump = new Pump("mechanical-pump"){{ shadow = "shadow-round-1"; pumpAmount = 0.1f; tier = 0; }}; - rotaryPump = new Pump("rotary-pump") {{ + rotaryPump = new Pump("rotary-pump"){{ shadow = "shadow-rounded-2"; pumpAmount = 0.25f; consumes.power(0.015f); @@ -27,7 +27,7 @@ public class LiquidBlocks extends BlockList implements ContentList{ tier = 1; }}; - thermalPump = new Pump("thermal-pump") {{ + thermalPump = new Pump("thermal-pump"){{ pumpAmount = 0.3f; consumes.power(0.05f); liquidCapacity = 40f; @@ -35,21 +35,21 @@ public class LiquidBlocks extends BlockList implements ContentList{ tier = 2; }}; - conduit = new Conduit("conduit") {{ + conduit = new Conduit("conduit"){{ health = 45; }}; - pulseConduit = new Conduit("pulse-conduit") {{ + pulseConduit = new Conduit("pulse-conduit"){{ liquidCapacity = 16f; liquidFlowFactor = 4.9f; health = 90; }}; - liquidRouter = new LiquidRouter("liquid-router") {{ + liquidRouter = new LiquidRouter("liquid-router"){{ liquidCapacity = 40f; }}; - liquidtank = new LiquidRouter("liquid-tank") {{ + liquidtank = new LiquidRouter("liquid-tank"){{ size = 3; liquidCapacity = 1500f; health = 500; @@ -57,12 +57,12 @@ public class LiquidBlocks extends BlockList implements ContentList{ liquidJunction = new LiquidJunction("liquid-junction"); - bridgeConduit = new LiquidExtendingBridge("bridge-conduit") {{ + bridgeConduit = new LiquidExtendingBridge("bridge-conduit"){{ range = 3; hasPower = false; }}; - phaseConduit = new LiquidBridge("phase-conduit") {{ + phaseConduit = new LiquidBridge("phase-conduit"){{ range = 7; }}; } diff --git a/core/src/io/anuke/mindustry/content/blocks/OreBlocks.java b/core/src/io/anuke/mindustry/content/blocks/OreBlocks.java index cd1764586b..4cb86032f8 100644 --- a/core/src/io/anuke/mindustry/content/blocks/OreBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/OreBlocks.java @@ -7,11 +7,18 @@ import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.blocks.Floor; import io.anuke.mindustry.world.blocks.OreBlock; -public class OreBlocks extends BlockList { +public class OreBlocks extends BlockList{ private static final ObjectMap> oreBlockMap = new ObjectMap<>(); + public static Block get(Block floor, Item item){ + if(!oreBlockMap.containsKey(item)) throw new IllegalArgumentException("Item '" + item + "' is not an ore!"); + if(!oreBlockMap.get(item).containsKey(floor)) + throw new IllegalArgumentException("Block '" + floor.name + "' does not support ores!"); + return oreBlockMap.get(item).get(floor); + } + @Override - public void load() { + public void load(){ Item[] ores = {Items.tungsten, Items.lead, Items.coal, Items.titanium, Items.thorium}; for(Item item : ores){ @@ -25,10 +32,4 @@ public class OreBlocks extends BlockList { } } } - - public static Block get(Block floor, Item item){ - if(!oreBlockMap.containsKey(item)) throw new IllegalArgumentException("Item '" + item + "' is not an ore!"); - if(!oreBlockMap.get(item).containsKey(floor)) throw new IllegalArgumentException("Block '" + floor.name + "' does not support ores!"); - return oreBlockMap.get(item).get(floor); - } } diff --git a/core/src/io/anuke/mindustry/content/blocks/PowerBlocks.java b/core/src/io/anuke/mindustry/content/blocks/PowerBlocks.java index 223a88efdb..df8ead1a55 100644 --- a/core/src/io/anuke/mindustry/content/blocks/PowerBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/PowerBlocks.java @@ -7,19 +7,19 @@ import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.blocks.distribution.WarpGate; import io.anuke.mindustry.world.blocks.power.*; -public class PowerBlocks extends BlockList implements ContentList { +public class PowerBlocks extends BlockList implements ContentList{ public static Block combustionGenerator, thermalGenerator, turbineGenerator, rtgGenerator, solarPanel, largeSolarPanel, nuclearReactor, fusionReactor, battery, batteryLarge, powerNode, powerNodeLarge, warpGate; @Override - public void load() { - combustionGenerator = new BurnerGenerator("combustion-generator") {{ + public void load(){ + combustionGenerator = new BurnerGenerator("combustion-generator"){{ powerOutput = 0.09f; powerCapacity = 40f; itemDuration = 40f; }}; - thermalGenerator = new LiquidHeatGenerator("thermal-generator") {{ + thermalGenerator = new LiquidHeatGenerator("thermal-generator"){{ maxLiquidGenerate = 0.5f; powerPerLiquid = 0.08f; powerCapacity = 40f; @@ -28,7 +28,7 @@ public class PowerBlocks extends BlockList implements ContentList { size = 2; }}; - turbineGenerator = new TurbineGenerator("turbine-generator") {{ + turbineGenerator = new TurbineGenerator("turbine-generator"){{ powerOutput = 0.28f; powerCapacity = 40f; itemDuration = 30f; @@ -37,46 +37,46 @@ public class PowerBlocks extends BlockList implements ContentList { size = 2; }}; - rtgGenerator = new DecayGenerator("rtg-generator") {{ + rtgGenerator = new DecayGenerator("rtg-generator"){{ powerCapacity = 40f; powerOutput = 0.02f; itemDuration = 500f; }}; - solarPanel = new SolarGenerator("solar-panel") {{ + solarPanel = new SolarGenerator("solar-panel"){{ generation = 0.0045f; }}; - largeSolarPanel = new SolarGenerator("solar-panel-large") {{ + largeSolarPanel = new SolarGenerator("solar-panel-large"){{ size = 3; generation = 0.055f; }}; - nuclearReactor = new NuclearReactor("nuclear-reactor") {{ + nuclearReactor = new NuclearReactor("nuclear-reactor"){{ size = 3; health = 700; powerMultiplier = 0.8f; }}; - fusionReactor = new FusionReactor("fusion-reactor") {{ + fusionReactor = new FusionReactor("fusion-reactor"){{ size = 4; health = 600; }}; - battery = new PowerDistributor("battery") {{ + battery = new PowerDistributor("battery"){{ powerCapacity = 320f; }}; - batteryLarge = new PowerDistributor("battery-large") {{ + batteryLarge = new PowerDistributor("battery-large"){{ size = 3; powerCapacity = 2000f; }}; - powerNode = new PowerNode("power-node") {{ + powerNode = new PowerNode("power-node"){{ shadow = "shadow-round-1"; }}; - powerNodeLarge = new PowerNode("power-node-large") {{ + powerNodeLarge = new PowerNode("power-node-large"){{ size = 2; powerSpeed = 1f; maxNodes = 5; diff --git a/core/src/io/anuke/mindustry/content/blocks/ProductionBlocks.java b/core/src/io/anuke/mindustry/content/blocks/ProductionBlocks.java index 0e3ae62edd..c07dc66303 100644 --- a/core/src/io/anuke/mindustry/content/blocks/ProductionBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/ProductionBlocks.java @@ -11,22 +11,22 @@ import io.anuke.mindustry.world.blocks.production.Drill; import io.anuke.mindustry.world.blocks.production.Fracker; import io.anuke.mindustry.world.blocks.production.SolidPump; -public class ProductionBlocks extends BlockList implements ContentList { +public class ProductionBlocks extends BlockList implements ContentList{ public static Block tungstenDrill, carbideDrill, laserdrill, blastdrill, plasmadrill, waterextractor, oilextractor, cultivator; @Override - public void load() { - tungstenDrill = new Drill("tungsten-drill") {{ + public void load(){ + tungstenDrill = new Drill("tungsten-drill"){{ tier = 2; drillTime = 340; }}; - carbideDrill = new Drill("carbide-drill") {{ + carbideDrill = new Drill("carbide-drill"){{ tier = 3; drillTime = 280; }}; - laserdrill = new Drill("laser-drill") {{ + laserdrill = new Drill("laser-drill"){{ drillTime = 180; size = 2; hasPower = true; @@ -37,7 +37,7 @@ public class ProductionBlocks extends BlockList implements ContentList { consumes.power(0.2f); }}; - blastdrill = new Drill("blast-drill") {{ + blastdrill = new Drill("blast-drill"){{ drillTime = 120; size = 3; drawRim = true; @@ -52,7 +52,7 @@ public class ProductionBlocks extends BlockList implements ContentList { consumes.power(0.5f); }}; - plasmadrill = new Drill("plasma-drill") {{ + plasmadrill = new Drill("plasma-drill"){{ heatColor = Color.valueOf("ff461b"); drillTime = 90; size = 4; @@ -69,7 +69,7 @@ public class ProductionBlocks extends BlockList implements ContentList { consumes.power(0.7f); }}; - waterextractor = new SolidPump("water-extractor") {{ + waterextractor = new SolidPump("water-extractor"){{ result = Liquids.water; pumpAmount = 0.1f; size = 2; @@ -79,7 +79,7 @@ public class ProductionBlocks extends BlockList implements ContentList { consumes.power(0.2f); }}; - oilextractor = new Fracker("oil-extractor") {{ + oilextractor = new Fracker("oil-extractor"){{ result = Liquids.oil; updateEffect = BlockFx.pulverize; liquidCapacity = 50f; @@ -93,7 +93,7 @@ public class ProductionBlocks extends BlockList implements ContentList { consumes.liquid(Liquids.water, 0.3f); }}; - cultivator = new Cultivator("cultivator") {{ + cultivator = new Cultivator("cultivator"){{ result = Items.biomatter; drillTime = 260; size = 2; diff --git a/core/src/io/anuke/mindustry/content/blocks/StorageBlocks.java b/core/src/io/anuke/mindustry/content/blocks/StorageBlocks.java index 0d736832cb..d3946f8339 100644 --- a/core/src/io/anuke/mindustry/content/blocks/StorageBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/StorageBlocks.java @@ -7,26 +7,26 @@ import io.anuke.mindustry.world.blocks.storage.SortedUnloader; import io.anuke.mindustry.world.blocks.storage.Unloader; import io.anuke.mindustry.world.blocks.storage.Vault; -public class StorageBlocks extends BlockList implements ContentList { +public class StorageBlocks extends BlockList implements ContentList{ public static Block core, vault, unloader, sortedunloader; @Override - public void load() { - core = new CoreBlock("core") {{ + public void load(){ + core = new CoreBlock("core"){{ health = 800; }}; - vault = new Vault("vault") {{ + vault = new Vault("vault"){{ size = 3; health = 600; itemCapacity = 2000; }}; - unloader = new Unloader("unloader") {{ + unloader = new Unloader("unloader"){{ speed = 5; }}; - sortedunloader = new SortedUnloader("sortedunloader") {{ + sortedunloader = new SortedUnloader("sortedunloader"){{ speed = 5; }}; } diff --git a/core/src/io/anuke/mindustry/content/blocks/TurretBlocks.java b/core/src/io/anuke/mindustry/content/blocks/TurretBlocks.java index c993ec8fbc..e408d82719 100644 --- a/core/src/io/anuke/mindustry/content/blocks/TurretBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/TurretBlocks.java @@ -12,22 +12,23 @@ import io.anuke.ucore.util.Angles; import io.anuke.ucore.util.Mathf; import io.anuke.ucore.util.Strings; -public class TurretBlocks extends BlockList implements ContentList { - public static Block duo, /*scatter,*/ scorch, hail, wave, lancer, arc, swarmer, salvo, fuse, ripple, cyclone, spectre, meltdown; +public class TurretBlocks extends BlockList implements ContentList{ + public static Block duo, /*scatter,*/ + scorch, hail, wave, lancer, arc, swarmer, salvo, fuse, ripple, cyclone, spectre, meltdown; - @Override - public void load() { - duo = new DoubleTurret("duo") {{ - ammoTypes = new AmmoType[]{AmmoTypes.bulletTungsten, AmmoTypes.bulletLead, AmmoTypes.bulletCarbide, AmmoTypes.bulletPyratite, AmmoTypes.bulletSilicon}; - reload = 25f; - restitution = 0.03f; - range = 90f; - shootCone = 15f; - ammoUseEffect = ShootFx.shellEjectSmall; - health = 80; - inaccuracy = 2f; - rotatespeed = 10f; - }}; + @Override + public void load(){ + duo = new DoubleTurret("duo"){{ + ammoTypes = new AmmoType[]{AmmoTypes.bulletTungsten, AmmoTypes.bulletLead, AmmoTypes.bulletCarbide, AmmoTypes.bulletPyratite, AmmoTypes.bulletSilicon}; + reload = 25f; + restitution = 0.03f; + range = 90f; + shootCone = 15f; + ammoUseEffect = ShootFx.shellEjectSmall; + health = 80; + inaccuracy = 2f; + rotatespeed = 10f; + }}; /* scatter = new BurstTurret("scatter") {{ ammoTypes = new AmmoType[]{AmmoTypes.flakLead, AmmoTypes.flakExplosive, AmmoTypes.flakPlastic}; @@ -41,165 +42,165 @@ public class TurretBlocks extends BlockList implements ContentList { ammoUseEffect = ShootFx.shellEjectSmall; }};*/ - hail = new ArtilleryTurret("hail") {{ - ammoTypes = new AmmoType[]{AmmoTypes.artilleryCarbide, AmmoTypes.artilleryHoming, AmmoTypes.artilleryIncindiary}; - reload = 100f; - recoil = 2f; - range = 200f; - inaccuracy = 5f; - health = 120; - }}; + hail = new ArtilleryTurret("hail"){{ + ammoTypes = new AmmoType[]{AmmoTypes.artilleryCarbide, AmmoTypes.artilleryHoming, AmmoTypes.artilleryIncindiary}; + reload = 100f; + recoil = 2f; + range = 200f; + inaccuracy = 5f; + health = 120; + }}; - scorch = new LiquidTurret("scorch") {{ + scorch = new LiquidTurret("scorch"){{ ammoTypes = new AmmoType[]{AmmoTypes.basicFlame}; recoil = 0f; reload = 4f; shootCone = 50f; ammoUseEffect = ShootFx.shellEjectSmall; - health = 160; + health = 160; drawer = (tile, entity) -> Draw.rect(entity.target != null ? name + "-shoot" : name, tile.drawx() + tr2.x, tile.drawy() + tr2.y, entity.rotation - 90); }}; - wave = new LiquidTurret("wave") {{ - ammoTypes = new AmmoType[]{AmmoTypes.water, AmmoTypes.lava, AmmoTypes.cryofluid, AmmoTypes.oil}; - size = 2; - recoil = 0f; - reload = 4f; - inaccuracy = 5f; - shootCone = 50f; - shootEffect = ShootFx.shootLiquid; - range = 70f; - health = 360; + wave = new LiquidTurret("wave"){{ + ammoTypes = new AmmoType[]{AmmoTypes.water, AmmoTypes.lava, AmmoTypes.cryofluid, AmmoTypes.oil}; + size = 2; + recoil = 0f; + reload = 4f; + inaccuracy = 5f; + shootCone = 50f; + shootEffect = ShootFx.shootLiquid; + range = 70f; + health = 360; - drawer = (tile, entity) -> { - Draw.rect(region, tile.drawx() + tr2.x, tile.drawy() + tr2.y, entity.rotation - 90); + drawer = (tile, entity) -> { + Draw.rect(region, tile.drawx() + tr2.x, tile.drawy() + tr2.y, entity.rotation - 90); - Draw.color(entity.liquids.current().color); - Draw.alpha(entity.liquids.total() / liquidCapacity); - Draw.rect(name + "-liquid", tile.drawx() + tr2.x, tile.drawy() + tr2.y, entity.rotation - 90); - Draw.color(); - }; - }}; + Draw.color(entity.liquids.current().color); + Draw.alpha(entity.liquids.total() / liquidCapacity); + Draw.rect(name + "-liquid", tile.drawx() + tr2.x, tile.drawy() + tr2.y, entity.rotation - 90); + Draw.color(); + }; + }}; - lancer = new LaserTurret("lancer") {{ - range = 90f; - chargeTime = 60f; - chargeMaxDelay = 30f; - chargeEffects = 7; - shootType = AmmoTypes.lancerLaser; - recoil = 2f; - reload = 100f; - cooldown = 0.03f; - powerUsed = 20f; - powerCapacity = 60f; - shootShake = 2f; - shootEffect = ShootFx.lancerLaserShoot; - smokeEffect = ShootFx.lancerLaserShootSmoke; - chargeEffect = ShootFx.lancerLaserCharge; - chargeBeginEffect = ShootFx.lancerLaserChargeBegin; - heatColor = Color.RED; - size = 2; - health = 320; - }}; + lancer = new LaserTurret("lancer"){{ + range = 90f; + chargeTime = 60f; + chargeMaxDelay = 30f; + chargeEffects = 7; + shootType = AmmoTypes.lancerLaser; + recoil = 2f; + reload = 100f; + cooldown = 0.03f; + powerUsed = 20f; + powerCapacity = 60f; + shootShake = 2f; + shootEffect = ShootFx.lancerLaserShoot; + smokeEffect = ShootFx.lancerLaserShootSmoke; + chargeEffect = ShootFx.lancerLaserCharge; + chargeBeginEffect = ShootFx.lancerLaserChargeBegin; + heatColor = Color.RED; + size = 2; + health = 320; + }}; - arc = new LaserTurret("arc") {{ - shootType = AmmoTypes.lightning; - reload = 100f; - chargeTime = 70f; - shootShake = 1f; - chargeMaxDelay = 30f; - chargeEffects = 7; - shootEffect = ShootFx.lightningShoot; - chargeEffect = ShootFx.lightningCharge; - chargeBeginEffect = ShootFx.lancerLaserChargeBegin; - heatColor = Color.RED; - recoil = 3f; - size = 2; - }}; + arc = new LaserTurret("arc"){{ + shootType = AmmoTypes.lightning; + reload = 100f; + chargeTime = 70f; + shootShake = 1f; + chargeMaxDelay = 30f; + chargeEffects = 7; + shootEffect = ShootFx.lightningShoot; + chargeEffect = ShootFx.lightningCharge; + chargeBeginEffect = ShootFx.lancerLaserChargeBegin; + heatColor = Color.RED; + recoil = 3f; + size = 2; + }}; - swarmer = new BurstTurret("swarmer") {{ - ammoTypes = new AmmoType[]{AmmoTypes.missileExplosive, AmmoTypes.missileIncindiary/*, AmmoTypes.missileSurge*/}; - reload = 60f; - shots = 4; - burstSpacing = 5; - inaccuracy = 10f; - range = 140f; - xRand = 6f; - size = 2; - health = 380; - }}; + swarmer = new BurstTurret("swarmer"){{ + ammoTypes = new AmmoType[]{AmmoTypes.missileExplosive, AmmoTypes.missileIncindiary/*, AmmoTypes.missileSurge*/}; + reload = 60f; + shots = 4; + burstSpacing = 5; + inaccuracy = 10f; + range = 140f; + xRand = 6f; + size = 2; + health = 380; + }}; - salvo = new BurstTurret("salvo") {{ - size = 2; - range = 120f; - ammoTypes = new AmmoType[]{AmmoTypes.bulletTungsten, AmmoTypes.bulletCarbide, AmmoTypes.bulletPyratite, AmmoTypes.bulletThorium, AmmoTypes.bulletSilicon}; - reload = 40f; - restitution = 0.03f; - ammoEjectBack = 3f; - cooldown = 0.03f; - recoil = 3f; - shootShake = 2f; - burstSpacing = 4; - shots = 3; - ammoUseEffect = ShootFx.shellEjectBig; + salvo = new BurstTurret("salvo"){{ + size = 2; + range = 120f; + ammoTypes = new AmmoType[]{AmmoTypes.bulletTungsten, AmmoTypes.bulletCarbide, AmmoTypes.bulletPyratite, AmmoTypes.bulletThorium, AmmoTypes.bulletSilicon}; + reload = 40f; + restitution = 0.03f; + ammoEjectBack = 3f; + cooldown = 0.03f; + recoil = 3f; + shootShake = 2f; + burstSpacing = 4; + shots = 3; + ammoUseEffect = ShootFx.shellEjectBig; - drawer = (tile, entity) -> { - Draw.rect(region, tile.drawx() + tr2.x, tile.drawy() + tr2.y, entity.rotation - 90); - float offsetx = (int) (Mathf.abscurve(Mathf.curve(entity.reload / reload, 0.3f, 0.2f)) * 3f); - float offsety = -(int) (Mathf.abscurve(Mathf.curve(entity.reload / reload, 0.3f, 0.2f)) * 2f); + drawer = (tile, entity) -> { + Draw.rect(region, tile.drawx() + tr2.x, tile.drawy() + tr2.y, entity.rotation - 90); + float offsetx = (int) (Mathf.abscurve(Mathf.curve(entity.reload / reload, 0.3f, 0.2f)) * 3f); + float offsety = -(int) (Mathf.abscurve(Mathf.curve(entity.reload / reload, 0.3f, 0.2f)) * 2f); - for (int i : Mathf.signs) { - float rot = entity.rotation + 90 * i; - Draw.rect(name + "-panel-" + Strings.dir(i), - tile.drawx() + tr2.x + Angles.trnsx(rot, offsetx, offsety), - tile.drawy() + tr2.y + Angles.trnsy(rot, -offsetx, offsety), entity.rotation - 90); - } - }; + for(int i : Mathf.signs){ + float rot = entity.rotation + 90 * i; + Draw.rect(name + "-panel-" + Strings.dir(i), + tile.drawx() + tr2.x + Angles.trnsx(rot, offsetx, offsety), + tile.drawy() + tr2.y + Angles.trnsy(rot, -offsetx, offsety), entity.rotation - 90); + } + }; - health = 360; - }}; + health = 360; + }}; - ripple = new ArtilleryTurret("ripple") {{ - ammoTypes = new AmmoType[]{AmmoTypes.artilleryCarbide, AmmoTypes.artilleryHoming, AmmoTypes.artilleryIncindiary, AmmoTypes.artilleryExplosive, AmmoTypes.artilleryPlastic}; - size = 3; - shots = 4; - inaccuracy = 12f; - reload = 60f; - ammoEjectBack = 5f; + ripple = new ArtilleryTurret("ripple"){{ + ammoTypes = new AmmoType[]{AmmoTypes.artilleryCarbide, AmmoTypes.artilleryHoming, AmmoTypes.artilleryIncindiary, AmmoTypes.artilleryExplosive, AmmoTypes.artilleryPlastic}; + size = 3; + shots = 4; + inaccuracy = 12f; + reload = 60f; + ammoEjectBack = 5f; ammoUseEffect = ShootFx.shellEjectBig; cooldown = 0.03f; - velocityInaccuracy = 0.2f; + velocityInaccuracy = 0.2f; restitution = 0.02f; recoil = 6f; shootShake = 2f; range = 300f; - health = 550; - }}; + health = 550; + }}; - cyclone = new ItemTurret("cyclone") {{ - ammoTypes = new AmmoType[]{AmmoTypes.flakLead, AmmoTypes.flakExplosive, AmmoTypes.flakPlastic, AmmoTypes.flakSurge}; - size = 3; - }}; + cyclone = new ItemTurret("cyclone"){{ + ammoTypes = new AmmoType[]{AmmoTypes.flakLead, AmmoTypes.flakExplosive, AmmoTypes.flakPlastic, AmmoTypes.flakSurge}; + size = 3; + }}; - fuse = new ItemTurret("fuse") {{ - //TODO make it use power - ammoTypes = new AmmoType[]{AmmoTypes.fuseShotgun}; - size = 3; - }}; + fuse = new ItemTurret("fuse"){{ + //TODO make it use power + ammoTypes = new AmmoType[]{AmmoTypes.fuseShotgun}; + size = 3; + }}; - spectre = new ItemTurret("spectre") {{ - ammoTypes = new AmmoType[]{AmmoTypes.bulletTungsten, AmmoTypes.bulletLead, AmmoTypes.bulletCarbide, AmmoTypes.bulletPyratite, AmmoTypes.bulletThorium, AmmoTypes.bulletSilicon}; - reload = 25f; - restitution = 0.03f; - ammoUseEffect = ShootFx.shellEjectSmall; - size = 4; - }}; + spectre = new ItemTurret("spectre"){{ + ammoTypes = new AmmoType[]{AmmoTypes.bulletTungsten, AmmoTypes.bulletLead, AmmoTypes.bulletCarbide, AmmoTypes.bulletPyratite, AmmoTypes.bulletThorium, AmmoTypes.bulletSilicon}; + reload = 25f; + restitution = 0.03f; + ammoUseEffect = ShootFx.shellEjectSmall; + size = 4; + }}; - meltdown = new PowerTurret("meltdown") {{ - shootType = AmmoTypes.meltdownLaser; - size = 4; - }}; - } + meltdown = new PowerTurret("meltdown"){{ + shootType = AmmoTypes.meltdownLaser; + size = 4; + }}; + } } diff --git a/core/src/io/anuke/mindustry/content/blocks/UnitBlocks.java b/core/src/io/anuke/mindustry/content/blocks/UnitBlocks.java index 5abf5ceb51..dc98341364 100644 --- a/core/src/io/anuke/mindustry/content/blocks/UnitBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/UnitBlocks.java @@ -7,12 +7,12 @@ import io.anuke.mindustry.type.ItemStack; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.blocks.units.*; -public class UnitBlocks extends BlockList implements ContentList { +public class UnitBlocks extends BlockList implements ContentList{ public static Block resupplyPoint, repairPoint, droneFactory, fabricatorFactory, dropPoint, reconstructor, overdriveProjector, shieldProjector; @Override - public void load() { - droneFactory = new UnitFactory("drone-factory") {{ + public void load(){ + droneFactory = new UnitFactory("drone-factory"){{ type = UnitTypes.drone; produceTime = 800; size = 2; @@ -20,7 +20,7 @@ public class UnitBlocks extends BlockList implements ContentList { consumes.items(new ItemStack[]{new ItemStack(Items.silicon, 30), new ItemStack(Items.lead, 30)}); }}; - fabricatorFactory = new UnitFactory("fabricator-factory") {{ + fabricatorFactory = new UnitFactory("fabricator-factory"){{ type = UnitTypes.fabricator; produceTime = 1600; size = 2; @@ -28,30 +28,30 @@ public class UnitBlocks extends BlockList implements ContentList { consumes.items(new ItemStack[]{new ItemStack(Items.silicon, 70), new ItemStack(Items.lead, 80), new ItemStack(Items.titanium, 80)}); }}; - resupplyPoint = new ResupplyPoint("resupply-point") {{ + resupplyPoint = new ResupplyPoint("resupply-point"){{ shadow = "shadow-round-1"; itemCapacity = 30; }}; - dropPoint = new DropPoint("drop-point") {{ + dropPoint = new DropPoint("drop-point"){{ shadow = "shadow-round-1"; itemCapacity = 40; }}; - repairPoint = new RepairPoint("repair-point") {{ + repairPoint = new RepairPoint("repair-point"){{ shadow = "shadow-round-1"; repairSpeed = 0.1f; }}; - reconstructor = new Reconstructor("reconstructor") {{ + reconstructor = new Reconstructor("reconstructor"){{ size = 2; }}; - overdriveProjector = new OverdriveProjector("overdrive-projector") {{ + overdriveProjector = new OverdriveProjector("overdrive-projector"){{ size = 2; }}; - shieldProjector = new ShieldProjector("shieldprojector") {{ + shieldProjector = new ShieldProjector("shieldprojector"){{ size = 2; }}; } diff --git a/core/src/io/anuke/mindustry/content/blocks/UpgradeBlocks.java b/core/src/io/anuke/mindustry/content/blocks/UpgradeBlocks.java index 7382e726fe..c66cfba208 100644 --- a/core/src/io/anuke/mindustry/content/blocks/UpgradeBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/UpgradeBlocks.java @@ -4,11 +4,11 @@ import io.anuke.mindustry.content.Mechs; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.blocks.units.MechFactory; -public class UpgradeBlocks extends BlockList { +public class UpgradeBlocks extends BlockList{ public static Block deltaFactory, tauFactory, omegaFactory, dartFactory, javelinFactory, tridentFactory, halberdFactory; @Override - public void load() { + public void load(){ deltaFactory = new MechFactory("delta-mech-factory"){{ mech = Mechs.delta; size = 2; diff --git a/core/src/io/anuke/mindustry/content/bullets/ArtilleryBullets.java b/core/src/io/anuke/mindustry/content/bullets/ArtilleryBullets.java index 0747fecae7..5898a70070 100644 --- a/core/src/io/anuke/mindustry/content/bullets/ArtilleryBullets.java +++ b/core/src/io/anuke/mindustry/content/bullets/ArtilleryBullets.java @@ -11,9 +11,9 @@ public class ArtilleryBullets extends BulletList implements ContentList{ public static BulletType carbide, plastic, plasticFrag, homing, incindiary, explosive, surge; @Override - public void load() { + public void load(){ - carbide = new ArtilleryBulletType(3f, 0, "shell") { + carbide = new ArtilleryBulletType(3f, 0, "shell"){ { hiteffect = BulletFx.flakExplosion; knockback = 0.8f; @@ -25,7 +25,7 @@ public class ArtilleryBullets extends BulletList implements ContentList{ } }; - plasticFrag = new BasicBulletType(2.5f, 6, "bullet") { + plasticFrag = new BasicBulletType(2.5f, 6, "bullet"){ { bulletWidth = 10f; bulletHeight = 12f; @@ -36,7 +36,7 @@ public class ArtilleryBullets extends BulletList implements ContentList{ } }; - plastic = new ArtilleryBulletType(3.3f, 0, "shell") { + plastic = new ArtilleryBulletType(3.3f, 0, "shell"){ { hiteffect = BulletFx.plasticExplosion; knockback = 1f; @@ -52,7 +52,7 @@ public class ArtilleryBullets extends BulletList implements ContentList{ } }; - homing = new ArtilleryBulletType(3f, 0, "shell") { + homing = new ArtilleryBulletType(3f, 0, "shell"){ { hiteffect = BulletFx.flakExplosion; knockback = 0.8f; @@ -66,7 +66,7 @@ public class ArtilleryBullets extends BulletList implements ContentList{ } }; - incindiary = new ArtilleryBulletType(3f, 0, "shell") { + incindiary = new ArtilleryBulletType(3f, 0, "shell"){ { hiteffect = BulletFx.blastExplosion; knockback = 0.8f; @@ -83,7 +83,7 @@ public class ArtilleryBullets extends BulletList implements ContentList{ } }; - explosive = new ArtilleryBulletType(2f, 0, "shell") { + explosive = new ArtilleryBulletType(2f, 0, "shell"){ { hiteffect = BulletFx.blastExplosion; knockback = 0.8f; @@ -97,7 +97,7 @@ public class ArtilleryBullets extends BulletList implements ContentList{ } }; - surge = new ArtilleryBulletType(3f, 0, "shell") { + surge = new ArtilleryBulletType(3f, 0, "shell"){ { //TODO } diff --git a/core/src/io/anuke/mindustry/content/bullets/BulletList.java b/core/src/io/anuke/mindustry/content/bullets/BulletList.java index dac973f2e0..ba0e465799 100644 --- a/core/src/io/anuke/mindustry/content/bullets/BulletList.java +++ b/core/src/io/anuke/mindustry/content/bullets/BulletList.java @@ -5,10 +5,10 @@ import io.anuke.mindustry.entities.bullet.BulletType; import io.anuke.mindustry.game.Content; import io.anuke.mindustry.type.ContentList; -public abstract class BulletList implements ContentList { +public abstract class BulletList implements ContentList{ @Override - public Array getAll() { + public Array getAll(){ return BulletType.all(); } } diff --git a/core/src/io/anuke/mindustry/content/bullets/FlakBullets.java b/core/src/io/anuke/mindustry/content/bullets/FlakBullets.java index fdcec72362..276cb76c7d 100644 --- a/core/src/io/anuke/mindustry/content/bullets/FlakBullets.java +++ b/core/src/io/anuke/mindustry/content/bullets/FlakBullets.java @@ -4,34 +4,34 @@ import io.anuke.mindustry.entities.bullet.BasicBulletType; import io.anuke.mindustry.entities.bullet.BulletType; import io.anuke.mindustry.type.ContentList; -public class FlakBullets extends BulletList implements ContentList { +public class FlakBullets extends BulletList implements ContentList{ public static BulletType lead, plastic, explosive, surge; @Override - public void load() { + public void load(){ - lead = new BasicBulletType(3f, 5, "bullet") { + lead = new BasicBulletType(3f, 5, "bullet"){ { bulletWidth = 7f; bulletHeight = 9f; } }; - plastic = new BasicBulletType(3f, 5, "bullet") { + plastic = new BasicBulletType(3f, 5, "bullet"){ { bulletWidth = 7f; bulletHeight = 9f; } }; - explosive = new BasicBulletType(3f, 5, "bullet") { + explosive = new BasicBulletType(3f, 5, "bullet"){ { bulletWidth = 7f; bulletHeight = 9f; } }; - surge = new BasicBulletType(3f, 5, "bullet") { + surge = new BasicBulletType(3f, 5, "bullet"){ { bulletWidth = 7f; bulletHeight = 9f; diff --git a/core/src/io/anuke/mindustry/content/bullets/MissileBullets.java b/core/src/io/anuke/mindustry/content/bullets/MissileBullets.java index f004eac290..ceea3423f2 100644 --- a/core/src/io/anuke/mindustry/content/bullets/MissileBullets.java +++ b/core/src/io/anuke/mindustry/content/bullets/MissileBullets.java @@ -6,13 +6,13 @@ import io.anuke.mindustry.entities.bullet.MissileBulletType; import io.anuke.mindustry.graphics.Palette; import io.anuke.mindustry.type.ContentList; -public class MissileBullets extends BulletList implements ContentList { +public class MissileBullets extends BulletList implements ContentList{ public static BulletType explosive, incindiary, surge, javelin; @Override - public void load() { + public void load(){ - explosive = new MissileBulletType(1.8f, 10, "missile") { + explosive = new MissileBulletType(1.8f, 10, "missile"){ { bulletWidth = 8f; bulletHeight = 8f; @@ -26,7 +26,7 @@ public class MissileBullets extends BulletList implements ContentList { } }; - incindiary = new MissileBulletType(2f, 12, "missile") { + incindiary = new MissileBulletType(2f, 12, "missile"){ { frontColor = Palette.lightishOrange; backColor = Palette.lightOrange; @@ -44,14 +44,14 @@ public class MissileBullets extends BulletList implements ContentList { } }; - surge = new MissileBulletType(3f, 5, "bullet") { + surge = new MissileBulletType(3f, 5, "bullet"){ { bulletWidth = 7f; bulletHeight = 9f; } }; - javelin = new MissileBulletType(2.5f, 10, "missile") { + javelin = new MissileBulletType(2.5f, 10, "missile"){ { bulletWidth = 8f; bulletHeight = 8f; diff --git a/core/src/io/anuke/mindustry/content/bullets/StandardBullets.java b/core/src/io/anuke/mindustry/content/bullets/StandardBullets.java index 37efcda5e0..0c0939ac67 100644 --- a/core/src/io/anuke/mindustry/content/bullets/StandardBullets.java +++ b/core/src/io/anuke/mindustry/content/bullets/StandardBullets.java @@ -5,27 +5,27 @@ import io.anuke.mindustry.entities.bullet.BulletType; import io.anuke.mindustry.graphics.Palette; import io.anuke.mindustry.type.ContentList; -public class StandardBullets extends BulletList implements ContentList { +public class StandardBullets extends BulletList implements ContentList{ public static BulletType tungsten, lead, carbide, thorium, homing, tracer; @Override - public void load() { + public void load(){ - tungsten = new BasicBulletType(3.2f, 10, "bullet") { + tungsten = new BasicBulletType(3.2f, 10, "bullet"){ { bulletWidth = 9f; bulletHeight = 11f; } }; - lead = new BasicBulletType(2.5f, 5, "bullet") { + lead = new BasicBulletType(2.5f, 5, "bullet"){ { bulletWidth = 7f; bulletHeight = 9f; } }; - carbide = new BasicBulletType(3.5f, 18, "bullet") { + carbide = new BasicBulletType(3.5f, 18, "bullet"){ { bulletWidth = 9f; bulletHeight = 12f; @@ -33,7 +33,7 @@ public class StandardBullets extends BulletList implements ContentList { } }; - thorium = new BasicBulletType(4f, 29, "bullet") { + thorium = new BasicBulletType(4f, 29, "bullet"){ { bulletWidth = 10f; bulletHeight = 13f; @@ -41,7 +41,7 @@ public class StandardBullets extends BulletList implements ContentList { } }; - homing = new BasicBulletType(3f, 9, "bullet") { + homing = new BasicBulletType(3f, 9, "bullet"){ { bulletWidth = 7f; bulletHeight = 9f; @@ -49,7 +49,7 @@ public class StandardBullets extends BulletList implements ContentList { } }; - tracer = new BasicBulletType(3.2f, 11, "bullet") { + tracer = new BasicBulletType(3.2f, 11, "bullet"){ { bulletWidth = 10f; bulletHeight = 12f; diff --git a/core/src/io/anuke/mindustry/content/bullets/TurretBullets.java b/core/src/io/anuke/mindustry/content/bullets/TurretBullets.java index 3912d60949..177acacb35 100644 --- a/core/src/io/anuke/mindustry/content/bullets/TurretBullets.java +++ b/core/src/io/anuke/mindustry/content/bullets/TurretBullets.java @@ -29,13 +29,13 @@ import io.anuke.ucore.util.Mathf; import static io.anuke.mindustry.Vars.world; -public class TurretBullets extends BulletList implements ContentList { +public class TurretBullets extends BulletList implements ContentList{ public static BulletType fireball, basicFlame, lancerLaser, fuseShot, waterShot, cryoShot, lavaShot, oilShot, lightning, driverBolt; @Override - public void load() { + public void load(){ - fireball = new BulletType(1f, 4) { + fireball = new BulletType(1f, 4){ { pierce = true; hitTiles = false; @@ -45,12 +45,12 @@ public class TurretBullets extends BulletList implements ContentList { } @Override - public void init(Bullet b) { + public void init(Bullet b){ b.getVelocity().setLength(0.6f + Mathf.random(2f)); } @Override - public void draw(Bullet b) { + public void draw(Bullet b){ //TODO add color to the bullet depending on the color of the flame it came from Draw.color(Palette.lightFlame, Palette.darkFlame, Color.GRAY, b.fin()); Fill.circle(b.x, b.y, 3f * b.fout()); @@ -58,25 +58,25 @@ public class TurretBullets extends BulletList implements ContentList { } @Override - public void update(Bullet b) { - if (Mathf.chance(0.04 * Timers.delta())) { + public void update(Bullet b){ + if(Mathf.chance(0.04 * Timers.delta())){ Tile tile = world.tileWorld(b.x, b.y); - if (tile != null) { + if(tile != null){ Fire.create(tile); } } - if (Mathf.chance(0.1 * Timers.delta())) { + if(Mathf.chance(0.1 * Timers.delta())){ Effects.effect(EnvironmentFx.fireballsmoke, b.x, b.y); } - if (Mathf.chance(0.1 * Timers.delta())) { + if(Mathf.chance(0.1 * Timers.delta())){ Effects.effect(EnvironmentFx.ballfire, b.x, b.y); } } }; - basicFlame = new BulletType(2f, 5) { + basicFlame = new BulletType(2f, 5){ { hitsize = 7f; lifetime = 30f; @@ -88,11 +88,11 @@ public class TurretBullets extends BulletList implements ContentList { } @Override - public void draw(Bullet b) { + public void draw(Bullet b){ } }; - lancerLaser = new BulletType(0.001f, 110) { + lancerLaser = new BulletType(0.001f, 110){ Color[] colors = {Palette.lancerLaser.cpy().mul(1f, 1f, 1f, 0.4f), Palette.lancerLaser, Color.WHITE}; float[] tscales = {1f, 0.7f, 0.5f, 0.2f}; float[] lenscales = {1f, 1.1f, 1.13f, 1.14f}; @@ -107,19 +107,19 @@ public class TurretBullets extends BulletList implements ContentList { } @Override - public void init(Bullet b) { + public void init(Bullet b){ Damage.collideLine(b, b.getTeam(), hiteffect, b.x, b.y, b.angle(), length); } @Override - public void draw(Bullet b) { + public void draw(Bullet b){ float f = Mathf.curve(b.fin(), 0f, 0.2f); float baseLen = length * f; Lines.lineAngle(b.x, b.y, b.angle(), baseLen); - for (int s = 0; s < 3; s++) { + for(int s = 0; s < 3; s++){ Draw.color(colors[s]); - for (int i = 0; i < tscales.length; i++) { + for(int i = 0; i < tscales.length; i++){ Lines.stroke(7f * b.fout() * (s == 0 ? 1.5f : s == 1 ? 1f : 0.3f) * tscales[i]); Lines.lineAngle(b.x, b.y, b.angle(), baseLen * lenscales[i]); } @@ -128,24 +128,24 @@ public class TurretBullets extends BulletList implements ContentList { } }; - fuseShot = new BulletType(0.01f, 100) { + fuseShot = new BulletType(0.01f, 100){ //TODO }; - waterShot = new LiquidBulletType(Liquids.water) { + waterShot = new LiquidBulletType(Liquids.water){ { status = StatusEffects.wet; statusIntensity = 0.5f; knockback = 0.65f; } }; - cryoShot = new LiquidBulletType(Liquids.cryofluid) { + cryoShot = new LiquidBulletType(Liquids.cryofluid){ { status = StatusEffects.freezing; statusIntensity = 0.5f; } }; - lavaShot = new LiquidBulletType(Liquids.lava) { + lavaShot = new LiquidBulletType(Liquids.lava){ { damage = 4; speed = 1.9f; @@ -154,7 +154,7 @@ public class TurretBullets extends BulletList implements ContentList { statusIntensity = 0.5f; } }; - oilShot = new LiquidBulletType(Liquids.oil) { + oilShot = new LiquidBulletType(Liquids.oil){ { speed = 2f; drag = 0.03f; @@ -162,7 +162,7 @@ public class TurretBullets extends BulletList implements ContentList { statusIntensity = 0.5f; } }; - lightning = new BulletType(0.001f, 10) { + lightning = new BulletType(0.001f, 10){ { lifetime = 1; despawneffect = Fx.none; @@ -170,16 +170,16 @@ public class TurretBullets extends BulletList implements ContentList { } @Override - public void draw(Bullet b) { + public void draw(Bullet b){ } @Override - public void init(Bullet b) { + public void init(Bullet b){ Lightning.create(b.getTeam(), hiteffect, Palette.lancerLaser, damage, b.x, b.y, b.angle(), 30); } }; - driverBolt = new BulletType(5f, 20) { + driverBolt = new BulletType(5f, 20){ { collidesTiles = false; lifetime = 200f; @@ -189,7 +189,7 @@ public class TurretBullets extends BulletList implements ContentList { } @Override - public void draw(Bullet b) { + public void draw(Bullet b){ Draw.color(Color.LIGHT_GRAY); Fill.square(b.x, b.y, 3f, b.angle()); @@ -199,7 +199,7 @@ public class TurretBullets extends BulletList implements ContentList { } @Override - public void update(Bullet b) { + public void update(Bullet b){ //data MUST be an instance of DriverBulletData if(!(b.getData() instanceof DriverBulletData)){ hit(b); @@ -208,7 +208,7 @@ public class TurretBullets extends BulletList implements ContentList { float hitDst = 7f; - DriverBulletData data = (DriverBulletData)b.getData(); + DriverBulletData data = (DriverBulletData) b.getData(); //if the target is dead, just keep flying until the bullet explodes if(data.to.isDead()){ @@ -245,15 +245,15 @@ public class TurretBullets extends BulletList implements ContentList { } @Override - public void despawned(Bullet b) { + public void despawned(Bullet b){ super.despawned(b); if(!(b.getData() instanceof DriverBulletData)) return; - DriverBulletData data = (DriverBulletData)b.getData(); + DriverBulletData data = (DriverBulletData) b.getData(); data.to.isRecieving = false; - for(int i = 0; i < data.items.length; i ++){ + for(int i = 0; i < data.items.length; i++){ int amountDropped = Mathf.random(0, data.items[i]); if(amountDropped > 0){ float angle = b.angle() + Mathf.range(100f); @@ -264,7 +264,7 @@ public class TurretBullets extends BulletList implements ContentList { } @Override - public void hit(Bullet b, float hitx, float hity) { + public void hit(Bullet b, float hitx, float hity){ super.hit(b, hitx, hity); despawned(b); } diff --git a/core/src/io/anuke/mindustry/content/bullets/WeaponBullets.java b/core/src/io/anuke/mindustry/content/bullets/WeaponBullets.java index d7195ee768..04997b8c68 100644 --- a/core/src/io/anuke/mindustry/content/bullets/WeaponBullets.java +++ b/core/src/io/anuke/mindustry/content/bullets/WeaponBullets.java @@ -16,12 +16,12 @@ import io.anuke.ucore.util.Mathf; import static io.anuke.mindustry.Vars.world; -public class WeaponBullets extends BulletList { +public class WeaponBullets extends BulletList{ public static BulletType tungstenShotgun, bombExplosive, bombIncendiary, bombOil, shellCarbide; @Override - public void load() { - tungstenShotgun = new BasicBulletType(5f, 8, "bullet") { + public void load(){ + tungstenShotgun = new BasicBulletType(5f, 8, "bullet"){ { bulletWidth = 8f; bulletHeight = 9f; @@ -49,10 +49,10 @@ public class WeaponBullets extends BulletList { } @Override - public void hit(Bullet b, float x, float y) { + public void hit(Bullet b, float x, float y){ super.hit(b, x, y); - for (int i = 0; i < 3; i++) { + for(int i = 0; i < 3; i++){ float cx = x + Mathf.range(10f); float cy = y + Mathf.range(10f); Tile tile = world.tileWorld(cx, cy); @@ -73,17 +73,17 @@ public class WeaponBullets extends BulletList { } @Override - public void hit(Bullet b, float x, float y) { + public void hit(Bullet b, float x, float y){ super.hit(b, x, y); - for (int i = 0; i < 3; i++) { + for(int i = 0; i < 3; i++){ Tile tile = world.tileWorld(x + Mathf.range(8f), y + Mathf.range(8f)); Puddle.deposit(tile, Liquids.oil, 5f); } } }; - shellCarbide = new BasicBulletType(3.4f, 20, "bullet") { + shellCarbide = new BasicBulletType(3.4f, 20, "bullet"){ { bulletWidth = 10f; bulletHeight = 12f; diff --git a/core/src/io/anuke/mindustry/content/fx/BlockFx.java b/core/src/io/anuke/mindustry/content/fx/BlockFx.java index 14e77406c6..9260c76b0f 100644 --- a/core/src/io/anuke/mindustry/content/fx/BlockFx.java +++ b/core/src/io/anuke/mindustry/content/fx/BlockFx.java @@ -19,7 +19,7 @@ public class BlockFx extends FxList implements ContentList{ public static Effect reactorsmoke, nuclearsmoke, nuclearcloud, redgeneratespark, generatespark, fuelburn, plasticburn, pulverize, pulverizeRed, pulverizeRedder, pulverizeSmall, pulverizeMedium, producesmoke, smeltsmoke, formsmoke, blastsmoke, lava, dooropen, doorclose, dooropenlarge, doorcloselarge, purify, purifyoil, purifystone, generate, mine, mineBig, mineHuge, smelt, teleportActivate, teleport, teleportOut, ripple, bubble; @Override - public void load() { + public void load(){ reactorsmoke = new Effect(17, e -> { Angles.randLenVectors(e.id, 4, e.fin() * 8f, (x, y) -> { diff --git a/core/src/io/anuke/mindustry/content/fx/BulletFx.java b/core/src/io/anuke/mindustry/content/fx/BulletFx.java index d5977c3ef9..73a53a6c70 100644 --- a/core/src/io/anuke/mindustry/content/fx/BulletFx.java +++ b/core/src/io/anuke/mindustry/content/fx/BulletFx.java @@ -10,12 +10,12 @@ import io.anuke.ucore.graphics.Lines; import io.anuke.ucore.util.Angles; import io.anuke.ucore.util.Mathf; -public class BulletFx extends FxList implements ContentList { +public class BulletFx extends FxList implements ContentList{ public static Effect hitBulletSmall, hitBulletBig, hitFlameSmall, hitLiquid, hitLancer, despawn, flakExplosion, blastExplosion, plasticExplosion, artilleryTrail, incendTrail, missileTrail; @Override - public void load() { + public void load(){ hitBulletSmall = new Effect(14, e -> { Draw.color(Color.WHITE, Palette.lightOrange, e.fin()); diff --git a/core/src/io/anuke/mindustry/content/fx/EnvironmentFx.java b/core/src/io/anuke/mindustry/content/fx/EnvironmentFx.java index d1eb624141..e8f3fe6b2e 100644 --- a/core/src/io/anuke/mindustry/content/fx/EnvironmentFx.java +++ b/core/src/io/anuke/mindustry/content/fx/EnvironmentFx.java @@ -10,11 +10,11 @@ import io.anuke.ucore.graphics.Fill; import io.anuke.ucore.util.Angles; import io.anuke.ucore.util.Mathf; -public class EnvironmentFx extends FxList implements ContentList { +public class EnvironmentFx extends FxList implements ContentList{ public static Effect burning, fire, smoke, steam, fireballsmoke, ballfire, freezing, melting, wet, oily; @Override - public void load() { + public void load(){ burning = new Effect(35f, e -> { Draw.color(Palette.lightFlame, Palette.darkFlame, e.fin()); diff --git a/core/src/io/anuke/mindustry/content/fx/ExplosionFx.java b/core/src/io/anuke/mindustry/content/fx/ExplosionFx.java index 78f4a7e4fa..457c2dfe32 100644 --- a/core/src/io/anuke/mindustry/content/fx/ExplosionFx.java +++ b/core/src/io/anuke/mindustry/content/fx/ExplosionFx.java @@ -10,11 +10,11 @@ import io.anuke.ucore.graphics.Lines; import io.anuke.ucore.util.Angles; import io.anuke.ucore.util.Mathf; -public class ExplosionFx extends FxList implements ContentList { +public class ExplosionFx extends FxList implements ContentList{ public static Effect shockwave, bigShockwave, nuclearShockwave, explosion, blockExplosion, blockExplosionSmoke; @Override - public void load() { + public void load(){ shockwave = new Effect(10f, 80f, e -> { Draw.color(Color.WHITE, Color.LIGHT_GRAY, e.fin()); diff --git a/core/src/io/anuke/mindustry/content/fx/Fx.java b/core/src/io/anuke/mindustry/content/fx/Fx.java index 693a412742..f76f12d233 100644 --- a/core/src/io/anuke/mindustry/content/fx/Fx.java +++ b/core/src/io/anuke/mindustry/content/fx/Fx.java @@ -11,59 +11,59 @@ import io.anuke.ucore.util.Angles; import static io.anuke.mindustry.Vars.tilesize; -public class Fx extends FxList implements ContentList { - public static Effect none, placeBlock, breakBlock, smoke, spawn, tapBlock, select; +public class Fx extends FxList implements ContentList{ + public static Effect none, placeBlock, breakBlock, smoke, spawn, tapBlock, select; - @Override - public void load() { + @Override + public void load(){ - none = new Effect(0, 0f, e -> { - }); + none = new Effect(0, 0f, e -> { + }); - placeBlock = new Effect(16, e -> { - Draw.color(Palette.accent); - Lines.stroke(3f - e.fin() * 2f); - Lines.square(e.x, e.y, tilesize / 2f * e.rotation + e.fin() * 3f); - Draw.reset(); - }); + placeBlock = new Effect(16, e -> { + Draw.color(Palette.accent); + Lines.stroke(3f - e.fin() * 2f); + Lines.square(e.x, e.y, tilesize / 2f * e.rotation + e.fin() * 3f); + Draw.reset(); + }); - tapBlock = new Effect(12, e -> { - Draw.color(Palette.accent); - Lines.stroke(3f - e.fin() * 2f); - Lines.circle(e.x, e.y, 4f + (tilesize/1.5f * e.rotation) * e.fin()); - Draw.reset(); - }); + tapBlock = new Effect(12, e -> { + Draw.color(Palette.accent); + Lines.stroke(3f - e.fin() * 2f); + Lines.circle(e.x, e.y, 4f + (tilesize / 1.5f * e.rotation) * e.fin()); + Draw.reset(); + }); - breakBlock = new Effect(12, e -> { - Draw.color(Palette.remove); - Lines.stroke(3f - e.fin() * 2f); - Lines.square(e.x, e.y, tilesize / 2f * e.rotation + e.fin() * 3f); + breakBlock = new Effect(12, e -> { + Draw.color(Palette.remove); + Lines.stroke(3f - e.fin() * 2f); + Lines.square(e.x, e.y, tilesize / 2f * e.rotation + e.fin() * 3f); - Angles.randLenVectors(e.id, 3 + (int) (e.rotation * 3), e.rotation * 2f + (tilesize * e.rotation) * e.finpow(), (x, y) -> { - Fill.square(e.x + x, e.y + y, 1f + e.fout() * (3f + e.rotation)); - }); - Draw.reset(); - }); + Angles.randLenVectors(e.id, 3 + (int) (e.rotation * 3), e.rotation * 2f + (tilesize * e.rotation) * e.finpow(), (x, y) -> { + Fill.square(e.x + x, e.y + y, 1f + e.fout() * (3f + e.rotation)); + }); + Draw.reset(); + }); - select = new Effect(23, e -> { - Draw.color(Palette.accent); - Lines.stroke(e.fout() * 3f); - Lines.circle(e.x, e.y, 3f + e.fin() * 14f); - Draw.reset(); - }); + select = new Effect(23, e -> { + Draw.color(Palette.accent); + Lines.stroke(e.fout() * 3f); + Lines.circle(e.x, e.y, 3f + e.fin() * 14f); + Draw.reset(); + }); - smoke = new Effect(100, e -> { - Draw.color(Color.GRAY, Palette.darkishGray, e.fin()); - float size = 7f - e.fin() * 7f; - Draw.rect("circle", e.x, e.y, size, size); - Draw.reset(); - }); + smoke = new Effect(100, e -> { + Draw.color(Color.GRAY, Palette.darkishGray, e.fin()); + float size = 7f - e.fin() * 7f; + Draw.rect("circle", e.x, e.y, size, size); + Draw.reset(); + }); - spawn = new Effect(23, e -> { - Lines.stroke(2f * e.fout()); - Draw.color(Palette.accent); - Lines.poly(e.x, e.y, 4, 3f + e.fin() * 8f); - Draw.reset(); - }); - } + spawn = new Effect(23, e -> { + Lines.stroke(2f * e.fout()); + Draw.color(Palette.accent); + Lines.poly(e.x, e.y, 4, 3f + e.fin() * 8f); + Draw.reset(); + }); + } } diff --git a/core/src/io/anuke/mindustry/content/fx/FxList.java b/core/src/io/anuke/mindustry/content/fx/FxList.java index f1fd467602..fe87cc2eb8 100644 --- a/core/src/io/anuke/mindustry/content/fx/FxList.java +++ b/core/src/io/anuke/mindustry/content/fx/FxList.java @@ -7,7 +7,7 @@ import io.anuke.mindustry.type.ContentList; public abstract class FxList implements ContentList{ @Override - public Array getAll() { + public Array getAll(){ return Array.with(); } } diff --git a/core/src/io/anuke/mindustry/content/fx/ShootFx.java b/core/src/io/anuke/mindustry/content/fx/ShootFx.java index b0a83f7f80..2e55246cc8 100644 --- a/core/src/io/anuke/mindustry/content/fx/ShootFx.java +++ b/core/src/io/anuke/mindustry/content/fx/ShootFx.java @@ -12,11 +12,11 @@ import io.anuke.ucore.graphics.Shapes; import io.anuke.ucore.util.Angles; import io.anuke.ucore.util.Mathf; -public class ShootFx extends FxList implements ContentList { +public class ShootFx extends FxList implements ContentList{ public static Effect shootSmall, shootSmallSmoke, shootBig, shootBig2, shootBigSmoke, shootBigSmoke2, shootSmallFlame, shootLiquid, shellEjectSmall, shellEjectMedium, shellEjectBig, lancerLaserShoot, lancerLaserShootSmoke, lancerLaserCharge, lancerLaserChargeBegin, lightningCharge, lightningShoot; @Override - public void load() { + public void load(){ shootSmall = new Effect(8, e -> { Draw.color(Palette.lighterOrange, Palette.lightOrange, e.fin()); @@ -111,7 +111,7 @@ public class ShootFx extends FxList implements ContentList { shellEjectMedium = new GroundEffect(34f, 400f, e -> { Draw.color(Palette.lightOrange, Color.LIGHT_GRAY, Palette.lightishGray, e.fin()); float rot = e.rotation + 90f; - for (int i : Mathf.signs) { + for(int i : Mathf.signs){ float len = (2f + e.finpow() * 10f) * i; float lr = rot + e.fin() * 20f * i; Draw.rect("casing", @@ -122,7 +122,7 @@ public class ShootFx extends FxList implements ContentList { Draw.color(Color.LIGHT_GRAY, Color.GRAY, e.fin()); - for (int i : Mathf.signs) { + for(int i : Mathf.signs){ Angles.randLenVectors(e.id, 4, 1f + e.finpow() * 11f, e.rotation + 90f * i, 20f, (x, y) -> { Fill.circle(e.x + x, e.y + y, e.fout() * 1.5f); }); @@ -134,7 +134,7 @@ public class ShootFx extends FxList implements ContentList { shellEjectBig = new GroundEffect(22f, 400f, e -> { Draw.color(Palette.lightOrange, Color.LIGHT_GRAY, Palette.lightishGray, e.fin()); float rot = e.rotation + 90f; - for (int i : Mathf.signs) { + for(int i : Mathf.signs){ float len = (4f + e.finpow() * 8f) * i; float lr = rot + Mathf.randomSeedRange(e.id + i + 6, 20f * e.fin()) * i; Draw.rect("casing", @@ -146,7 +146,7 @@ public class ShootFx extends FxList implements ContentList { Draw.color(Color.LIGHT_GRAY); - for (int i : Mathf.signs) { + for(int i : Mathf.signs){ Angles.randLenVectors(e.id, 4, -e.finpow() * 15f, e.rotation + 90f * i, 25f, (x, y) -> { Fill.circle(e.x + x, e.y + y, e.fout() * 2f); }); @@ -158,7 +158,7 @@ public class ShootFx extends FxList implements ContentList { lancerLaserShoot = new Effect(21f, e -> { Draw.color(Palette.lancerLaser); - for (int i : Mathf.signs) { + for(int i : Mathf.signs){ Shapes.tri(e.x, e.y, 4f * e.fout(), 29f, e.rotation + 90f * i); } diff --git a/core/src/io/anuke/mindustry/content/fx/UnitFx.java b/core/src/io/anuke/mindustry/content/fx/UnitFx.java index 3035fce5b1..69117a0237 100644 --- a/core/src/io/anuke/mindustry/content/fx/UnitFx.java +++ b/core/src/io/anuke/mindustry/content/fx/UnitFx.java @@ -10,11 +10,11 @@ import io.anuke.ucore.graphics.Lines; import io.anuke.ucore.util.Angles; import io.anuke.ucore.util.Mathf; -public class UnitFx extends FxList implements ContentList { +public class UnitFx extends FxList implements ContentList{ public static Effect vtolHover, unitDrop, unitPickup, pickup; @Override - public void load() { + public void load(){ vtolHover = new Effect(40f, e -> { float len = e.finpow() * 10f; diff --git a/core/src/io/anuke/mindustry/core/ContentLoader.java b/core/src/io/anuke/mindustry/core/ContentLoader.java index d60fd098a3..db6a5ed7a2 100644 --- a/core/src/io/anuke/mindustry/core/ContentLoader.java +++ b/core/src/io/anuke/mindustry/core/ContentLoader.java @@ -25,76 +25,80 @@ import io.anuke.ucore.core.Effects; import io.anuke.ucore.function.Consumer; import io.anuke.ucore.util.Log; -/**Loads all game content. - * Call load() before doing anything with content.*/ -public class ContentLoader { +/** + * Loads all game content. + * Call load() before doing anything with content. + */ +public class ContentLoader{ private static boolean loaded = false; private static ObjectSet> contentSet = new OrderedSet<>(); private static OrderedMap> contentMap = new OrderedMap<>(); private static ObjectSet> initialization = new ObjectSet<>(); private static ContentList[] content = { - //effects - new BlockFx(), - new BulletFx(), - new EnvironmentFx(), - new ExplosionFx(), - new Fx(), - new ShootFx(), - new UnitFx(), + //effects + new BlockFx(), + new BulletFx(), + new EnvironmentFx(), + new ExplosionFx(), + new Fx(), + new ShootFx(), + new UnitFx(), - //items - new Items(), + //items + new Items(), - //status effects - new StatusEffects(), + //status effects + new StatusEffects(), - //liquids - new Liquids(), + //liquids + new Liquids(), - //bullets - new ArtilleryBullets(), - new FlakBullets(), - new MissileBullets(), - new StandardBullets(), - new TurretBullets(), - new WeaponBullets(), + //bullets + new ArtilleryBullets(), + new FlakBullets(), + new MissileBullets(), + new StandardBullets(), + new TurretBullets(), + new WeaponBullets(), - //ammotypes - new AmmoTypes(), + //ammotypes + new AmmoTypes(), - //weapons - new Weapons(), + //weapons + new Weapons(), - //mechs - new Mechs(), + //mechs + new Mechs(), - //units - new UnitTypes(), + //units + new UnitTypes(), - //blocks - new Blocks(), - new DefenseBlocks(), - new DistributionBlocks(), - new ProductionBlocks(), - new TurretBlocks(), - new DebugBlocks(), - new LiquidBlocks(), - new StorageBlocks(), - new UnitBlocks(), - new PowerBlocks(), - new CraftingBlocks(), - new UpgradeBlocks(), - new OreBlocks(), + //blocks + new Blocks(), + new DefenseBlocks(), + new DistributionBlocks(), + new ProductionBlocks(), + new TurretBlocks(), + new DebugBlocks(), + new LiquidBlocks(), + new StorageBlocks(), + new UnitBlocks(), + new PowerBlocks(), + new CraftingBlocks(), + new UpgradeBlocks(), + new OreBlocks(), - //not really a content class, but this makes initialization easier - new ColorMapper(), + //not really a content class, but this makes initialization easier + new ColorMapper(), - //recipes - new Recipes(), + //recipes + new Recipes(), }; - /**Creates all content types.*/ + /** + * Creates all content types. + */ public static void load(){ if(loaded){ Log.info("Content already loaded, skipping."); @@ -103,11 +107,11 @@ public class ContentLoader { registerTypes(); - for (ContentList list : content){ + for(ContentList list : content){ list.load(); } - for (ContentList list : content){ + for(ContentList list : content){ if(list.getAll().size != 0){ String type = list.getAll().first().getContentTypeName(); @@ -134,7 +138,9 @@ public class ContentLoader { loaded = true; } - /**Initializes all content with the specified function.*/ + /** + * Initializes all content with the specified function. + */ public static void initialize(Consumer callable){ if(initialization.contains(callable)) return; @@ -155,8 +161,10 @@ public class ContentLoader { return contentMap; } - /**Registers sync IDs for all types of sync entities. - * Do not register units here!*/ + /** + * Registers sync IDs for all types of sync entities. + * Do not register units here! + */ private static void registerTypes(){ TypeTrait.registerType(Player.class, Player::new); TypeTrait.registerType(ItemDrop.class, ItemDrop::new); diff --git a/core/src/io/anuke/mindustry/core/Control.java b/core/src/io/anuke/mindustry/core/Control.java index 1c53e73208..6a9dca0998 100644 --- a/core/src/io/anuke/mindustry/core/Control.java +++ b/core/src/io/anuke/mindustry/core/Control.java @@ -30,148 +30,152 @@ import io.anuke.ucore.util.Atlas; import static io.anuke.mindustry.Vars.*; -/**Control module. +/** + * Control module. * Handles all input, saving, keybinds and keybinds. * Should not handle any logic-critical state. - * This class is not created in the headless server.*/ + * This class is not created in the headless server. + */ public class Control extends Module{ - /**Minimum period of time between the same sound being played.*/ - private static final long minSoundPeriod = 100; + /** + * Minimum period of time between the same sound being played. + */ + private static final long minSoundPeriod = 100; - private boolean hiscore = false; - private boolean wasPaused = false; - private Saves saves; - private ContentDatabase db; - private InputHandler[] inputs = {}; - private ObjectMap soundMap = new ObjectMap<>(); + private boolean hiscore = false; + private boolean wasPaused = false; + private Saves saves; + private ContentDatabase db; + private InputHandler[] inputs = {}; + private ObjectMap soundMap = new ObjectMap<>(); private Throwable error; private Input gdxInput; - public Control(){ + public Control(){ - saves = new Saves(); - db = new ContentDatabase(); + saves = new Saves(); + db = new ContentDatabase(); - Inputs.useControllers(!gwt); + Inputs.useControllers(!gwt); - Gdx.input.setCatchBackKey(true); + Gdx.input.setCatchBackKey(true); - Effects.setShakeFalloff(10000f); + Effects.setShakeFalloff(10000f); - ContentLoader.initialize(Content::init); - Core.atlas = new Atlas("sprites.atlas"); - Core.atlas.setErrorRegion("error"); - ContentLoader.initialize(Content::load); + ContentLoader.initialize(Content::init); + Core.atlas = new Atlas("sprites.atlas"); + Core.atlas.setErrorRegion("error"); + ContentLoader.initialize(Content::load); - db.load(); + db.load(); - gdxInput = Gdx.input; + gdxInput = Gdx.input; - Sounds.load("shoot.mp3", "place.mp3", "explosion.mp3", "enemyshoot.mp3", - "corexplode.mp3", "break.mp3", "spawn.mp3", "flame.mp3", "die.mp3", - "respawn.mp3", "purchase.mp3", "flame2.mp3", "bigshot.mp3", "laser.mp3", "lasershot.mp3", - "ping.mp3", "tesla.mp3", "waveend.mp3", "railgun.mp3", "blast.mp3", "bang2.mp3"); + Sounds.load("shoot.mp3", "place.mp3", "explosion.mp3", "enemyshoot.mp3", + "corexplode.mp3", "break.mp3", "spawn.mp3", "flame.mp3", "die.mp3", + "respawn.mp3", "purchase.mp3", "flame2.mp3", "bigshot.mp3", "laser.mp3", "lasershot.mp3", + "ping.mp3", "tesla.mp3", "waveend.mp3", "railgun.mp3", "blast.mp3", "bang2.mp3"); - Sounds.setFalloff(9000f); - Sounds.setPlayer((sound, volume) -> { - long time = TimeUtils.millis(); - long value = soundMap.get(sound, 0L); + Sounds.setFalloff(9000f); + Sounds.setPlayer((sound, volume) -> { + long time = TimeUtils.millis(); + long value = soundMap.get(sound, 0L); - if(TimeUtils.timeSinceMillis(value) >= minSoundPeriod){ - threads.run(() -> sound.play(volume)); - soundMap.put(sound, time); - } - }); + if(TimeUtils.timeSinceMillis(value) >= minSoundPeriod){ + threads.run(() -> sound.play(volume)); + soundMap.put(sound, time); + } + }); Musics.load("1.mp3", "2.mp3", "3.mp3", "4.mp3", "5.mp3", "6.mp3"); DefaultKeybinds.load(); - Settings.defaultList( - "ip", "localhost", - "port", port+"", - "color-0", Color.rgba8888(playerColors[8]), - "color-1", Color.rgba8888(playerColors[11]), - "color-2", Color.rgba8888(playerColors[13]), - "color-3", Color.rgba8888(playerColors[9]), - "name", "player", - "lastBuild", 0 - ); + Settings.defaultList( + "ip", "localhost", + "port", port + "", + "color-0", Color.rgba8888(playerColors[8]), + "color-1", Color.rgba8888(playerColors[11]), + "color-2", Color.rgba8888(playerColors[13]), + "color-3", Color.rgba8888(playerColors[9]), + "name", "player", + "lastBuild", 0 + ); - KeyBinds.load(); + KeyBinds.load(); - addPlayer(0); + addPlayer(0); - saves.load(); + saves.load(); - Events.on(StateChangeEvent.class, (from, to) -> { - if((from == State.playing && to == State.menu) || (from == State.menu && to != State.menu)){ - Timers.runTask(5f, Platform.instance::updateRPC); - } - }); + Events.on(StateChangeEvent.class, (from, to) -> { + if((from == State.playing && to == State.menu) || (from == State.menu && to != State.menu)){ + Timers.runTask(5f, Platform.instance::updateRPC); + } + }); - Events.on(PlayEvent.class, () -> { - for(Player player : players){ + Events.on(PlayEvent.class, () -> { + for(Player player : players){ player.add(); } - state.set(State.playing); - }); + state.set(State.playing); + }); - Events.on(WorldLoadGraphicsEvent.class, () -> { - if(mobile){ - Core.camera.position.set(players[0].x, players[0].y, 0); - } - }); + Events.on(WorldLoadGraphicsEvent.class, () -> { + if(mobile){ + Core.camera.position.set(players[0].x, players[0].y, 0); + } + }); - Events.on(ResetEvent.class, () -> { - for(Player player : players){ - player.reset(); + Events.on(ResetEvent.class, () -> { + for(Player player : players){ + player.reset(); } - hiscore = false; + hiscore = false; - saves.resetSave(); - }); + saves.resetSave(); + }); - Events.on(WaveEvent.class, () -> { + Events.on(WaveEvent.class, () -> { - int last = Settings.getInt("hiscore" + world.getMap().name, 0); + int last = Settings.getInt("hiscore" + world.getMap().name, 0); - if(state.wave > last && !state.mode.infiniteResources && !state.mode.disableWaveTimer){ - Settings.putInt("hiscore" + world.getMap().name, state.wave); - Settings.save(); - hiscore = true; - } + if(state.wave > last && !state.mode.infiniteResources && !state.mode.disableWaveTimer){ + Settings.putInt("hiscore" + world.getMap().name, state.wave); + Settings.save(); + hiscore = true; + } - Platform.instance.updateRPC(); - }); + Platform.instance.updateRPC(); + }); - Events.on(GameOverEvent.class, () -> { - Effects.shake(5, 6, Core.camera.position.x, Core.camera.position.y); + Events.on(GameOverEvent.class, () -> { + Effects.shake(5, 6, Core.camera.position.x, Core.camera.position.y); - //TODO game over effect - ui.restart.show(); + //TODO game over effect + ui.restart.show(); - Timers.runTask(30f, () -> state.set(State.menu)); - }); + Timers.runTask(30f, () -> state.set(State.menu)); + }); - Events.on(WorldLoadEvent.class, () -> threads.runGraphics(() -> Events.fire(WorldLoadGraphicsEvent.class))); - } + Events.on(WorldLoadEvent.class, () -> threads.runGraphics(() -> Events.fire(WorldLoadGraphicsEvent.class))); + } - public void addPlayer(int index){ - if(players.length != index + 1){ - Player[] old = players; - players = new Player[index + 1]; + public void addPlayer(int index){ + if(players.length != index + 1){ + Player[] old = players; + players = new Player[index + 1]; System.arraycopy(old, 0, players, 0, old.length); } if(inputs.length != index + 1){ - InputHandler[] oldi = inputs; - inputs = new InputHandler[index + 1]; - System.arraycopy(oldi, 0, inputs, 0, oldi.length); - } + InputHandler[] oldi = inputs; + inputs = new InputHandler[index + 1]; + System.arraycopy(oldi, 0, inputs, 0, oldi.length); + } Player setTo = (index == 0 ? null : players[0]); @@ -205,8 +209,8 @@ public class Control extends Module{ } public void removePlayer(){ - players[players.length-1].remove(); - inputs[inputs.length-1].remove(); + players[players.length - 1].remove(); + inputs[inputs.length - 1].remove(); Player[] old = players; players = new Player[players.length - 1]; @@ -217,191 +221,191 @@ public class Control extends Module{ System.arraycopy(oldi, 0, inputs, 0, inputs.length); } - public ContentDatabase database() { - return db; - } - - public Input gdxInput(){ - return gdxInput; + public ContentDatabase database(){ + return db; } - public void setError(Throwable error){ - this.error = error; - } - - public Saves getSaves(){ - return saves; - } - - public InputHandler input(int index){ - return inputs[index]; - } - - public void triggerUpdateInput(){ - //Gdx.input = proxy; + public Input gdxInput(){ + return gdxInput; } - public void playMap(Map map){ - ui.loadfrag.show(); + public void setError(Throwable error){ + this.error = error; + } - Timers.run(5f, () -> - threads.run(() -> { - logic.reset(); - world.loadMap(map); - logic.play(); + public Saves getSaves(){ + return saves; + } - Gdx.app.postRunnable(ui.loadfrag::hide); - })); - } + public InputHandler input(int index){ + return inputs[index]; + } - public boolean isHighScore(){ - return hiscore; - } + public void triggerUpdateInput(){ + //Gdx.input = proxy; + } - private void checkUnlockableBlocks(){ - TileEntity entity = players[0].getClosestCore(); + public void playMap(Map map){ + ui.loadfrag.show(); - if(entity == null) return; + Timers.run(5f, () -> + threads.run(() -> { + logic.reset(); + world.loadMap(map); + logic.play(); - entity.items.forEach((item, amount) -> control.database().unlockContent(item)); + Gdx.app.postRunnable(ui.loadfrag::hide); + })); + } - if(players[0].inventory.hasItem()){ - control.database().unlockContent(players[0].inventory.getItem().item); - } + public boolean isHighScore(){ + return hiscore; + } - for(int i = 0 ; i < Recipe.all().size; i ++){ - Recipe recipe = Recipe.all().get(i); - if(!recipe.debugOnly && entity.items.has(recipe.requirements, 1.4f)){ - if(control.database().unlockContent(recipe)){ - ui.hudfrag.showUnlock(recipe); - } - } - } - } + private void checkUnlockableBlocks(){ + TileEntity entity = players[0].getClosestCore(); - @Override - public void dispose(){ - Platform.instance.onGameExit(); - ContentLoader.dispose(); - Net.dispose(); - ui.editor.dispose(); - inputs = new InputHandler[]{}; - players = new Player[]{}; - } + if(entity == null) return; - @Override - public void pause(){ - wasPaused = state.is(State.paused); - if(state.is(State.playing)) state.set(State.paused); - } + entity.items.forEach((item, amount) -> control.database().unlockContent(item)); - @Override - public void resume(){ - if(state.is(State.paused) && !wasPaused){ + if(players[0].inventory.hasItem()){ + control.database().unlockContent(players[0].inventory.getItem().item); + } + + for(int i = 0; i < Recipe.all().size; i++){ + Recipe recipe = Recipe.all().get(i); + if(!recipe.debugOnly && entity.items.has(recipe.requirements, 1.4f)){ + if(control.database().unlockContent(recipe)){ + ui.hudfrag.showUnlock(recipe); + } + } + } + } + + @Override + public void dispose(){ + Platform.instance.onGameExit(); + ContentLoader.dispose(); + Net.dispose(); + ui.editor.dispose(); + inputs = new InputHandler[]{}; + players = new Player[]{}; + } + + @Override + public void pause(){ + wasPaused = state.is(State.paused); + if(state.is(State.playing)) state.set(State.paused); + } + + @Override + public void resume(){ + if(state.is(State.paused) && !wasPaused){ state.set(State.playing); - } - } + } + } - @Override - public void init(){ - EntityPhysics.initPhysics(); + @Override + public void init(){ + EntityPhysics.initPhysics(); - Platform.instance.updateRPC(); + Platform.instance.updateRPC(); - if(!Settings.has("4.0-warning")){ - Settings.putBool("4.0-warning", true); + if(!Settings.has("4.0-warning")){ + Settings.putBool("4.0-warning", true); - Timers.run(5f, () -> { - FloatingDialog dialog = new FloatingDialog("[orange]WARNING![]"); - dialog.buttons().addButton("$text.ok", dialog::hide).size(100f, 60f); - dialog.content().add("The beta version you are about to play should be considered very unstable, and is [accent]not representative of the final 4.0 release.[]\n\n " + - "A large portion of content is still unimplemented. \nAll current art and UI is temporary, and will be re-drawn before release. " + - "\n\n[accent]Saves and maps may be corrupted without warning between updates.[] You have been warned!").wrap().width(500f); - dialog.show(); + Timers.run(5f, () -> { + FloatingDialog dialog = new FloatingDialog("[orange]WARNING![]"); + dialog.buttons().addButton("$text.ok", dialog::hide).size(100f, 60f); + dialog.content().add("The beta version you are about to play should be considered very unstable, and is [accent]not representative of the final 4.0 release.[]\n\n " + + "A large portion of content is still unimplemented. \nAll current art and UI is temporary, and will be re-drawn before release. " + + "\n\n[accent]Saves and maps may be corrupted without warning between updates.[] You have been warned!").wrap().width(500f); + dialog.show(); - }); - } + }); + } - if(!Settings.has("4.0-no-sound")){ - Settings.putBool("4.0-no-sound", true); + if(!Settings.has("4.0-no-sound")){ + Settings.putBool("4.0-no-sound", true); - Timers.run(4f, () -> { - FloatingDialog dialog = new FloatingDialog("[orange]Attention![]"); - dialog.buttons().addButton("$text.ok", dialog::hide).size(100f, 60f); - dialog.content().add("You might have noticed that 4.0 does not have any sound.\nThis is [orange]intentional![] Sound will be added in a later update.\n\n[LIGHT_GRAY](now stop reporting this as a bug)").wrap().width(500f); - dialog.show(); + Timers.run(4f, () -> { + FloatingDialog dialog = new FloatingDialog("[orange]Attention![]"); + dialog.buttons().addButton("$text.ok", dialog::hide).size(100f, 60f); + dialog.content().add("You might have noticed that 4.0 does not have any sound.\nThis is [orange]intentional![] Sound will be added in a later update.\n\n[LIGHT_GRAY](now stop reporting this as a bug)").wrap().width(500f); + dialog.show(); - }); - } - } + }); + } + } - /**Called from main logic thread.*/ - public void runUpdateLogic(){ - if(!state.is(State.menu)) { - renderer.minimap().updateUnitArray(); - } - } + /** Called from main logic thread.*/ + public void runUpdateLogic(){ + if(!state.is(State.menu)){ + renderer.minimap().updateUnitArray(); + } + } - @Override - public void update(){ + @Override + public void update(){ - if(error != null){ - throw new RuntimeException(error); - } + if(error != null){ + throw new RuntimeException(error); + } if(Inputs.keyTap("console")){ - console = !console; - } + console = !console; + } saves.update(); - triggerUpdateInput(); + triggerUpdateInput(); - for(InputHandler inputHandler : inputs){ - inputHandler.updateController(); - } + for(InputHandler inputHandler : inputs){ + inputHandler.updateController(); + } - if(!state.is(State.menu)){ - for(InputHandler input : inputs){ - input.update(); + if(!state.is(State.menu)){ + for(InputHandler input : inputs){ + input.update(); } //check unlocks every 2 seconds - if(!state.mode.infiniteResources && Timers.get("timerCheckUnlock", 120)){ - checkUnlockableBlocks(); + if(!state.mode.infiniteResources && Timers.get("timerCheckUnlock", 120)){ + checkUnlockableBlocks(); - //save if the db changed, but don't save unlocks - if(db.isDirty() && !debug){ - db.save(); - } - } + //save if the db changed, but don't save unlocks + if(db.isDirty() && !debug){ + db.save(); + } + } - if(Inputs.keyTap("pause") && !ui.restart.isShown() && (state.is(State.paused) || state.is(State.playing))){ + if(Inputs.keyTap("pause") && !ui.restart.isShown() && (state.is(State.paused) || state.is(State.playing))){ state.set(state.is(State.playing) ? State.paused : State.playing); - } + } - if(Inputs.keyTap("menu")){ - if(state.is(State.paused)){ - ui.paused.hide(); + if(Inputs.keyTap("menu")){ + if(state.is(State.paused)){ + ui.paused.hide(); state.set(State.playing); - }else if (!ui.restart.isShown()){ - if(ui.chatfrag.chatOpen()) { - ui.chatfrag.hide(); - }else{ - ui.paused.show(); + }else if(!ui.restart.isShown()){ + if(ui.chatfrag.chatOpen()){ + ui.chatfrag.hide(); + }else{ + ui.paused.show(); state.set(State.paused); - } - } - } + } + } + } - if(!state.is(State.paused) || Net.active()){ - Entities.update(effectGroup); - Entities.update(groundEffectGroup); - } - }else{ - if(!state.is(State.paused) || Net.active()){ - Timers.update(); - } - } - } + if(!state.is(State.paused) || Net.active()){ + Entities.update(effectGroup); + Entities.update(groundEffectGroup); + } + }else{ + if(!state.is(State.paused) || Net.active()){ + Timers.update(); + } + } + } } diff --git a/core/src/io/anuke/mindustry/core/GameState.java b/core/src/io/anuke/mindustry/core/GameState.java index b368a9867e..92eee9a4d3 100644 --- a/core/src/io/anuke/mindustry/core/GameState.java +++ b/core/src/io/anuke/mindustry/core/GameState.java @@ -8,31 +8,30 @@ import io.anuke.mindustry.game.TeamInfo; import io.anuke.ucore.core.Events; public class GameState{ - private State state = State.menu; + public int wave = 1; + public float wavetime; + public boolean gameOver = false; + public GameMode mode = GameMode.waves; + public Difficulty difficulty = Difficulty.normal; + public boolean friendlyFire; + public WaveSpawner spawner = new WaveSpawner(); + public TeamInfo teams = new TeamInfo(); + private State state = State.menu; - public int wave = 1; - public float wavetime; - public boolean gameOver = false; - public GameMode mode = GameMode.waves; - public Difficulty difficulty = Difficulty.normal; - public boolean friendlyFire; - public WaveSpawner spawner = new WaveSpawner(); - public TeamInfo teams = new TeamInfo(); - - public void set(State astate){ - Events.fire(StateChangeEvent.class, state, astate); - state = astate; - } - - public boolean is(State astate){ - return state == astate; - } + public void set(State astate){ + Events.fire(StateChangeEvent.class, state, astate); + state = astate; + } - public State getState(){ - return state; - } - - public enum State{ - paused, playing, menu - } + public boolean is(State astate){ + return state == astate; + } + + public State getState(){ + return state; + } + + public enum State{ + paused, playing, menu + } } diff --git a/core/src/io/anuke/mindustry/core/Logic.java b/core/src/io/anuke/mindustry/core/Logic.java index d065be9098..2e07b0a2e6 100644 --- a/core/src/io/anuke/mindustry/core/Logic.java +++ b/core/src/io/anuke/mindustry/core/Logic.java @@ -24,13 +24,15 @@ import io.anuke.ucore.modules.Module; import static io.anuke.mindustry.Vars.*; -/**Logic module. +/** + * Logic module. * Handles all logic for entities and waves. * Handles game state events. * Does not store any game state itself. - * - * This class should not call any outside methods to change state of modules, but instead fire events.*/ -public class Logic extends Module { + *

+ * This class should not call any outside methods to change state of modules, but instead fire events. + */ +public class Logic extends Module{ public boolean doUpdate = true; public Logic(){ @@ -49,11 +51,11 @@ public class Logic extends Module { //fill inventory with items for debugging - for (TeamData team : state.teams.getTeams()) { - for (Tile tile : team.cores) { - if(debug) { - for (Item item : Item.all()) { - if (item.type == ItemType.material) { + for(TeamData team : state.teams.getTeams()){ + for(Tile tile : team.cores){ + if(debug){ + for(Item item : Item.all()){ + if(item.type == ItemType.material){ tile.entity.items.add(item, 1000); } } @@ -85,7 +87,7 @@ public class Logic extends Module { public void runWave(){ state.spawner.spawnEnemies(); - state.wave ++; + state.wave++; state.wavetime = wavespace * state.difficulty.timeScaling; Events.fire(WaveEvent.class); @@ -137,7 +139,8 @@ public class Logic extends Module { runWave(); } - if(!Entities.defaultGroup().isEmpty()) throw new RuntimeException("Do not add anything to the default group!"); + if(!Entities.defaultGroup().isEmpty()) + throw new RuntimeException("Do not add anything to the default group!"); Entities.update(bulletGroup); for(EntityGroup group : unitGroups){ diff --git a/core/src/io/anuke/mindustry/core/NetClient.java b/core/src/io/anuke/mindustry/core/NetClient.java index 5b820adf88..4ba984c6d3 100644 --- a/core/src/io/anuke/mindustry/core/NetClient.java +++ b/core/src/io/anuke/mindustry/core/NetClient.java @@ -36,38 +36,64 @@ import java.util.Random; import static io.anuke.mindustry.Vars.*; -public class NetClient extends Module { - private final static float dataTimeout = 60*18; +public class NetClient extends Module{ + private final static float dataTimeout = 60 * 18; private final static float playerSyncTime = 2; private Timer timer = new Timer(5); - /**Whether the client is currently connecting.*/ + /** + * Whether the client is currently connecting. + */ private boolean connecting = false; - /**If true, no message will be shown on disconnect.*/ + /** + * If true, no message will be shown on disconnect. + */ private boolean quiet = false; - /**Counter for data timeout.*/ + /** + * Counter for data timeout. + */ private float timeoutTime = 0f; - /**Last sent client snapshot ID.*/ + /** + * Last sent client snapshot ID. + */ private int lastSent; - /**Last snapshot ID recieved.*/ + /** + * Last snapshot ID recieved. + */ private int lastSnapshotBaseID = -1; - /**Last snapshot recieved.*/ + /** + * Last snapshot recieved. + */ private byte[] lastSnapshotBase; - /**Current snapshot that is being built from chinks.*/ + /** + * Current snapshot that is being built from chinks. + */ private byte[] currentSnapshot; - /**Array of recieved chunk statuses.*/ + /** + * Array of recieved chunk statuses. + */ private boolean[] recievedChunks; - /**Counter of how many chunks have been recieved.*/ + /** + * Counter of how many chunks have been recieved. + */ private int recievedChunkCounter; - /**ID of snapshot that is currently being constructed.*/ + /** + * ID of snapshot that is currently being constructed. + */ private int currentSnapshotID = -1; - /**Decoder for uncompressing snapshots.*/ + /** + * Decoder for uncompressing snapshots. + */ private DEZDecoder decoder = new DEZDecoder(); - /**List of entities that were removed, and need not be added while syncing.*/ + /** + * List of entities that were removed, and need not be added while syncing. + */ private IntSet removed = new IntSet(); - /**Byte stream for reading in snapshots.*/ + /** + * Byte stream for reading in snapshots. + */ private ReusableByteArrayInputStream byteStream = new ReusableByteArrayInputStream(); private DataInputStream dataStream = new DataInputStream(byteStream); @@ -120,7 +146,7 @@ public class NetClient extends Module { }); Net.handleClient(Disconnect.class, packet -> { - if (quiet) return; + if(quiet) return; Timers.runTask(3f, ui.loadfrag::hide); @@ -145,6 +171,166 @@ public class NetClient extends Module { }); } + @Remote(variants = Variant.one, priority = PacketPriority.high) + public static void onKick(KickReason reason){ + netClient.disconnectQuietly(); + state.set(State.menu); + if(!reason.quiet) ui.showError("$text.server.kicked." + reason.name()); + ui.loadfrag.hide(); + } + + @Remote(variants = Variant.one) + public static void onPositionSet(float x, float y){ + players[0].x = x; + players[0].y = y; + } + + @Remote(variants = Variant.one) + public static void onTraceInfo(TraceInfo info){ + Player player = playerGroup.getByID(info.playerid); + ui.traces.show(player, info); + } + + @Remote + public static void onPlayerDisconnect(int playerid){ + playerGroup.removeByID(playerid); + } + + @Remote(variants = Variant.one, priority = PacketPriority.low, unreliable = true) + public static void onSnapshot(byte[] chunk, int snapshotID, short chunkID, int totalLength, int base){ + if(NetServer.showSnapshotSize) + Log.info("Recieved snapshot: len {0} ID {1} chunkID {2} totalLength {3} base {4} client-base {5}", chunk.length, snapshotID, chunkID, totalLength, base, netClient.lastSnapshotBaseID); + + //skip snapshot IDs that have already been recieved OR snapshots that are too far in front + if(snapshotID < netClient.lastSnapshotBaseID || base != netClient.lastSnapshotBaseID){ + if(NetServer.showSnapshotSize) Log.info("//SKIP SNAPSHOT"); + return; + } + + try{ + byte[] snapshot; + + //total length exceeds that needed to hold one snapshot, therefore, it is split into chunks + if(totalLength > NetServer.maxSnapshotSize){ + //total amount of chunks to recieve + int totalChunks = Mathf.ceil((float) totalLength / NetServer.maxSnapshotSize); + + //reset status when a new snapshot sending begins + if(netClient.currentSnapshotID != snapshotID){ + netClient.currentSnapshotID = snapshotID; + netClient.currentSnapshot = new byte[totalLength]; + netClient.recievedChunkCounter = 0; + netClient.recievedChunks = new boolean[totalChunks]; + } + + //if this chunk hasn't been recieved yet... + if(!netClient.recievedChunks[chunkID]){ + netClient.recievedChunks[chunkID] = true; + netClient.recievedChunkCounter++; //update recieved status + //copy the recieved bytes into the holding array + System.arraycopy(chunk, 0, netClient.currentSnapshot, chunkID * NetServer.maxSnapshotSize, + Math.min(NetServer.maxSnapshotSize, totalLength - chunkID * NetServer.maxSnapshotSize)); + } + + //when all chunks have been recieved, begin + if(netClient.recievedChunkCounter >= totalChunks){ + snapshot = netClient.currentSnapshot; + }else{ + return; + } + }else{ + snapshot = chunk; + } + + if(NetServer.showSnapshotSize) + Log.info("Finished recieving snapshot ID {0} length {1}", snapshotID, chunk.length); + + byte[] result; + int length; + if(base == -1){ //fresh snapshot + result = snapshot; + length = snapshot.length; + netClient.lastSnapshotBase = Arrays.copyOf(snapshot, snapshot.length); + }else{ //otherwise, last snapshot must not be null, decode it + if(NetServer.showSnapshotSize) + Log.info("Base size: {0} Patch size: {1}", netClient.lastSnapshotBase.length, snapshot.length); + netClient.decoder.init(netClient.lastSnapshotBase, snapshot); + result = netClient.decoder.decode(); + length = netClient.decoder.getDecodedLength(); + //set last snapshot to a copy to prevent issues + netClient.lastSnapshotBase = Arrays.copyOf(result, length); + } + + netClient.lastSnapshotBaseID = snapshotID; + + //set stream bytes to begin snapshot reaeding + netClient.byteStream.setBytes(result, 0, length); + + //get data input for reading from the stream + DataInputStream input = netClient.dataStream; + + //read wave info + state.wavetime = input.readFloat(); + state.wave = input.readInt(); + + byte cores = input.readByte(); + for(int i = 0; i < cores; i++){ + int pos = input.readInt(); + world.tile(pos).entity.items.read(input); + } + + long timestamp = input.readLong(); + + byte totalGroups = input.readByte(); + //for each group... + for(int i = 0; i < totalGroups; i++){ + //read group info + byte groupID = input.readByte(); + short amount = input.readShort(); + + EntityGroup group = Entities.getGroup(groupID); + + //go through each entity + for(int j = 0; j < amount; j++){ + int position = netClient.byteStream.position(); //save position to check read/write correctness + int id = input.readInt(); + byte typeID = input.readByte(); + + SyncTrait entity = (SyncTrait) group.getByID(id); + boolean add = false; + + //entity must not be added yet, so create it + if(entity == null){ + entity = (SyncTrait) TypeTrait.getTypeByID(typeID).get(); //create entity from supplier + entity.resetID(id); + if(!netClient.isEntityUsed(entity.getID())){ + add = true; + } + } + + //read the entity + entity.read(input, timestamp); + + byte readLength = input.readByte(); + if(netClient.byteStream.position() - position - 1 != readLength){ + throw new RuntimeException("Error reading entity of type '" + group.getType() + "': Read length mismatch [write=" + readLength + ", read=" + (netClient.byteStream.position() - position - 1) + "]"); + } + + if(add){ + entity.add(); + netClient.addRemovedEntity(entity.getID()); + } + } + } + + //confirm that snapshot has been recieved + netClient.lastSnapshotBaseID = snapshotID; + + }catch(Exception e){ + throw new RuntimeException(e); + } + } + @Override public void update(){ if(!Net.client()) return; @@ -224,161 +410,4 @@ public class NetClient extends Module { return result; } } - - @Remote(variants = Variant.one, priority = PacketPriority.high) - public static void onKick(KickReason reason){ - netClient.disconnectQuietly(); - state.set(State.menu); - if(!reason.quiet) ui.showError("$text.server.kicked." + reason.name()); - ui.loadfrag.hide(); - } - - @Remote(variants = Variant.one) - public static void onPositionSet(float x, float y){ - players[0].x = x; - players[0].y = y; - } - - @Remote(variants = Variant.one) - public static void onTraceInfo(TraceInfo info){ - Player player = playerGroup.getByID(info.playerid); - ui.traces.show(player, info); - } - - @Remote - public static void onPlayerDisconnect(int playerid){ - playerGroup.removeByID(playerid); - } - - @Remote(variants = Variant.one, priority = PacketPriority.low, unreliable = true) - public static void onSnapshot(byte[] chunk, int snapshotID, short chunkID, int totalLength, int base){ - if(NetServer.showSnapshotSize) Log.info("Recieved snapshot: len {0} ID {1} chunkID {2} totalLength {3} base {4} client-base {5}", chunk.length, snapshotID, chunkID, totalLength, base, netClient.lastSnapshotBaseID); - - //skip snapshot IDs that have already been recieved OR snapshots that are too far in front - if(snapshotID < netClient.lastSnapshotBaseID || base != netClient.lastSnapshotBaseID){ - if(NetServer.showSnapshotSize) Log.info("//SKIP SNAPSHOT"); - return; - } - - try { - byte[] snapshot; - - //total length exceeds that needed to hold one snapshot, therefore, it is split into chunks - if(totalLength > NetServer.maxSnapshotSize) { - //total amount of chunks to recieve - int totalChunks = Mathf.ceil((float) totalLength / NetServer.maxSnapshotSize); - - //reset status when a new snapshot sending begins - if (netClient.currentSnapshotID != snapshotID) { - netClient.currentSnapshotID = snapshotID; - netClient.currentSnapshot = new byte[totalLength]; - netClient.recievedChunkCounter = 0; - netClient.recievedChunks = new boolean[totalChunks]; - } - - //if this chunk hasn't been recieved yet... - if (!netClient.recievedChunks[chunkID]) { - netClient.recievedChunks[chunkID] = true; - netClient.recievedChunkCounter ++; //update recieved status - //copy the recieved bytes into the holding array - System.arraycopy(chunk, 0, netClient.currentSnapshot, chunkID * NetServer.maxSnapshotSize, - Math.min(NetServer.maxSnapshotSize, totalLength - chunkID * NetServer.maxSnapshotSize)); - } - - //when all chunks have been recieved, begin - if(netClient.recievedChunkCounter >= totalChunks){ - snapshot = netClient.currentSnapshot; - }else{ - return; - } - }else{ - snapshot = chunk; - } - - if(NetServer.showSnapshotSize) Log.info("Finished recieving snapshot ID {0} length {1}", snapshotID, chunk.length); - - byte[] result; - int length; - if (base == -1) { //fresh snapshot - result = snapshot; - length = snapshot.length; - netClient.lastSnapshotBase = Arrays.copyOf(snapshot, snapshot.length); - } else { //otherwise, last snapshot must not be null, decode it - if(NetServer.showSnapshotSize) Log.info("Base size: {0} Patch size: {1}", netClient.lastSnapshotBase.length, snapshot.length); - netClient.decoder.init(netClient.lastSnapshotBase, snapshot); - result = netClient.decoder.decode(); - length = netClient.decoder.getDecodedLength(); - //set last snapshot to a copy to prevent issues - netClient.lastSnapshotBase = Arrays.copyOf(result, length); - } - - netClient.lastSnapshotBaseID = snapshotID; - - //set stream bytes to begin snapshot reaeding - netClient.byteStream.setBytes(result, 0, length); - - //get data input for reading from the stream - DataInputStream input = netClient.dataStream; - - //read wave info - state.wavetime = input.readFloat(); - state.wave = input.readInt(); - - byte cores = input.readByte(); - for (int i = 0; i < cores; i++) { - int pos = input.readInt(); - world.tile(pos).entity.items.read(input); - } - - long timestamp = input.readLong(); - - byte totalGroups = input.readByte(); - //for each group... - for (int i = 0; i < totalGroups; i++) { - //read group info - byte groupID = input.readByte(); - short amount = input.readShort(); - - EntityGroup group = Entities.getGroup(groupID); - - //go through each entity - for (int j = 0; j < amount; j++) { - int position = netClient.byteStream.position(); //save position to check read/write correctness - int id = input.readInt(); - byte typeID = input.readByte(); - - SyncTrait entity = (SyncTrait) group.getByID(id); - boolean add = false; - - //entity must not be added yet, so create it - if(entity == null){ - entity = (SyncTrait) TypeTrait.getTypeByID(typeID).get(); //create entity from supplier - entity.resetID(id); - if(!netClient.isEntityUsed(entity.getID())){ - add = true; - } - } - - //read the entity - entity.read(input, timestamp); - - byte readLength = input.readByte(); - if(netClient.byteStream.position() - position - 1 != readLength){ - throw new RuntimeException("Error reading entity of type '"+ group.getType() + "': Read length mismatch [write=" + readLength + ", read=" + (netClient.byteStream.position() - position - 1)+ "]"); - } - - if(add){ - entity.add(); - netClient.addRemovedEntity(entity.getID()); - } - } - } - - //confirm that snapshot has been recieved - netClient.lastSnapshotBaseID = snapshotID; - - }catch (Exception e){ - throw new RuntimeException(e); - } - } } \ No newline at end of file diff --git a/core/src/io/anuke/mindustry/core/NetServer.java b/core/src/io/anuke/mindustry/core/NetServer.java index 2abffd3abf..fa2c5ac999 100644 --- a/core/src/io/anuke/mindustry/core/NetServer.java +++ b/core/src/io/anuke/mindustry/core/NetServer.java @@ -45,20 +45,30 @@ public class NetServer extends Module{ private final static byte[] reusableSnapArray = new byte[maxSnapshotSize]; private final static float serverSyncTime = 4, kickDuration = 30 * 1000; private final static Vector2 vector = new Vector2(); - /**If a play goes away of their server-side coordinates by this distance, they get teleported back.*/ + /** + * If a play goes away of their server-side coordinates by this distance, they get teleported back. + */ private final static float correctDist = 16f; public final Administration admins = new Administration(); - /**Maps connection IDs to players.*/ + /** + * Maps connection IDs to players. + */ private IntMap connections = new IntMap<>(); private boolean closing = false; - /**Stream for writing player sync data to.*/ + /** + * Stream for writing player sync data to. + */ private CountableByteArrayOutputStream syncStream = new CountableByteArrayOutputStream(); - /**Data stream for writing player sync data to.*/ + /** + * Data stream for writing player sync data to. + */ private DataOutputStream dataStream = new DataOutputStream(syncStream); - /**Encoder for computing snapshot deltas.*/ + /** + * Encoder for computing snapshot deltas. + */ private DEZEncoder encoder = new DEZEncoder(); public NetServer(){ @@ -105,14 +115,14 @@ public class NetServer extends Module{ boolean preventDuplicates = headless; - if(preventDuplicates) { - for (Player player : playerGroup.all()) { - if (player.name.equalsIgnoreCase(packet.name)) { + if(preventDuplicates){ + for(Player player : playerGroup.all()){ + if(player.name.equalsIgnoreCase(packet.name)){ kick(id, KickReason.nameInUse); return; } - if (player.uuid.equals(packet.uuid)) { + if(player.uuid.equals(packet.uuid)){ kick(id, KickReason.idInUse); return; } @@ -181,7 +191,7 @@ public class NetServer extends Module{ long elapsed = TimeUtils.timeSinceMillis(connection.lastRecievedClientTime); - float maxSpeed = (packet.boosting && !player.mech.flying ? player.mech.boostSpeed : player.mech.speed)*2.5f; + float maxSpeed = (packet.boosting && !player.mech.flying ? player.mech.boostSpeed : player.mech.speed) * 2.5f; //extra 1.1x multiplicaton is added just in case float maxMove = elapsed / 1000f * 60f * maxSpeed * 1.1f; @@ -238,6 +248,86 @@ public class NetServer extends Module{ }); } + /** + * Sends a raw byte[] snapshot to a client, splitting up into chunks when needed. + */ + private static void sendSplitSnapshot(int userid, byte[] bytes, int snapshotID, int base){ + if(bytes.length < maxSnapshotSize){ + Call.onSnapshot(userid, bytes, snapshotID, (short) 0, bytes.length, base); + }else{ + int remaining = bytes.length; + int offset = 0; + int chunkid = 0; + while(remaining > 0){ + int used = Math.min(remaining, maxSnapshotSize); + byte[] toSend; + //re-use sent byte arrays when possible + if(used == maxSnapshotSize){ + toSend = reusableSnapArray; + System.arraycopy(bytes, offset, toSend, 0, Math.min(offset + maxSnapshotSize, bytes.length) - offset); + }else{ + toSend = Arrays.copyOfRange(bytes, offset, Math.min(offset + maxSnapshotSize, bytes.length)); + } + Call.onSnapshot(userid, toSend, snapshotID, (short) chunkid, bytes.length, base); + + remaining -= used; + offset += used; + chunkid++; + } + } + } + + public static void onDisconnect(Player player){ + Call.sendMessage("[accent]" + player.name + " has disconnected."); + Call.onPlayerDisconnect(player.id); + player.remove(); + netServer.connections.remove(player.con.id); + } + + @Remote(targets = Loc.client, called = Loc.server) + public static void onAdminRequest(Player player, Player other, AdminAction action){ + + if(!player.isAdmin){ + Log.err("ACCESS DENIED: Player {0} / {1} attempted to perform admin action without proper security access.", + player.name, player.con.address); + return; + } + + if(other == null || (other.isAdmin && other != player)){ //fun fact: this means you can ban yourself + Log.err("{0} attempted to perform admin action on nonexistant or admin player.", player.name); + return; + } + + if(action == AdminAction.wave){ + //no verification is done, so admins can hypothetically spam waves + //not a real issue, because server owners may want to do just that + state.wavetime = 0f; + }else if(action == AdminAction.ban){ + netServer.admins.banPlayerIP(other.con.address); + netServer.kick(other.con.id, KickReason.banned); + Log.info("&lc{0} has banned {1}.", player.name, other.name); + }else if(action == AdminAction.kick){ + netServer.kick(other.con.id, KickReason.kick); + Log.info("&lc{0} has kicked {1}.", player.name, other.name); + }else if(action == AdminAction.trace){ + //TODO + if(player.con != null){ + Call.onTraceInfo(player.con.id, netServer.admins.getTraceByID(other.uuid)); + }else{ + NetClient.onTraceInfo(netServer.admins.getTraceByID(other.uuid)); + } + Log.info("&lc{0} has requested trace info of {1}.", player.name, other.name); + } + } + + @Remote(targets = Loc.client) + public static void connectConfirm(Player player){ + player.add(); + player.con.hasConnected = true; + Call.sendMessage("[accent]" + player.name + " has connected."); + Log.info("&y{0} has connected.", player.name); + } + public void update(){ if(!headless && !closing && Net.server() && state.is(State.menu)){ closing = true; @@ -270,7 +360,7 @@ public class NetServer extends Module{ if((reason == KickReason.kick || reason == KickReason.banned) && admins.getTraceByID(getUUID(con.id)).uuid != null){ PlayerInfo info = admins.getInfo(admins.getTraceByID(getUUID(con.id)).uuid); - info.timesKicked ++; + info.timesKicked++; info.lastKicked = TimeUtils.millis(); } @@ -288,7 +378,7 @@ public class NetServer extends Module{ String fixName(String name){ - for(int i = 0; i < name.length(); i ++){ + for(int i = 0; i < name.length(); i++){ if(name.charAt(i) == '[' && i != name.length() - 1 && name.charAt(i + 1) != '[' && (i == 0 || name.charAt(i - 1) != '[')){ String prev = name.substring(0, i); String next = name.substring(i); @@ -303,7 +393,7 @@ public class NetServer extends Module{ String checkColor(String str){ - for(int i = 1; i < str.length(); i ++){ + for(int i = 1; i < str.length(); i++){ if(str.charAt(i) == ']'){ String color = str.substring(1, i); @@ -318,7 +408,7 @@ public class NetServer extends Module{ if(result.a <= 0.8f){ return str.substring(i + 1); } - }catch (Exception e){ + }catch(Exception e){ return str; } } @@ -328,10 +418,10 @@ public class NetServer extends Module{ } void sync(){ - try { + try{ //iterate through each player - for (Player player : connections.values()) { + for(Player player : connections.values()){ NetConnection connection = player.con; if(!connection.isConnected()){ @@ -344,7 +434,8 @@ public class NetServer extends Module{ //if the player hasn't acknowledged that it has recieved the packet, send the same thing again if(connection.currentBaseID < connection.lastSentSnapshotID){ - if(showSnapshotSize) Log.info("Re-sending snapshot: {0} bytes, ID {1} base {2} baselength {3}", connection.lastSentSnapshot.length, connection.lastSentSnapshotID, connection.lastSentBase, connection.currentBaseSnapshot.length); + if(showSnapshotSize) + Log.info("Re-sending snapshot: {0} bytes, ID {1} base {2} baselength {3}", connection.lastSentSnapshot.length, connection.lastSentSnapshotID, connection.lastSentBase, connection.currentBaseSnapshot.length); sendSplitSnapshot(connection.id, connection.lastSentSnapshot, connection.lastSentSnapshotID, connection.lastSentBase); return; } @@ -371,17 +462,17 @@ public class NetServer extends Module{ int totalGroups = 0; - for (EntityGroup group : Entities.getAllGroups()) { - if (!group.isEmpty() && (group.all().get(0) instanceof SyncTrait)) totalGroups ++; + for(EntityGroup group : Entities.getAllGroups()){ + if(!group.isEmpty() && (group.all().get(0) instanceof SyncTrait)) totalGroups++; } //write total amount of serializable groups dataStream.writeByte(totalGroups); //check for syncable groups - for (EntityGroup group : Entities.getAllGroups()) { + for(EntityGroup group : Entities.getAllGroups()){ //TODO range-check sync positions to optimize? - if (group.isEmpty() || !(group.all().get(0) instanceof SyncTrait)) continue; + if(group.isEmpty() || !(group.all().get(0) instanceof SyncTrait)) continue; //make sure mapping is enabled for this group if(!group.mappingEnabled()){ @@ -391,8 +482,8 @@ public class NetServer extends Module{ int amount = 0; for(Entity entity : group.all()){ - if(((SyncTrait)entity).isSyncing()){ - amount ++; + if(((SyncTrait) entity).isSyncing()){ + amount++; } } @@ -401,15 +492,16 @@ public class NetServer extends Module{ dataStream.writeShort(amount); for(Entity entity : group.all()){ - if(!((SyncTrait)entity).isSyncing()) continue; + if(!((SyncTrait) entity).isSyncing()) continue; int position = syncStream.position(); //write all entities now dataStream.writeInt(entity.getID()); //write id - dataStream.writeByte(((SyncTrait)entity).getTypeID()); //write type ID - ((SyncTrait)entity).write(dataStream); //write entity + dataStream.writeByte(((SyncTrait) entity).getTypeID()); //write type ID + ((SyncTrait) entity).write(dataStream); //write entity int length = syncStream.position() - position; //length must always be less than 127 bytes - if(length > 127) throw new RuntimeException("Write size for entity of type " + group.getType() + " must not exceed 127!"); + if(length > 127) + throw new RuntimeException("Write size for entity of type " + group.getType() + " must not exceed 127!"); dataStream.writeByte(length); } } @@ -433,7 +525,8 @@ public class NetServer extends Module{ //send diff, otherwise byte[] diff = ByteDeltaEncoder.toDiff(new ByteMatcherHash(connection.currentBaseSnapshot, bytes), encoder); - if(showSnapshotSize) Log.info("Shrank snapshot: {0} -> {1}, Base {2} ID {3} base length = {4}", bytes.length, diff.length, connection.currentBaseID, connection.currentBaseID + 1, connection.currentBaseSnapshot.length); + if(showSnapshotSize) + Log.info("Shrank snapshot: {0} -> {1}, Base {2} ID {3} base length = {4}", bytes.length, diff.length, connection.currentBaseID, connection.currentBaseID + 1, connection.currentBaseSnapshot.length); sendSplitSnapshot(connection.id, diff, connection.currentBaseID + 1, connection.currentBaseID); connection.lastSentSnapshot = diff; connection.lastSentSnapshotID = connection.currentBaseID + 1; @@ -441,86 +534,8 @@ public class NetServer extends Module{ } } - }catch (IOException e){ + }catch(IOException e){ e.printStackTrace(); } } - - /**Sends a raw byte[] snapshot to a client, splitting up into chunks when needed.*/ - private static void sendSplitSnapshot(int userid, byte[] bytes, int snapshotID, int base){ - if(bytes.length < maxSnapshotSize){ - Call.onSnapshot(userid, bytes, snapshotID, (short)0, bytes.length, base); - }else{ - int remaining = bytes.length; - int offset = 0; - int chunkid = 0; - while(remaining > 0){ - int used = Math.min(remaining, maxSnapshotSize); - byte[] toSend; - //re-use sent byte arrays when possible - if(used == maxSnapshotSize){ - toSend = reusableSnapArray; - System.arraycopy(bytes, offset, toSend, 0, Math.min(offset + maxSnapshotSize, bytes.length) - offset); - }else { - toSend = Arrays.copyOfRange(bytes, offset, Math.min(offset + maxSnapshotSize, bytes.length)); - } - Call.onSnapshot(userid, toSend, snapshotID, (short)chunkid, bytes.length, base); - - remaining -= used; - offset += used; - chunkid ++; - } - } - } - - public static void onDisconnect(Player player){ - Call.sendMessage("[accent]" + player.name + " has disconnected."); - Call.onPlayerDisconnect(player.id); - player.remove(); - netServer.connections.remove(player.con.id); - } - - @Remote(targets = Loc.client, called = Loc.server) - public static void onAdminRequest(Player player, Player other, AdminAction action){ - - if(!player.isAdmin){ - Log.err("ACCESS DENIED: Player {0} / {1} attempted to perform admin action without proper security access.", - player.name, player.con.address); - return; - } - - if(other == null || (other.isAdmin && other != player)){ //fun fact: this means you can ban yourself - Log.err("{0} attempted to perform admin action on nonexistant or admin player.", player.name); - return; - } - - if(action == AdminAction.wave) { - //no verification is done, so admins can hypothetically spam waves - //not a real issue, because server owners may want to do just that - state.wavetime = 0f; - }else if(action == AdminAction.ban){ - netServer.admins.banPlayerIP(other.con.address); - netServer.kick(other.con.id, KickReason.banned); - Log.info("&lc{0} has banned {1}.", player.name, other.name); - }else if(action == AdminAction.kick){ - netServer.kick(other.con.id, KickReason.kick); - Log.info("&lc{0} has kicked {1}.", player.name, other.name); - }else if(action == AdminAction.trace){ - //TODO - if(player.con != null) { - Call.onTraceInfo(player.con.id, netServer.admins.getTraceByID(other.uuid)); - }else{ - NetClient.onTraceInfo(netServer.admins.getTraceByID(other.uuid)); - } - Log.info("&lc{0} has requested trace info of {1}.", player.name, other.name); - } - } - - @Remote(targets = Loc.client) - public static void connectConfirm(Player player){ - player.add(); - player.con.hasConnected = true; - Call.sendMessage("[accent]" + player.name + " has connected."); - Log.info("&y{0} has connected.", player.name); - } } diff --git a/core/src/io/anuke/mindustry/core/Platform.java b/core/src/io/anuke/mindustry/core/Platform.java index c4064fd5fb..7462ba3eb2 100644 --- a/core/src/io/anuke/mindustry/core/Platform.java +++ b/core/src/io/anuke/mindustry/core/Platform.java @@ -11,63 +11,129 @@ import java.util.Date; import java.util.Locale; import java.util.Random; -public abstract class Platform { - /**Each separate game platform should set this instance to their own implementation.*/ - public static Platform instance = new Platform() {}; +public abstract class Platform{ + /** + * Each separate game platform should set this instance to their own implementation. + */ + public static Platform instance = new Platform(){ + }; - /**Format the date using the default date formatter.*/ - public String format(Date date){return "invalid";} - /**Format a number by adding in commas or periods where needed.*/ - public String format(int number){return "invalid";} - /**Show a native error dialog.*/ - public void showError(String text){} - /**Add a text input dialog that should show up after the field is tapped.*/ - public void addDialog(TextField field){ - addDialog(field, 16); - } - /**See addDialog().*/ - public void addDialog(TextField field, int maxLength){} - /**Update discord RPC.*/ - public void updateRPC(){} - /**Called when the game is exited.*/ - public void onGameExit(){} - /**Open donation dialog. Currently android only.*/ - public void openDonations(){} - /**Whether donating is supported.*/ - public boolean canDonate(){ - return false; - } - /**Whether discord RPC is supported.*/ - public boolean hasDiscord(){return true;} - /**Return the localized name for the locale. This is basically a workaround for GWT not supporting getName().*/ - public String getLocaleName(Locale locale){ - return locale.toString(); - } - /**Whether joining games is supported.*/ - public boolean canJoinGame(){ - return true; - } - /**Whether debug mode is enabled.*/ - public boolean isDebug(){return false;} - /**Must be a base64 string 8 bytes in length.*/ - public String getUUID(){ - String uuid = Settings.getString("uuid", ""); - if(uuid.isEmpty()){ - byte[] result = new byte[8]; - new Random().nextBytes(result); - uuid = new String(Base64Coder.encode(result)); - Settings.putString("uuid", uuid); - Settings.save(); - return uuid; - } - return uuid; - } - /**Only used for iOS or android: open the share menu for a map or save.*/ - public void shareFile(FileHandle file){} - /**Download a file. Only used on GWT backend.*/ - public void downloadFile(String name, byte[] bytes){} + /** + * Format the date using the default date formatter. + */ + public String format(Date date){ + return "invalid"; + } - /**Show a file chooser. Desktop only. + /** + * Format a number by adding in commas or periods where needed. + */ + public String format(int number){ + return "invalid"; + } + + /** + * Show a native error dialog. + */ + public void showError(String text){ + } + + /** + * Add a text input dialog that should show up after the field is tapped. + */ + public void addDialog(TextField field){ + addDialog(field, 16); + } + + /** + * See addDialog(). + */ + public void addDialog(TextField field, int maxLength){ + } + + /** + * Update discord RPC. + */ + public void updateRPC(){ + } + + /** + * Called when the game is exited. + */ + public void onGameExit(){ + } + + /** + * Open donation dialog. Currently android only. + */ + public void openDonations(){ + } + + /** + * Whether donating is supported. + */ + public boolean canDonate(){ + return false; + } + + /** + * Whether discord RPC is supported. + */ + public boolean hasDiscord(){ + return true; + } + + /** + * Return the localized name for the locale. This is basically a workaround for GWT not supporting getName(). + */ + public String getLocaleName(Locale locale){ + return locale.toString(); + } + + /** + * Whether joining games is supported. + */ + public boolean canJoinGame(){ + return true; + } + + /** + * Whether debug mode is enabled. + */ + public boolean isDebug(){ + return false; + } + + /** + * Must be a base64 string 8 bytes in length. + */ + public String getUUID(){ + String uuid = Settings.getString("uuid", ""); + if(uuid.isEmpty()){ + byte[] result = new byte[8]; + new Random().nextBytes(result); + uuid = new String(Base64Coder.encode(result)); + Settings.putString("uuid", uuid); + Settings.save(); + return uuid; + } + return uuid; + } + + /** + * Only used for iOS or android: open the share menu for a map or save. + */ + public void shareFile(FileHandle file){ + } + + /** + * Download a file. Only used on GWT backend. + */ + public void downloadFile(String name, byte[] bytes){ + } + + /** + * Show a file chooser. Desktop only. * * @param text File chooser title text * @param content Description of the type of files to be loaded @@ -75,24 +141,54 @@ public abstract class Platform { * @param open Whether to open or save files * @param filetype File extension to filter */ - public void showFileChooser(String text, String content, Consumer cons, boolean open, String filetype){} - /**Use the default thread provider from the kryonet module for this.*/ - public ThreadProvider getThreadProvider(){ - return new ThreadProvider() { - @Override public boolean isOnThread() {return true;} - @Override public void sleep(long ms) {} - @Override public void start(Runnable run) {} - @Override public void stop() {} - @Override public void notify(Object object) {} - @Override public void wait(Object object) {} - }; - } + public void showFileChooser(String text, String content, Consumer cons, boolean open, String filetype){ + } - //TODO iOS implementation - /**Forces the app into landscape mode. Currently Android only.*/ - public void beginForceLandscape(){} + /** + * Use the default thread provider from the kryonet module for this. + */ + public ThreadProvider getThreadProvider(){ + return new ThreadProvider(){ + @Override + public boolean isOnThread(){ + return true; + } - //TODO iOS implementation - /**Stops forcing the app into landscape orientation. Currently Android only.*/ - public void endForceLandscape(){} + @Override + public void sleep(long ms){ + } + + @Override + public void start(Runnable run){ + } + + @Override + public void stop(){ + } + + @Override + public void notify(Object object){ + } + + @Override + public void wait(Object object){ + } + }; + } + + //TODO iOS implementation + + /** + * Forces the app into landscape mode. Currently Android only. + */ + public void beginForceLandscape(){ + } + + //TODO iOS implementation + + /** + * Stops forcing the app into landscape orientation. Currently Android only. + */ + public void endForceLandscape(){ + } } diff --git a/core/src/io/anuke/mindustry/core/Renderer.java b/core/src/io/anuke/mindustry/core/Renderer.java index 095fac27d5..29ddc9762a 100644 --- a/core/src/io/anuke/mindustry/core/Renderer.java +++ b/core/src/io/anuke/mindustry/core/Renderer.java @@ -47,429 +47,429 @@ import static io.anuke.ucore.core.Core.batch; import static io.anuke.ucore.core.Core.camera; public class Renderer extends RendererModule{ - public Surface effectSurface; - - private int targetscale = baseCameraScale; - private Texture background = new Texture("sprites/background.png"); + public Surface effectSurface; - private Rectangle rect = new Rectangle(), rect2 = new Rectangle(); - private Vector2 avgPosition = new Translator(); - private Vector2 tmpVector1 = new Translator(); - private Vector2 tmpVector2 = new Translator(); + private int targetscale = baseCameraScale; + private Texture background = new Texture("sprites/background.png"); - private BlockRenderer blocks = new BlockRenderer(); - private MinimapRenderer minimap = new MinimapRenderer(); - private OverlayRenderer overlays = new OverlayRenderer(); - private FogRenderer fog = new FogRenderer(); + private Rectangle rect = new Rectangle(), rect2 = new Rectangle(); + private Vector2 avgPosition = new Translator(); + private Vector2 tmpVector1 = new Translator(); + private Vector2 tmpVector2 = new Translator(); - public Renderer() { - pixelate = true; - Lines.setCircleVertices(14); + private BlockRenderer blocks = new BlockRenderer(); + private MinimapRenderer minimap = new MinimapRenderer(); + private OverlayRenderer overlays = new OverlayRenderer(); + private FogRenderer fog = new FogRenderer(); - Shaders.init(); + public Renderer(){ + pixelate = true; + Lines.setCircleVertices(14); - Core.cameraScale = baseCameraScale; - Effects.setEffectProvider((effect, color, x, y, rotation, data) -> { - if(effect == Fx.none) return; - if(Settings.getBool("effects")){ - Rectangle view = rect.setSize(camera.viewportWidth, camera.viewportHeight) - .setCenter(camera.position.x, camera.position.y); - Rectangle pos = rect2.setSize(effect.size).setCenter(x, y); + Shaders.init(); - if(view.overlaps(pos)){ + Core.cameraScale = baseCameraScale; + Effects.setEffectProvider((effect, color, x, y, rotation, data) -> { + if(effect == Fx.none) return; + if(Settings.getBool("effects")){ + Rectangle view = rect.setSize(camera.viewportWidth, camera.viewportHeight) + .setCenter(camera.position.x, camera.position.y); + Rectangle pos = rect2.setSize(effect.size).setCenter(x, y); - if(!(effect instanceof GroundEffect)) { - EffectEntity entity = Pooling.obtain(EffectEntity.class); - entity.effect = effect; - entity.color = color; - entity.rotation = rotation; - entity.data = data; - entity.id ++; - entity.set(x, y); - if(data instanceof BaseEntity){ - entity.setParent((BaseEntity)data); - } - threads.runGraphics(() -> effectGroup.add(entity)); - }else{ - GroundEffectEntity entity = Pooling.obtain(GroundEffectEntity.class); - entity.effect = effect; - entity.color = color; - entity.rotation = rotation; - entity.id ++; - entity.data = data; - entity.set(x, y); - threads.runGraphics(() -> groundEffectGroup.add(entity)); - } - } - } - }); + if(view.overlaps(pos)){ - Cursors.cursorScaling = 3; - Cursors.outlineColor = Color.valueOf("444444"); + if(!(effect instanceof GroundEffect)){ + EffectEntity entity = Pooling.obtain(EffectEntity.class); + entity.effect = effect; + entity.color = color; + entity.rotation = rotation; + entity.data = data; + entity.id++; + entity.set(x, y); + if(data instanceof BaseEntity){ + entity.setParent((BaseEntity) data); + } + threads.runGraphics(() -> effectGroup.add(entity)); + }else{ + GroundEffectEntity entity = Pooling.obtain(GroundEffectEntity.class); + entity.effect = effect; + entity.color = color; + entity.rotation = rotation; + entity.id++; + entity.data = data; + entity.set(x, y); + threads.runGraphics(() -> groundEffectGroup.add(entity)); + } + } + } + }); - Cursors.arrow = Cursors.loadCursor("cursor"); - Cursors.hand = Cursors.loadCursor("hand"); - Cursors.ibeam = Cursors.loadCursor("ibar"); - Cursors.loadCustom("drill"); - Cursors.loadCustom("unload"); + Cursors.cursorScaling = 3; + Cursors.outlineColor = Color.valueOf("444444"); - clearColor = Hue.lightness(0.4f); - clearColor.a = 1f; + Cursors.arrow = Cursors.loadCursor("cursor"); + Cursors.hand = Cursors.loadCursor("hand"); + Cursors.ibeam = Cursors.loadCursor("ibar"); + Cursors.loadCustom("drill"); + Cursors.loadCustom("unload"); - background.setWrap(TextureWrap.Repeat, TextureWrap.Repeat); - } + clearColor = Hue.lightness(0.4f); + clearColor.a = 1f; - @Override - public void init(){ - int scale = Core.cameraScale; + background.setWrap(TextureWrap.Repeat, TextureWrap.Repeat); + } + + @Override + public void init(){ + int scale = Core.cameraScale; effectSurface = Graphics.createSurface(scale); - pixelSurface = Graphics.createSurface(scale); - } + pixelSurface = Graphics.createSurface(scale); + } - @Override - public void update(){ + @Override + public void update(){ - if(Core.cameraScale != targetscale){ - float targetzoom = (float) Core.cameraScale / targetscale; - camera.zoom = Mathf.lerpDelta(camera.zoom, targetzoom, 0.2f); + if(Core.cameraScale != targetscale){ + float targetzoom = (float) Core.cameraScale / targetscale; + camera.zoom = Mathf.lerpDelta(camera.zoom, targetzoom, 0.2f); - if(Mathf.in(camera.zoom, targetzoom, 0.005f)){ - camera.zoom = 1f; - Graphics.setCameraScale(targetscale); - for(Player player : players) { + if(Mathf.in(camera.zoom, targetzoom, 0.005f)){ + camera.zoom = 1f; + Graphics.setCameraScale(targetscale); + for(Player player : players){ control.input(player.playerIndex).resetCursor(); } - } - }else{ - camera.zoom = Mathf.lerpDelta(camera.zoom, 1f, 0.2f); - } + } + }else{ + camera.zoom = Mathf.lerpDelta(camera.zoom, 1f, 0.2f); + } - if(state.is(State.menu)){ - Graphics.clear(Color.BLACK); - }else{ + if(state.is(State.menu)){ + Graphics.clear(Color.BLACK); + }else{ Vector2 position = averagePosition(); if(!mobile){ - setCamera(position.x + 0.0001f, position.y + 0.0001f); - } + setCamera(position.x + 0.0001f, position.y + 0.0001f); + } - clampCamera(-tilesize / 2f, -tilesize / 2f + 1, world.width() * tilesize - tilesize / 2f, world.height() * tilesize - tilesize / 2f); + clampCamera(-tilesize / 2f, -tilesize / 2f + 1, world.width() * tilesize - tilesize / 2f, world.height() * tilesize - tilesize / 2f); - float prex = camera.position.x, prey = camera.position.y; - updateShake(0.75f); + float prex = camera.position.x, prey = camera.position.y; + updateShake(0.75f); - float deltax = camera.position.x - prex, deltay = camera.position.y - prey; - float lastx = camera.position.x, lasty = camera.position.y; - - if(snapCamera){ - camera.position.set((int) camera.position.x, (int) camera.position.y, 0); - } - - if(Gdx.graphics.getHeight() / Core.cameraScale % 2 == 1){ - camera.position.add(0, -0.5f, 0); - } + float deltax = camera.position.x - prex, deltay = camera.position.y - prey; + float lastx = camera.position.x, lasty = camera.position.y; - if(Gdx.graphics.getWidth() / Core.cameraScale % 2 == 1){ - camera.position.add(-0.5f, 0, 0); - } - - draw(); + if(snapCamera){ + camera.position.set((int) camera.position.x, (int) camera.position.y, 0); + } - camera.position.set(lastx - deltax, lasty - deltay, 0); - } + if(Gdx.graphics.getHeight() / Core.cameraScale % 2 == 1){ + camera.position.add(0, -0.5f, 0); + } - if(debug && !ui.chatfrag.chatOpen()) { - renderer.record(); //this only does something if GdxGifRecorder is on the class path, which it usually isn't - } - } + if(Gdx.graphics.getWidth() / Core.cameraScale % 2 == 1){ + camera.position.add(-0.5f, 0, 0); + } - @Override - public void draw(){ - camera.update(); + draw(); - Graphics.clear(clearColor); - - batch.setProjectionMatrix(camera.combined); + camera.position.set(lastx - deltax, lasty - deltay, 0); + } - Graphics.surface(pixelSurface, false); + if(debug && !ui.chatfrag.chatOpen()){ + renderer.record(); //this only does something if GdxGifRecorder is on the class path, which it usually isn't + } + } - drawPadding(); - - blocks.drawFloor(); + @Override + public void draw(){ + camera.update(); - drawAndInterpolate(groundEffectGroup, e -> e instanceof BelowLiquidTrait); - drawAndInterpolate(puddleGroup); - drawAndInterpolate(groundEffectGroup, e -> !(e instanceof BelowLiquidTrait)); + Graphics.clear(clearColor); - blocks.processBlocks(); - blocks.drawBlocks(Layer.block); + batch.setProjectionMatrix(camera.combined); - Graphics.shader(Shaders.blockbuild, false); + Graphics.surface(pixelSurface, false); + + drawPadding(); + + blocks.drawFloor(); + + drawAndInterpolate(groundEffectGroup, e -> e instanceof BelowLiquidTrait); + drawAndInterpolate(puddleGroup); + drawAndInterpolate(groundEffectGroup, e -> !(e instanceof BelowLiquidTrait)); + + blocks.processBlocks(); + blocks.drawBlocks(Layer.block); + + Graphics.shader(Shaders.blockbuild, false); blocks.drawBlocks(Layer.placement); Graphics.shader(); blocks.drawBlocks(Layer.overlay); if(itemGroup.size() > 0){ - Shaders.outline.color.set(Team.none.color); + Shaders.outline.color.set(Team.none.color); - Graphics.beginShaders(Shaders.outline); - drawAndInterpolate(itemGroup); - Graphics.endShaders(); - } + Graphics.beginShaders(Shaders.outline); + drawAndInterpolate(itemGroup); + Graphics.endShaders(); + } drawAllTeams(false); - blocks.skipLayer(Layer.turret); - blocks.drawBlocks(Layer.laser); + blocks.skipLayer(Layer.turret); + blocks.drawBlocks(Layer.laser); - drawFlyerShadows(); + drawFlyerShadows(); - drawAllTeams(true); + drawAllTeams(true); - drawAndInterpolate(bulletGroup); - drawAndInterpolate(effectGroup); + drawAndInterpolate(bulletGroup); + drawAndInterpolate(effectGroup); - overlays.drawBottom(); - drawAndInterpolate(playerGroup, p -> true, Player::drawBuildRequests); - overlays.drawTop(); + overlays.drawBottom(); + drawAndInterpolate(playerGroup, p -> true, Player::drawBuildRequests); + overlays.drawTop(); - Graphics.flushSurface(); + Graphics.flushSurface(); - if(showPaths && debug) drawDebug(); + if(showPaths && debug) drawDebug(); - batch.end(); + batch.end(); - if(showFog){ - fog.draw(); - } + if(showFog){ + fog.draw(); + } - Graphics.beginCam(); - EntityDraw.setClip(false); - drawAndInterpolate(playerGroup, p -> !p.isDead() && !p.isLocal, Player::drawName); - EntityDraw.setClip(true); - Graphics.end(); - } + Graphics.beginCam(); + EntityDraw.setClip(false); + drawAndInterpolate(playerGroup, p -> !p.isDead() && !p.isLocal, Player::drawName); + EntityDraw.setClip(true); + Graphics.end(); + } - private void drawFlyerShadows(){ - Graphics.surface(effectSurface, true, false); + private void drawFlyerShadows(){ + Graphics.surface(effectSurface, true, false); - float trnsX = 12, trnsY = -13; + float trnsX = 12, trnsY = -13; - Graphics.end(); - Core.batch.getTransformMatrix().translate(trnsX, trnsY, 0); - Graphics.begin(); + Graphics.end(); + Core.batch.getTransformMatrix().translate(trnsX, trnsY, 0); + Graphics.begin(); - for(EntityGroup group : unitGroups){ - if(!group.isEmpty()){ - drawAndInterpolate(group, unit -> unit.isFlying() && !unit.isDead(), Unit::drawShadow); - } - } + for(EntityGroup group : unitGroups){ + if(!group.isEmpty()){ + drawAndInterpolate(group, unit -> unit.isFlying() && !unit.isDead(), Unit::drawShadow); + } + } - if(!playerGroup.isEmpty()){ - drawAndInterpolate(playerGroup, unit -> unit.isFlying() && !unit.isDead(), Unit::drawShadow); - } + if(!playerGroup.isEmpty()){ + drawAndInterpolate(playerGroup, unit -> unit.isFlying() && !unit.isDead(), Unit::drawShadow); + } - Graphics.end(); - Core.batch.getTransformMatrix().translate(-trnsX, -trnsY, 0); - Graphics.begin(); + Graphics.end(); + Core.batch.getTransformMatrix().translate(-trnsX, -trnsY, 0); + Graphics.begin(); - //TODO this actually isn't necessary - Draw.color(0, 0, 0, 0.15f); - Graphics.flushSurface(); - Draw.color(); - } + //TODO this actually isn't necessary + Draw.color(0, 0, 0, 0.15f); + Graphics.flushSurface(); + Draw.color(); + } - private void drawAllTeams(boolean flying){ - for(Team team : Team.all){ - EntityGroup group = unitGroups[team.ordinal()]; + private void drawAllTeams(boolean flying){ + for(Team team : Team.all){ + EntityGroup group = unitGroups[team.ordinal()]; - if(group.count(p -> p.isFlying() == flying) + - playerGroup.count(p -> p.isFlying() == flying && p.getTeam() == team) == 0 && flying) continue; + if(group.count(p -> p.isFlying() == flying) + + playerGroup.count(p -> p.isFlying() == flying && p.getTeam() == team) == 0 && flying) continue; - drawAndInterpolate(unitGroups[team.ordinal()], u -> u.isFlying() == flying && !u.isDead(), Unit::drawUnder); - drawAndInterpolate(playerGroup, p -> p.isFlying() == flying && p.getTeam() == team, Unit::drawUnder); + drawAndInterpolate(unitGroups[team.ordinal()], u -> u.isFlying() == flying && !u.isDead(), Unit::drawUnder); + drawAndInterpolate(playerGroup, p -> p.isFlying() == flying && p.getTeam() == team, Unit::drawUnder); - Shaders.outline.color.set(team.color); - Shaders.mix.color.set(Color.WHITE); + Shaders.outline.color.set(team.color); + Shaders.mix.color.set(Color.WHITE); - Graphics.beginShaders(Shaders.outline); - Graphics.shader(Shaders.mix, true); - drawAndInterpolate(unitGroups[team.ordinal()], u -> u.isFlying() == flying && !u.isDead()); - drawAndInterpolate(playerGroup, p -> p.isFlying() == flying && p.getTeam() == team); - Graphics.shader(); - blocks.drawTeamBlocks(Layer.turret, team); - Graphics.endShaders(); + Graphics.beginShaders(Shaders.outline); + Graphics.shader(Shaders.mix, true); + drawAndInterpolate(unitGroups[team.ordinal()], u -> u.isFlying() == flying && !u.isDead()); + drawAndInterpolate(playerGroup, p -> p.isFlying() == flying && p.getTeam() == team); + Graphics.shader(); + blocks.drawTeamBlocks(Layer.turret, team); + Graphics.endShaders(); - drawAndInterpolate(unitGroups[team.ordinal()], u -> u.isFlying() == flying && !u.isDead(), Unit::drawOver); - drawAndInterpolate(playerGroup, p -> p.isFlying() == flying && p.getTeam() == team, Unit::drawOver); - } - } + drawAndInterpolate(unitGroups[team.ordinal()], u -> u.isFlying() == flying && !u.isDead(), Unit::drawOver); + drawAndInterpolate(playerGroup, p -> p.isFlying() == flying && p.getTeam() == team, Unit::drawOver); + } + } - public void drawAndInterpolate(EntityGroup group){ - drawAndInterpolate(group, t -> true, DrawTrait::draw); - } + public void drawAndInterpolate(EntityGroup group){ + drawAndInterpolate(group, t -> true, DrawTrait::draw); + } - public void drawAndInterpolate(EntityGroup group, Predicate toDraw){ - drawAndInterpolate(group, toDraw, DrawTrait::draw); - } + public void drawAndInterpolate(EntityGroup group, Predicate toDraw){ + drawAndInterpolate(group, toDraw, DrawTrait::draw); + } - public void drawAndInterpolate(EntityGroup group, Predicate toDraw, Consumer drawer){ - EntityDraw.drawWith(group, toDraw, t -> { - float lastx = t.getX(), lasty = t.getY(), lastrot = 0f; + public void drawAndInterpolate(EntityGroup group, Predicate toDraw, Consumer drawer){ + EntityDraw.drawWith(group, toDraw, t -> { + float lastx = t.getX(), lasty = t.getY(), lastrot = 0f; - if(threads.doInterpolate() && threads.isEnabled() && t instanceof SolidTrait){ - SolidTrait s = (SolidTrait)t; + if(threads.doInterpolate() && threads.isEnabled() && t instanceof SolidTrait){ + SolidTrait s = (SolidTrait) t; - lastrot = s.getRotation(); + lastrot = s.getRotation(); - if(s.lastUpdated() != 0){ - float timeSinceUpdate = TimeUtils.timeSinceMillis(s.lastUpdated()); - float alpha = Math.min(timeSinceUpdate / s.updateSpacing(), 1f); + if(s.lastUpdated() != 0){ + float timeSinceUpdate = TimeUtils.timeSinceMillis(s.lastUpdated()); + float alpha = Math.min(timeSinceUpdate / s.updateSpacing(), 1f); - tmpVector1.set(s.lastPosition().x, s.lastPosition().y) - .lerp(tmpVector2.set(lastx, lasty), alpha); - s.setRotation(Mathf.slerp(s.lastPosition().z, lastrot, alpha)); + tmpVector1.set(s.lastPosition().x, s.lastPosition().y) + .lerp(tmpVector2.set(lastx, lasty), alpha); + s.setRotation(Mathf.slerp(s.lastPosition().z, lastrot, alpha)); - s.setX(tmpVector1.x); - s.setY(tmpVector1.y); - } - } + s.setX(tmpVector1.x); + s.setY(tmpVector1.y); + } + } - //TODO extremely hacky - if(t instanceof Player && ((Player) t).getCarry() != null && ((Player) t).getCarry() instanceof Player && ((Player) ((Player) t).getCarry()).isLocal){ - ((Player) t).x = ((Player) t).getCarry().getX(); - ((Player) t).y = ((Player) t).getCarry().getY(); - } + //TODO extremely hacky + if(t instanceof Player && ((Player) t).getCarry() != null && ((Player) t).getCarry() instanceof Player && ((Player) ((Player) t).getCarry()).isLocal){ + ((Player) t).x = ((Player) t).getCarry().getX(); + ((Player) t).y = ((Player) t).getCarry().getY(); + } - drawer.accept(t); + drawer.accept(t); - t.setX(lastx); - t.setY(lasty); + t.setX(lastx); + t.setY(lasty); - if(threads.doInterpolate() && threads.isEnabled()) { + if(threads.doInterpolate() && threads.isEnabled()){ - if (t instanceof SolidTrait) { - ((SolidTrait) t).setRotation(lastrot); - } - } - }); - } + if(t instanceof SolidTrait){ + ((SolidTrait) t).setRotation(lastrot); + } + } + }); + } - @Override - public void resize(int width, int height){ - super.resize(width, height); - for(Player player : players) { + @Override + public void resize(int width, int height){ + super.resize(width, height); + for(Player player : players){ control.input(player.playerIndex).resetCursor(); } - camera.position.set(players[0].x, players[0].y, 0); - } + camera.position.set(players[0].x, players[0].y, 0); + } - @Override - public void dispose() { - background.dispose(); - fog.dispose(); - } + @Override + public void dispose(){ + background.dispose(); + fog.dispose(); + } - public Vector2 averagePosition(){ - avgPosition.setZero(); + public Vector2 averagePosition(){ + avgPosition.setZero(); - drawAndInterpolate(playerGroup, p -> p.isLocal, p -> { - avgPosition.add(p.x, p.y); - }); + drawAndInterpolate(playerGroup, p -> p.isLocal, p -> { + avgPosition.add(p.x, p.y); + }); avgPosition.scl(1f / players.length); return avgPosition; } - public FogRenderer fog() { - return fog; - } + public FogRenderer fog(){ + return fog; + } - public MinimapRenderer minimap() { - return minimap; - } + public MinimapRenderer minimap(){ + return minimap; + } - void drawPadding(){ - float vw = world.width() * tilesize; - float cw = camera.viewportWidth * camera.zoom; - float ch = camera.viewportHeight * camera.zoom; - if(vw < cw){ - batch.draw(background, - camera.position.x + vw/2, - Mathf.round(camera.position.y - ch/2, tilesize), - (cw - vw) /2, - ch + tilesize, - 0, 0, - ((cw - vw) / 2 / tilesize), -ch / tilesize + 1); + void drawPadding(){ + float vw = world.width() * tilesize; + float cw = camera.viewportWidth * camera.zoom; + float ch = camera.viewportHeight * camera.zoom; + if(vw < cw){ + batch.draw(background, + camera.position.x + vw / 2, + Mathf.round(camera.position.y - ch / 2, tilesize), + (cw - vw) / 2, + ch + tilesize, + 0, 0, + ((cw - vw) / 2 / tilesize), -ch / tilesize + 1); - batch.draw(background, - camera.position.x - vw/2, - Mathf.round(camera.position.y - ch/2, tilesize), - -(cw - vw) /2, - ch + tilesize, - 0, 0, - -((cw - vw) / 2 / tilesize), -ch / tilesize + 1); - } - } + batch.draw(background, + camera.position.x - vw / 2, + Mathf.round(camera.position.y - ch / 2, tilesize), + -(cw - vw) / 2, + ch + tilesize, + 0, 0, + -((cw - vw) / 2 / tilesize), -ch / tilesize + 1); + } + } - void drawDebug(){ - int rangex = (int)(Core.camera.viewportWidth/tilesize/2), rangey = (int)(Core.camera.viewportHeight/tilesize/2); + void drawDebug(){ + int rangex = (int) (Core.camera.viewportWidth / tilesize / 2), rangey = (int) (Core.camera.viewportHeight / tilesize / 2); - for(int x = -rangex; x <= rangex; x++) { - for (int y = -rangey; y <= rangey; y++) { - int worldx = Mathf.scl(camera.position.x, tilesize) + x; - int worldy = Mathf.scl(camera.position.y, tilesize) + y; + for(int x = -rangex; x <= rangex; x++){ + for(int y = -rangey; y <= rangey; y++){ + int worldx = Mathf.scl(camera.position.x, tilesize) + x; + int worldy = Mathf.scl(camera.position.y, tilesize) + y; - if(world.tile(worldx, worldy) == null) continue; + if(world.tile(worldx, worldy) == null) continue; - float value = world.pathfinder().getDebugValue(worldx, worldy); - Draw.color(Color.PURPLE); - Draw.alpha((value % 10f) / 10f); - Lines.square(worldx * tilesize, worldy*tilesize, 4f); - } - } + float value = world.pathfinder().getDebugValue(worldx, worldy); + Draw.color(Color.PURPLE); + Draw.alpha((value % 10f) / 10f); + Lines.square(worldx * tilesize, worldy * tilesize, 4f); + } + } - Draw.color(Color.ORANGE); - Draw.tcolor(Color.ORANGE); + Draw.color(Color.ORANGE); + Draw.tcolor(Color.ORANGE); - ObjectIntMap seen = new ObjectIntMap<>(); + ObjectIntMap seen = new ObjectIntMap<>(); - for(BlockFlag flag : BlockFlag.values()){ - for(Tile tile : world.indexer().getEnemy(Team.blue, flag)){ - int index = seen.getAndIncrement(tile, 0, 1); - Draw.tscl(0.125f); - Draw.text(flag.name(), tile.drawx(), tile.drawy() + tile.block().size * tilesize/2f + 4 + index * 3); - Lines.square(tile.drawx(), tile.drawy(), tile.block().size * tilesize/2f); - } - } - Draw.tscl(fontScale); - Draw.tcolor(); + for(BlockFlag flag : BlockFlag.values()){ + for(Tile tile : world.indexer().getEnemy(Team.blue, flag)){ + int index = seen.getAndIncrement(tile, 0, 1); + Draw.tscl(0.125f); + Draw.text(flag.name(), tile.drawx(), tile.drawy() + tile.block().size * tilesize / 2f + 4 + index * 3); + Lines.square(tile.drawx(), tile.drawy(), tile.block().size * tilesize / 2f); + } + } + Draw.tscl(fontScale); + Draw.tcolor(); - Draw.color(); - } + Draw.color(); + } - public BlockRenderer getBlocks() { - return blocks; - } + public BlockRenderer getBlocks(){ + return blocks; + } - public void setCameraScale(int amount){ - targetscale = amount; - clampScale(); - //scale up all surfaces in preparation for the zoom - for(Surface surface : Graphics.getSurfaces()){ - surface.setScale(targetscale); - } - } + public void setCameraScale(int amount){ + targetscale = amount; + clampScale(); + //scale up all surfaces in preparation for the zoom + for(Surface surface : Graphics.getSurfaces()){ + surface.setScale(targetscale); + } + } - public void scaleCamera(int amount){ - setCameraScale(targetscale + amount); - } + public void scaleCamera(int amount){ + setCameraScale(targetscale + amount); + } - public void clampScale(){ - float s = io.anuke.ucore.scene.ui.layout.Unit.dp.scl(1f); - targetscale = Mathf.clamp(targetscale, Math.round(s*2), Math.round(s*5)); - } + public void clampScale(){ + float s = io.anuke.ucore.scene.ui.layout.Unit.dp.scl(1f); + targetscale = Mathf.clamp(targetscale, Math.round(s * 2), Math.round(s * 5)); + } } diff --git a/core/src/io/anuke/mindustry/core/ThreadHandler.java b/core/src/io/anuke/mindustry/core/ThreadHandler.java index c90a32131c..22bc5ff2fe 100644 --- a/core/src/io/anuke/mindustry/core/ThreadHandler.java +++ b/core/src/io/anuke/mindustry/core/ThreadHandler.java @@ -9,30 +9,29 @@ import io.anuke.ucore.util.Log; import static io.anuke.mindustry.Vars.control; import static io.anuke.mindustry.Vars.logic; -public class ThreadHandler { +public class ThreadHandler{ private final Queue toRun = new Queue<>(); private final ThreadProvider impl; + private final Object updateLock = new Object(); private float delta = 1f; private float smoothDelta = 1f; private long frame = 0, lastDeltaUpdate; private float framesSinceUpdate; private boolean enabled; - - private final Object updateLock = new Object(); private boolean rendered = true; public ThreadHandler(ThreadProvider impl){ this.impl = impl; Timers.setDeltaProvider(() -> { - float result = impl.isOnThread() ? delta : Gdx.graphics.getDeltaTime()*60f; + float result = impl.isOnThread() ? delta : Gdx.graphics.getDeltaTime() * 60f; return Math.min(Float.isNaN(result) ? 1f : result, 15f); }); } public void run(Runnable r){ - if(enabled) { - synchronized (toRun) { + if(enabled){ + synchronized(toRun){ toRun.addLast(r); } }else{ @@ -41,7 +40,7 @@ public class ThreadHandler { } public void runGraphics(Runnable r){ - if(enabled) { + if(enabled){ Gdx.app.postRunnable(r); }else{ r.run(); @@ -49,8 +48,8 @@ public class ThreadHandler { } public void runDelay(Runnable r){ - if(enabled) { - synchronized (toRun) { + if(enabled){ + synchronized(toRun){ toRun.addLast(r); } }else{ @@ -59,7 +58,7 @@ public class ThreadHandler { } public int getTPS(){ - return (int)(60/smoothDelta); + return (int) (60 / smoothDelta); } public long getFrameID(){ @@ -76,12 +75,16 @@ public class ThreadHandler { framesSinceUpdate += Timers.delta(); - synchronized (updateLock) { + synchronized(updateLock){ rendered = true; impl.notify(updateLock); } } + public boolean isEnabled(){ + return enabled; + } + public void setEnabled(boolean enabled){ if(enabled){ logic.doUpdate = false; @@ -98,10 +101,6 @@ public class ThreadHandler { } } - public boolean isEnabled(){ - return enabled; - } - public boolean doInterpolate(){ return enabled && Gdx.graphics.getFramesPerSecond() - getTPS() > 20 && getTPS() < 30; } @@ -111,13 +110,13 @@ public class ThreadHandler { } private void runLogic(){ - try { - while (true) { + try{ + while(true){ long time = TimeUtils.nanoTime(); while(true){ Runnable r; - synchronized (toRun){ + synchronized(toRun){ if(toRun.size > 0){ r = toRun.removeFirst(); }else{ @@ -135,12 +134,12 @@ public class ThreadHandler { long elapsed = TimeUtils.nanosToMillis(TimeUtils.timeSinceNanos(time)); long target = (long) ((1000) / 60f); - if (elapsed < target) { + if(elapsed < target){ impl.sleep(target - elapsed); } - synchronized(updateLock) { - while(!rendered) { + synchronized(updateLock){ + while(!rendered){ impl.wait(updateLock); } rendered = false; @@ -154,22 +153,27 @@ public class ThreadHandler { smoothDelta = delta; } - frame ++; + frame++; framesSinceUpdate = 0; } - } catch (InterruptedException ex) { + }catch(InterruptedException ex){ Log.info("Stopping logic thread."); - } catch (Throwable ex) { + }catch(Throwable ex){ control.setError(ex); } } - public interface ThreadProvider { + public interface ThreadProvider{ boolean isOnThread(); + void sleep(long ms) throws InterruptedException; + void start(Runnable run); + void stop(); + void wait(Object object) throws InterruptedException; + void notify(Object object); } } diff --git a/core/src/io/anuke/mindustry/core/UI.java b/core/src/io/anuke/mindustry/core/UI.java index 821ea28cff..059d58f5c3 100644 --- a/core/src/io/anuke/mindustry/core/UI.java +++ b/core/src/io/anuke/mindustry/core/UI.java @@ -37,29 +37,7 @@ import static io.anuke.mindustry.Vars.players; import static io.anuke.ucore.scene.actions.Actions.*; public class UI extends SceneModule{ - public AboutDialog about; - public RestartDialog restart; - public LevelDialog levels; - public MapsDialog maps; - public LoadDialog load; - public DiscordDialog discord; - public JoinDialog join; - public HostDialog host; - public PausedDialog paused; - public SettingsMenuDialog settings; - public ControlsDialog controls; - public MapEditorDialog editor; - public LanguageDialog language; - public BansDialog bans; - public AdminsDialog admins; - public TraceDialog traces; - public RollbackDialog rollback; - public ChangelogDialog changelog; - public LocalPlayerDialog localplayers; - public UnlocksDialog unlocks; - public ContentInfoDialog content; - - public final MenuFragment menufrag = new MenuFragment(); + public final MenuFragment menufrag = new MenuFragment(); public final HudFragment hudfrag = new HudFragment(); public final ChatFragment chatfrag = new ChatFragment(); public final PlayerListFragment listfrag = new PlayerListFragment(); @@ -67,239 +45,246 @@ public class UI extends SceneModule{ public final LoadingFragment loadfrag = new LoadingFragment(); public final DebugFragment debugfrag = new DebugFragment(); + public AboutDialog about; + public RestartDialog restart; + public LevelDialog levels; + public MapsDialog maps; + public LoadDialog load; + public DiscordDialog discord; + public JoinDialog join; + public HostDialog host; + public PausedDialog paused; + public SettingsMenuDialog settings; + public ControlsDialog controls; + public MapEditorDialog editor; + public LanguageDialog language; + public BansDialog bans; + public AdminsDialog admins; + public TraceDialog traces; + public RollbackDialog rollback; + public ChangelogDialog changelog; + public LocalPlayerDialog localplayers; + public UnlocksDialog unlocks; + public ContentInfoDialog content; + private Locale lastLocale; - - public UI() { - Dialog.setShowAction(()-> sequence( - alpha(0f), - originCenter(), - moveToAligned(Gdx.graphics.getWidth()/2, Gdx.graphics.getHeight()/2, Align.center), - scaleTo(0.0f, 1f), - parallel( - scaleTo(1f, 1f, 0.1f, Interpolation.fade), - fadeIn(0.1f, Interpolation.fade) - ) - )); - - Dialog.setHideAction(()-> sequence( - parallel( - scaleTo(0.01f, 0.01f, 0.1f, Interpolation.fade), - fadeOut(0.1f, Interpolation.fade) - ) - )); - - TooltipManager.getInstance().animations = false; - - Settings.setErrorHandler(()-> Timers.run(1f, ()-> showError("[crimson]Failed to access local storage.\nSettings will not be saved."))); - - Dialog.closePadR = -1; - Dialog.closePadT = 5; - - Colors.put("description", Palette.description); - Colors.put("turretinfo", Palette.turretinfo); - Colors.put("iteminfo", Palette.iteminfo); - Colors.put("powerinfo", Palette.powerinfo); - Colors.put("liquidinfo", Palette.liquidinfo); - Colors.put("craftinfo", Palette.craftinfo); - Colors.put("missingitems", Palette.missingitems); - Colors.put("health", Palette.health); - Colors.put("healthstats", Palette.healthstats); - Colors.put("interact", Palette.interact); - Colors.put("accent", Palette.accent); - Colors.put("place", Palette.place); - Colors.put("remove", Palette.remove); - Colors.put("placeRotate", Palette.placeRotate); - Colors.put("range", Palette.range); - Colors.put("power", Palette.power); - } - @Override - protected void loadSkin(){ - skin = new Skin(Gdx.files.internal("ui/uiskin.json"), Core.atlas); - Mathf.each(font -> { - font.setUseIntegerPositions(false); - font.getData().setScale(Vars.fontScale); - font.getData().down += Unit.dp.scl(4f); - font.getData().lineHeight -= Unit.dp.scl(2f); - }, skin.font(), skin.getFont("default-font-chat"), skin.getFont("korean")); - } + public UI(){ + Dialog.setShowAction(() -> sequence( + alpha(0f), + originCenter(), + moveToAligned(Gdx.graphics.getWidth() / 2, Gdx.graphics.getHeight() / 2, Align.center), + scaleTo(0.0f, 1f), + parallel( + scaleTo(1f, 1f, 0.1f, Interpolation.fade), + fadeIn(0.1f, Interpolation.fade) + ) + )); - @Override - public synchronized void update(){ - if(Vars.debug && !Vars.showUI) return; + Dialog.setHideAction(() -> sequence( + parallel( + scaleTo(0.01f, 0.01f, 0.1f, Interpolation.fade), + fadeOut(0.1f, Interpolation.fade) + ) + )); - if(Graphics.drawing()) Graphics.end(); - - act(); + TooltipManager.getInstance().animations = false; - Graphics.begin(); + Settings.setErrorHandler(() -> Timers.run(1f, () -> showError("[crimson]Failed to access local storage.\nSettings will not be saved."))); - for(int i = 0; i < players.length; i ++){ - InputHandler input = control.input(i); + Dialog.closePadR = -1; + Dialog.closePadT = 5; - if(input.isCursorVisible()) { + Colors.put("description", Palette.description); + Colors.put("turretinfo", Palette.turretinfo); + Colors.put("iteminfo", Palette.iteminfo); + Colors.put("powerinfo", Palette.powerinfo); + Colors.put("liquidinfo", Palette.liquidinfo); + Colors.put("craftinfo", Palette.craftinfo); + Colors.put("missingitems", Palette.missingitems); + Colors.put("health", Palette.health); + Colors.put("healthstats", Palette.healthstats); + Colors.put("interact", Palette.interact); + Colors.put("accent", Palette.accent); + Colors.put("place", Palette.place); + Colors.put("remove", Palette.remove); + Colors.put("placeRotate", Palette.placeRotate); + Colors.put("range", Palette.range); + Colors.put("power", Palette.power); + } + + @Override + protected void loadSkin(){ + skin = new Skin(Gdx.files.internal("ui/uiskin.json"), Core.atlas); + Mathf.each(font -> { + font.setUseIntegerPositions(false); + font.getData().setScale(Vars.fontScale); + font.getData().down += Unit.dp.scl(4f); + font.getData().lineHeight -= Unit.dp.scl(2f); + }, skin.font(), skin.getFont("default-font-chat"), skin.getFont("korean")); + } + + @Override + public synchronized void update(){ + if(Vars.debug && !Vars.showUI) return; + + if(Graphics.drawing()) Graphics.end(); + + act(); + + Graphics.begin(); + + for(int i = 0; i < players.length; i++){ + InputHandler input = control.input(i); + + if(input.isCursorVisible()){ Draw.color(); float scl = Unit.dp.scl(3f); - Draw.rect("controller-cursor", input.getMouseX(), Gdx.graphics.getHeight() - input.getMouseY(), 16*scl, 16*scl); + Draw.rect("controller-cursor", input.getMouseX(), Gdx.graphics.getHeight() - input.getMouseY(), 16 * scl, 16 * scl); } } - Graphics.end(); + Graphics.end(); Draw.color(); - } + } - @Override - public void init(){ - editor = new MapEditorDialog(); - controls = new ControlsDialog(); - restart = new RestartDialog(); - join = new JoinDialog(); - discord = new DiscordDialog(); - load = new LoadDialog(); - levels = new LevelDialog(); - language = new LanguageDialog(); - settings = new SettingsMenuDialog(); - paused = new PausedDialog(); - changelog = new ChangelogDialog(); - about = new AboutDialog(); - host = new HostDialog(); - bans = new BansDialog(); - admins = new AdminsDialog(); - traces = new TraceDialog(); - rollback = new RollbackDialog(); - maps = new MapsDialog(); - localplayers = new LocalPlayerDialog(); - unlocks = new UnlocksDialog(); - content = new ContentInfoDialog(); + @Override + public void init(){ + editor = new MapEditorDialog(); + controls = new ControlsDialog(); + restart = new RestartDialog(); + join = new JoinDialog(); + discord = new DiscordDialog(); + load = new LoadDialog(); + levels = new LevelDialog(); + language = new LanguageDialog(); + settings = new SettingsMenuDialog(); + paused = new PausedDialog(); + changelog = new ChangelogDialog(); + about = new AboutDialog(); + host = new HostDialog(); + bans = new BansDialog(); + admins = new AdminsDialog(); + traces = new TraceDialog(); + rollback = new RollbackDialog(); + maps = new MapsDialog(); + localplayers = new LocalPlayerDialog(); + unlocks = new UnlocksDialog(); + content = new ContentInfoDialog(); - build.begin(scene); + build.begin(scene); - Group group = Core.scene.getRoot(); + Group group = Core.scene.getRoot(); - backfrag.build(group); - hudfrag.build(group); - menufrag.build(group); - chatfrag.container().build(group); - listfrag.build(group); - debugfrag.build(group); - loadfrag.build(group); + backfrag.build(group); + hudfrag.build(group); + menufrag.build(group); + chatfrag.container().build(group); + listfrag.build(group); + debugfrag.build(group); + loadfrag.build(group); - build.end(); - } + build.end(); + } - @Override - public boolean hasMouse() { - return super.hasMouse(); - } + @Override + public boolean hasMouse(){ + return super.hasMouse(); + } - @Override - public void resize(int width, int height) { - super.resize(width, height); + @Override + public void resize(int width, int height){ + super.resize(width, height); - Events.fire(ResizeEvent.class); - } + Events.fire(ResizeEvent.class); + } - public Locale getLocale(){ - String loc = Settings.getString("locale"); - if(loc.equals("default")){ - return Locale.getDefault(); - }else{ - if(lastLocale == null || !lastLocale.toString().equals(loc)){ - if(loc.contains("_")){ - String[] split = loc.split("_"); - lastLocale = new Locale(split[0], split[1]); - }else{ - lastLocale = new Locale(loc); - } - } + public Locale getLocale(){ + String loc = Settings.getString("locale"); + if(loc.equals("default")){ + return Locale.getDefault(); + }else{ + if(lastLocale == null || !lastLocale.toString().equals(loc)){ + if(loc.contains("_")){ + String[] split = loc.split("_"); + lastLocale = new Locale(split[0], split[1]); + }else{ + lastLocale = new Locale(loc); + } + } - return lastLocale; - } - } + return lastLocale; + } + } - public void loadAnd(Callable call){ - loadAnd("$text.loading", call); - } + public void loadAnd(Callable call){ + loadAnd("$text.loading", call); + } - public void loadAnd(String text, Callable call){ - loadfrag.show(text); - Timers.runTask(7f, () -> { - call.run(); - loadfrag.hide(); - }); - } + public void loadAnd(String text, Callable call){ + loadfrag.show(text); + Timers.runTask(7f, () -> { + call.run(); + loadfrag.hide(); + }); + } - public void showTextInput(String title, String text, String def, TextFieldFilter filter, Consumer confirmed){ - new Dialog(title, "dialog"){{ - content().margin(30).add(text).padRight(6f); - TextField field = content().addField(def, t->{}).size(170f, 50f).get(); - field.setTextFieldFilter((f, c) -> field.getText().length() < 12 && filter.acceptChar(f, c)); - Platform.instance.addDialog(field); - buttons().defaults().size(120, 54).pad(4); - buttons().addButton("$text.ok", () -> { - confirmed.accept(field.getText()); - hide(); - }).disabled(b -> field.getText().isEmpty()); - buttons().addButton("$text.cancel", this::hide); - }}.show(); - } + public void showTextInput(String title, String text, String def, TextFieldFilter filter, Consumer confirmed){ + new Dialog(title, "dialog"){{ + content().margin(30).add(text).padRight(6f); + TextField field = content().addField(def, t -> { + }).size(170f, 50f).get(); + field.setTextFieldFilter((f, c) -> field.getText().length() < 12 && filter.acceptChar(f, c)); + Platform.instance.addDialog(field); + buttons().defaults().size(120, 54).pad(4); + buttons().addButton("$text.ok", () -> { + confirmed.accept(field.getText()); + hide(); + }).disabled(b -> field.getText().isEmpty()); + buttons().addButton("$text.cancel", this::hide); + }}.show(); + } - public void showTextInput(String title, String text, String def, Consumer confirmed){ - showTextInput(title, text, def, (field, c) -> true, confirmed); - } + public void showTextInput(String title, String text, String def, Consumer confirmed){ + showTextInput(title, text, def, (field, c) -> true, confirmed); + } - public void showInfoFade(String info){ - Table table = new Table(); - table.setFillParent(true); - table.actions(Actions.fadeOut(7f, Interpolation.fade), Actions.removeActor()); - table.top().add(info).padTop(8); - Core.scene.add(table); - } + public void showInfoFade(String info){ + Table table = new Table(); + table.setFillParent(true); + table.actions(Actions.fadeOut(7f, Interpolation.fade), Actions.removeActor()); + table.top().add(info).padTop(8); + Core.scene.add(table); + } - public void showInfo(String info){ - new Dialog("$text.info.title", "dialog"){{ - getCell(content()).growX(); - content().margin(15).add(info).width(400f).wrap().get().setAlignment(Align.center, Align.center); - buttons().addButton("$text.ok", this::hide).size(90, 50).pad(4); - }}.show(); - } + public void showInfo(String info){ + new Dialog("$text.info.title", "dialog"){{ + getCell(content()).growX(); + content().margin(15).add(info).width(400f).wrap().get().setAlignment(Align.center, Align.center); + buttons().addButton("$text.ok", this::hide).size(90, 50).pad(4); + }}.show(); + } - public void showError(String text){ - new Dialog("$text.error.title", "dialog"){{ - content().margin(15).add(text).width(400f).wrap().get().setAlignment(Align.center, Align.center); - buttons().addButton("$text.ok", this::hide).size(90, 50).pad(4); - }}.show(); - } + public void showError(String text){ + new Dialog("$text.error.title", "dialog"){{ + content().margin(15).add(text).width(400f).wrap().get().setAlignment(Align.center, Align.center); + buttons().addButton("$text.ok", this::hide).size(90, 50).pad(4); + }}.show(); + } - public void showConfirm(String title, String text, Listenable confirmed){ - FloatingDialog dialog = new FloatingDialog(title); - dialog.content().add(text).width(400f).wrap().pad(4f).get().setAlignment(Align.center, Align.center); - dialog.buttons().defaults().size(200f, 54f).pad(2f); - dialog.buttons().addButton("$text.cancel", dialog::hide); - dialog.buttons().addButton("$text.ok", () -> { - dialog.hide(); - confirmed.listen(); - }); - dialog.keyDown(Keys.ESCAPE, dialog::hide); - dialog.keyDown(Keys.BACK, dialog::hide); - dialog.show(); - } - - public void showConfirmListen(String title, String text, Consumer listener){ - FloatingDialog dialog = new FloatingDialog(title); - dialog.content().add(text).pad(4f); - dialog.buttons().defaults().size(200f, 54f).pad(2f); - dialog.buttons().addButton("$text.cancel", () -> { - dialog.hide(); - listener.accept(true); - }); - dialog.buttons().addButton("$text.ok", () -> { - dialog.hide(); - listener.accept(true); - }); - dialog.show(); - } - + public void showConfirm(String title, String text, Listenable confirmed){ + FloatingDialog dialog = new FloatingDialog(title); + dialog.content().add(text).width(400f).wrap().pad(4f).get().setAlignment(Align.center, Align.center); + dialog.buttons().defaults().size(200f, 54f).pad(2f); + dialog.buttons().addButton("$text.cancel", dialog::hide); + dialog.buttons().addButton("$text.ok", () -> { + dialog.hide(); + confirmed.listen(); + }); + dialog.keyDown(Keys.ESCAPE, dialog::hide); + dialog.keyDown(Keys.BACK, dialog::hide); + dialog.show(); + } } diff --git a/core/src/io/anuke/mindustry/core/World.java b/core/src/io/anuke/mindustry/core/World.java index 73a03e013b..0a6a0300de 100644 --- a/core/src/io/anuke/mindustry/core/World.java +++ b/core/src/io/anuke/mindustry/core/World.java @@ -11,7 +11,10 @@ import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.game.EventType.TileChangeEvent; import io.anuke.mindustry.game.EventType.WorldLoadEvent; import io.anuke.mindustry.game.Team; -import io.anuke.mindustry.io.*; +import io.anuke.mindustry.io.Map; +import io.anuke.mindustry.io.MapIO; +import io.anuke.mindustry.io.MapMeta; +import io.anuke.mindustry.io.Maps; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.mapgen.WorldGenerator; @@ -27,124 +30,130 @@ import io.anuke.ucore.util.Tmp; import static io.anuke.mindustry.Vars.*; public class World extends Module{ - private int seed; - - private Map currentMap; - private Tile[][] tiles; - private Pathfinder pathfinder = new Pathfinder(); - private BlockIndexer indexer = new BlockIndexer(); - private Maps maps = new Maps(); + private int seed; - private Array tempTiles = new ThreadArray<>(); - private boolean generating, invalidMap; - - public World(){ - maps.load(); - } - - @Override - public void dispose(){ - maps.dispose(); - } - - public Maps maps(){ - return maps; - } + private Map currentMap; + private Tile[][] tiles; + private Pathfinder pathfinder = new Pathfinder(); + private BlockIndexer indexer = new BlockIndexer(); + private Maps maps = new Maps(); - public BlockIndexer indexer() { - return indexer; - } + private Array tempTiles = new ThreadArray<>(); + private boolean generating, invalidMap; - public Pathfinder pathfinder(){ - return pathfinder; - } + public World(){ + maps.load(); + } - public boolean isInvalidMap() { - return invalidMap; - } + @Override + public void dispose(){ + maps.dispose(); + } - public boolean solid(int x, int y){ - Tile tile = tile(x, y); - - return tile == null || tile.solid(); - } - - public boolean passable(int x, int y){ - Tile tile = tile(x, y); - - return tile != null && tile.passable(); - } - - public boolean wallSolid(int x, int y){ - Tile tile = tile(x, y); - return tile == null || tile.block().solid; - } - - public boolean isAccessible(int x, int y){ - return !wallSolid(x, y-1) || !wallSolid(x, y+1) || !wallSolid(x-1, y) ||!wallSolid(x+1, y); - } - - public boolean floorBlends(int x, int y, Block block){ - Tile tile = tile(x, y); - return tile == null || tile.floor().id <= block.id; - } - - public Map getMap(){ - return currentMap; - } - - public int width(){ - return tiles == null ? 0 : tiles.length; - } - - public int height(){ - return tiles == null ? 0 : tiles[0].length; - } + public Maps maps(){ + return maps; + } - public int toPacked(int x, int y){ - return x + y *width(); - } + public BlockIndexer indexer(){ + return indexer; + } - public Tile tile(int packed){ - return tiles == null ? null : tile(packed % width(), packed / width()); - } - - public Tile tile(int x, int y){ - if(tiles == null){ - return null; - } - if(!Mathf.inBounds(x, y, tiles)) return null; - return tiles[x][y]; - } + public Pathfinder pathfinder(){ + return pathfinder; + } - public Tile rawTile(int x, int y){ - return tiles[x][y]; - } - - public Tile tileWorld(float x, float y){ - return tile(Mathf.scl2(x, tilesize), Mathf.scl2(y, tilesize)); - } + public boolean isInvalidMap(){ + return invalidMap; + } - public int toTile(float coord){ - return Mathf.scl2(coord, tilesize); - } - - public Tile[][] getTiles(){ - return tiles; - } - - private void clearTileEntities(){ - for(int x = 0; x < tiles.length; x ++){ - for(int y = 0; y < tiles[0].length; y ++){ - if(tiles[x][y] != null && tiles[x][y].entity != null){ - tiles[x][y].entity.remove(); - } - } - } - } + public boolean solid(int x, int y){ + Tile tile = tile(x, y); - /**Resizes the tile array to the specified size and returns the resulting tile array. - * Only use for loading saves!*/ + return tile == null || tile.solid(); + } + + public boolean passable(int x, int y){ + Tile tile = tile(x, y); + + return tile != null && tile.passable(); + } + + public boolean wallSolid(int x, int y){ + Tile tile = tile(x, y); + return tile == null || tile.block().solid; + } + + public boolean isAccessible(int x, int y){ + return !wallSolid(x, y - 1) || !wallSolid(x, y + 1) || !wallSolid(x - 1, y) || !wallSolid(x + 1, y); + } + + public boolean floorBlends(int x, int y, Block block){ + Tile tile = tile(x, y); + return tile == null || tile.floor().id <= block.id; + } + + public Map getMap(){ + return currentMap; + } + + public void setMap(Map map){ + this.currentMap = map; + } + + public int width(){ + return tiles == null ? 0 : tiles.length; + } + + public int height(){ + return tiles == null ? 0 : tiles[0].length; + } + + public int toPacked(int x, int y){ + return x + y * width(); + } + + public Tile tile(int packed){ + return tiles == null ? null : tile(packed % width(), packed / width()); + } + + public Tile tile(int x, int y){ + if(tiles == null){ + return null; + } + if(!Mathf.inBounds(x, y, tiles)) return null; + return tiles[x][y]; + } + + public Tile rawTile(int x, int y){ + return tiles[x][y]; + } + + public Tile tileWorld(float x, float y){ + return tile(Mathf.scl2(x, tilesize), Mathf.scl2(y, tilesize)); + } + + public int toTile(float coord){ + return Mathf.scl2(coord, tilesize); + } + + public Tile[][] getTiles(){ + return tiles; + } + + private void clearTileEntities(){ + for(int x = 0; x < tiles.length; x++){ + for(int y = 0; y < tiles[0].length; y++){ + if(tiles[x][y] != null && tiles[x][y].entity != null){ + tiles[x][y].entity.remove(); + } + } + } + } + + /** + * Resizes the tile array to the specified size and returns the resulting tile array. + * Only use for loading saves! + */ public Tile[][] createTiles(int width, int height){ if(tiles != null){ clearTileEntities(); @@ -159,207 +168,214 @@ public class World extends Module{ return tiles; } - /**Call to signify the beginning of map loading. - * TileChangeEvents will not be fired until endMapLoad().*/ - public void beginMapLoad(){ - generating = true; - } + /** + * Call to signify the beginning of map loading. + * TileChangeEvents will not be fired until endMapLoad(). + */ + public void beginMapLoad(){ + generating = true; + } - /**Call to signify the end of map loading. Updates tile occlusions and sets up physics for the world. - * A WorldLoadEvent will be fire.*/ - public void endMapLoad(){ - for(int x = 0; x < tiles.length; x ++) { - for (int y = 0; y < tiles[0].length; y++) { - tiles[x][y].updateOcclusion(); + /** + * Call to signify the end of map loading. Updates tile occlusions and sets up physics for the world. + * A WorldLoadEvent will be fire. + */ + public void endMapLoad(){ + for(int x = 0; x < tiles.length; x++){ + for(int y = 0; y < tiles[0].length; y++){ + tiles[x][y].updateOcclusion(); - if(tiles[x][y].entity != null){ - tiles[x][y].entity.updateProximity(); - } - } - } + if(tiles[x][y].entity != null){ + tiles[x][y].entity.updateProximity(); + } + } + } - EntityPhysics.resizeTree(0, 0, tiles.length * tilesize, tiles[0].length * tilesize); + EntityPhysics.resizeTree(0, 0, tiles.length * tilesize, tiles[0].length * tilesize); - generating = false; - Events.fire(WorldLoadEvent.class); - } + generating = false; + Events.fire(WorldLoadEvent.class); + } - /**Loads up a procedural map. This does not call play(), but calls reset().*/ - public void loadProceduralMap(){ - Timers.mark(); - Timers.mark(); + /** + * Loads up a procedural map. This does not call play(), but calls reset(). + */ + public void loadProceduralMap(){ + Timers.mark(); + Timers.mark(); - logic.reset(); + logic.reset(); - beginMapLoad(); + beginMapLoad(); - int width = 400, height = 400; + int width = 400, height = 400; - Tile[][] tiles = createTiles(width, height); + Tile[][] tiles = createTiles(width, height); - Map map = new Map("Generated Map", new MapMeta(0, new ObjectMap<>(), width, height, null), true, () -> null); - setMap(map); + Map map = new Map("Generated Map", new MapMeta(0, new ObjectMap<>(), width, height, null), true, () -> null); + setMap(map); - EntityPhysics.resizeTree(0, 0, width * tilesize, height * tilesize); + EntityPhysics.resizeTree(0, 0, width * tilesize, height * tilesize); - Timers.mark(); - WorldGenerator.generateMap(tiles, Mathf.random(9999999)); - Log.info("Time to generate base map: {0}", Timers.elapsed()); + Timers.mark(); + WorldGenerator.generateMap(tiles, Mathf.random(9999999)); + Log.info("Time to generate base map: {0}", Timers.elapsed()); - Log.info("Time to generate fully without additional events: {0}", Timers.elapsed()); + Log.info("Time to generate fully without additional events: {0}", Timers.elapsed()); - endMapLoad(); + endMapLoad(); - Log.info("Full time to generate: {0}", Timers.elapsed()); - } + Log.info("Full time to generate: {0}", Timers.elapsed()); + } - public void setMap(Map map){ - this.currentMap = map; - } - - public void loadMap(Map map){ - loadMap(map, MathUtils.random(0, 999999)); - } - - public void loadMap(Map map, int seed){ - beginMapLoad(); - this.currentMap = map; - this.seed = seed; + public void loadMap(Map map){ + loadMap(map, MathUtils.random(0, 999999)); + } - int width = map.meta.width, height = map.meta.height; + public void loadMap(Map map, int seed){ + beginMapLoad(); + this.currentMap = map; + this.seed = seed; - createTiles(width, height); - - EntityPhysics.resizeTree(0, 0, width * tilesize, height * tilesize); + int width = map.meta.width, height = map.meta.height; - WorldGenerator.loadTileData(tiles, MapIO.readTileData(map, true), map.meta.hasOreGen(), seed); + createTiles(width, height); - if(!headless && state.teams.get(players[0].getTeam()).cores.size == 0){ - ui.showError("$text.map.nospawn"); - threads.runDelay(() -> state.set(State.menu)); - invalidMap = true; - }else{ - invalidMap = false; - } + EntityPhysics.resizeTree(0, 0, width * tilesize, height * tilesize); - endMapLoad(); - } + WorldGenerator.loadTileData(tiles, MapIO.readTileData(map, true), map.meta.hasOreGen(), seed); - public int getSeed(){ - return seed; - } + if(!headless && state.teams.get(players[0].getTeam()).cores.size == 0){ + ui.showError("$text.map.nospawn"); + threads.runDelay(() -> state.set(State.menu)); + invalidMap = true; + }else{ + invalidMap = false; + } - public void notifyChanged(Tile tile){ - if(!generating){ - threads.runDelay(() -> Events.fire(TileChangeEvent.class, tile)); - } - } + endMapLoad(); + } - public void removeBlock(Tile tile){ - if(!tile.block().isMultiblock() && !tile.isLinked()){ - tile.setBlock(Blocks.air); - }else{ - Tile target = tile.target(); - Array removals = target.getLinkedTiles(tempTiles); - for(Tile toremove : removals){ - //note that setting a new block automatically unlinks it - if(toremove != null) toremove.setBlock(Blocks.air); - } - } - } + public int getSeed(){ + return seed; + } - public void setBlock(Tile tile, Block block, Team team){ - tile.setBlock(block); - if (block.isMultiblock()) { - int offsetx = -(block.size - 1) / 2; - int offsety = -(block.size - 1) / 2; + public void notifyChanged(Tile tile){ + if(!generating){ + threads.runDelay(() -> Events.fire(TileChangeEvent.class, tile)); + } + } - for (int dx = 0; dx < block.size; dx++) { - for (int dy = 0; dy < block.size; dy++) { - int worldx = dx + offsetx + tile.x; - int worldy = dy + offsety + tile.y; - if (!(worldx == tile.x && worldy == tile.y)) { - Tile toplace = world.tile(worldx, worldy); - if (toplace != null) { - toplace.setLinked((byte) (dx + offsetx), (byte) (dy + offsety)); - toplace.setTeam(team); - } - } - } - } - } - } + public void removeBlock(Tile tile){ + if(!tile.block().isMultiblock() && !tile.isLinked()){ + tile.setBlock(Blocks.air); + }else{ + Tile target = tile.target(); + Array removals = target.getLinkedTiles(tempTiles); + for(Tile toremove : removals){ + //note that setting a new block automatically unlinks it + if(toremove != null) toremove.setBlock(Blocks.air); + } + } + } - /**Raycast, but with world coordinates.*/ - public GridPoint2 raycastWorld(float x, float y, float x2, float y2){ - return raycast(Mathf.scl2(x, tilesize), Mathf.scl2(y, tilesize), - Mathf.scl2(x2, tilesize), Mathf.scl2(y2, tilesize)); - } - - /**Input is in block coordinates, not world coordinates. - * @return null if no collisions found, block position otherwise.*/ - public GridPoint2 raycast(int x0f, int y0f, int x1, int y1){ - int x0 = x0f; - int y0 = y0f; - int dx = Math.abs(x1 - x0); - int dy = Math.abs(y1 - y0); + public void setBlock(Tile tile, Block block, Team team){ + tile.setBlock(block); + if(block.isMultiblock()){ + int offsetx = -(block.size - 1) / 2; + int offsety = -(block.size - 1) / 2; - int sx = x0 < x1 ? 1 : -1; - int sy = y0 < y1 ? 1 : -1; + for(int dx = 0; dx < block.size; dx++){ + for(int dy = 0; dy < block.size; dy++){ + int worldx = dx + offsetx + tile.x; + int worldy = dy + offsety + tile.y; + if(!(worldx == tile.x && worldy == tile.y)){ + Tile toplace = world.tile(worldx, worldy); + if(toplace != null){ + toplace.setLinked((byte) (dx + offsetx), (byte) (dy + offsety)); + toplace.setTeam(team); + } + } + } + } + } + } - int err = dx - dy; - int e2; - while(true){ + /** + * Raycast, but with world coordinates. + */ + public GridPoint2 raycastWorld(float x, float y, float x2, float y2){ + return raycast(Mathf.scl2(x, tilesize), Mathf.scl2(y, tilesize), + Mathf.scl2(x2, tilesize), Mathf.scl2(y2, tilesize)); + } - if(!passable(x0, y0)){ - return Tmp.g1.set(x0, y0); - } - if(x0 == x1 && y0 == y1) break; + /** + * Input is in block coordinates, not world coordinates. + * + * @return null if no collisions found, block position otherwise. + */ + public GridPoint2 raycast(int x0f, int y0f, int x1, int y1){ + int x0 = x0f; + int y0 = y0f; + int dx = Math.abs(x1 - x0); + int dy = Math.abs(y1 - y0); - e2 = 2 * err; - if(e2 > -dy){ - err = err - dy; - x0 = x0 + sx; - } + int sx = x0 < x1 ? 1 : -1; + int sy = y0 < y1 ? 1 : -1; - if(e2 < dx){ - err = err + dx; - y0 = y0 + sy; - } - } - return null; - } + int err = dx - dy; + int e2; + while(true){ - public void raycastEach(int x0f, int y0f, int x1, int y1, Raycaster cons){ - int x0 = x0f; - int y0 = y0f; - int dx = Math.abs(x1 - x0); - int dy = Math.abs(y1 - y0); + if(!passable(x0, y0)){ + return Tmp.g1.set(x0, y0); + } + if(x0 == x1 && y0 == y1) break; - int sx = x0 < x1 ? 1 : -1; - int sy = y0 < y1 ? 1 : -1; + e2 = 2 * err; + if(e2 > -dy){ + err = err - dy; + x0 = x0 + sx; + } - int err = dx - dy; - int e2; - while(true){ + if(e2 < dx){ + err = err + dx; + y0 = y0 + sy; + } + } + return null; + } - if(cons.accept(x0, y0)) break; - if(x0 == x1 && y0 == y1) break; + public void raycastEach(int x0f, int y0f, int x1, int y1, Raycaster cons){ + int x0 = x0f; + int y0 = y0f; + int dx = Math.abs(x1 - x0); + int dy = Math.abs(y1 - y0); - e2 = 2 * err; - if(e2 > -dy){ - err = err - dy; - x0 = x0 + sx; - } + int sx = x0 < x1 ? 1 : -1; + int sy = y0 < y1 ? 1 : -1; - if(e2 < dx){ - err = err + dx; - y0 = y0 + sy; - } - } - } + int err = dx - dy; + int e2; + while(true){ - public interface Raycaster{ - boolean accept(int x, int y); - } + if(cons.accept(x0, y0)) break; + if(x0 == x1 && y0 == y1) break; + + e2 = 2 * err; + if(e2 > -dy){ + err = err - dy; + x0 = x0 + sx; + } + + if(e2 < dx){ + err = err + dx; + y0 = y0 + sy; + } + } + } + + public interface Raycaster{ + boolean accept(int x, int y); + } } diff --git a/core/src/io/anuke/mindustry/editor/DrawOperation.java b/core/src/io/anuke/mindustry/editor/DrawOperation.java index 56fad5c41d..421a34bd6c 100755 --- a/core/src/io/anuke/mindustry/editor/DrawOperation.java +++ b/core/src/io/anuke/mindustry/editor/DrawOperation.java @@ -7,60 +7,66 @@ import io.anuke.mindustry.io.MapTileData.TileDataMarker; import io.anuke.ucore.util.Bits; public class DrawOperation{ - /**Data to apply operation to.*/ - private MapTileData data; - /**List of per-tile operations that occurred.*/ - private Array operations = new Array<>(); - /**Checks for duplicate operations, useful for brushes.*/ - private IntSet checks = new IntSet(); + /** + * Data to apply operation to. + */ + private MapTileData data; + /** + * List of per-tile operations that occurred. + */ + private Array operations = new Array<>(); + /** + * Checks for duplicate operations, useful for brushes. + */ + private IntSet checks = new IntSet(); - public DrawOperation(MapTileData data){ - this.data = data; - } + public DrawOperation(MapTileData data){ + this.data = data; + } - public boolean isEmpty(){ - return operations.size == 0; - } + public boolean isEmpty(){ + return operations.size == 0; + } - public boolean checkDuplicate(short x, short y){ - int i = Bits.packInt(x, y); - if(checks.contains(i)) return true; + public boolean checkDuplicate(short x, short y){ + int i = Bits.packInt(x, y); + if(checks.contains(i)) return true; - checks.add(i); - return false; - } + checks.add(i); + return false; + } - public void addOperation(TileOperation op){ - operations.add(op); - } + public void addOperation(TileOperation op){ + operations.add(op); + } - public void undo(MapEditor editor) { - for(int i = operations.size - 1; i >= 0; i --){ - TileOperation op = operations.get(i); - data.position(op.x, op.y); - data.write(op.from); - editor.renderer().updatePoint(op.x, op.y); - } - } + public void undo(MapEditor editor){ + for(int i = operations.size - 1; i >= 0; i--){ + TileOperation op = operations.get(i); + data.position(op.x, op.y); + data.write(op.from); + editor.renderer().updatePoint(op.x, op.y); + } + } - public void redo(MapEditor editor) { - for(TileOperation op : operations){ - data.position(op.x, op.y); - data.write(op.to); - editor.renderer().updatePoint(op.x, op.y); - } - } + public void redo(MapEditor editor){ + for(TileOperation op : operations){ + data.position(op.x, op.y); + data.write(op.to); + editor.renderer().updatePoint(op.x, op.y); + } + } - public static class TileOperation{ - public short x, y; - public TileDataMarker from; - public TileDataMarker to; + public static class TileOperation{ + public short x, y; + public TileDataMarker from; + public TileDataMarker to; - public TileOperation(short x, short y, TileDataMarker from, TileDataMarker to) { - this.x = x; - this.y = y; - this.from = from; - this.to = to; - } - } + public TileOperation(short x, short y, TileDataMarker from, TileDataMarker to){ + this.x = x; + this.y = y; + this.from = from; + this.to = to; + } + } } diff --git a/core/src/io/anuke/mindustry/editor/EditorTool.java b/core/src/io/anuke/mindustry/editor/EditorTool.java index c1bfe98750..70c69897e1 100644 --- a/core/src/io/anuke/mindustry/editor/EditorTool.java +++ b/core/src/io/anuke/mindustry/editor/EditorTool.java @@ -12,132 +12,132 @@ import io.anuke.ucore.util.Bits; import static io.anuke.mindustry.Vars.ui; public enum EditorTool{ - pick{ - public void touched(MapEditor editor, int x, int y){ - byte bf = editor.getMap().read(x, y, DataPosition.floor); - byte bw = editor.getMap().read(x, y, DataPosition.wall); - byte link = editor.getMap().read(x, y, DataPosition.link); + pick{ + public void touched(MapEditor editor, int x, int y){ + byte bf = editor.getMap().read(x, y, DataPosition.floor); + byte bw = editor.getMap().read(x, y, DataPosition.wall); + byte link = editor.getMap().read(x, y, DataPosition.link); - if(link != 0){ - x -= (Bits.getLeftByte(link) - 8); - y -= (Bits.getRightByte(link) - 8); - bf = editor.getMap().read(x, y, DataPosition.floor); - bw = editor.getMap().read(x, y, DataPosition.wall); - } + if(link != 0){ + x -= (Bits.getLeftByte(link) - 8); + y -= (Bits.getRightByte(link) - 8); + bf = editor.getMap().read(x, y, DataPosition.floor); + bw = editor.getMap().read(x, y, DataPosition.wall); + } - Block block = Block.getByID(bw == 0 ? bf : bw); - editor.setDrawBlock(block); - ui.editor.updateSelectedBlock(); - } - }, - pencil{ - { - edit = true; - draggable = true; - } + Block block = Block.getByID(bw == 0 ? bf : bw); + editor.setDrawBlock(block); + ui.editor.updateSelectedBlock(); + } + }, + pencil{ + { + edit = true; + draggable = true; + } - @Override - public void touched(MapEditor editor, int x, int y){ - editor.draw(x, y); - } - }, - eraser{ - { - edit = true; - draggable = true; - } + @Override + public void touched(MapEditor editor, int x, int y){ + editor.draw(x, y); + } + }, + eraser{ + { + edit = true; + draggable = true; + } - @Override - public void touched(MapEditor editor, int x, int y){ - editor.draw(x, y, Blocks.air); - } - }, - elevation{ - { - edit = true; - draggable = true; - } + @Override + public void touched(MapEditor editor, int x, int y){ + editor.draw(x, y, Blocks.air); + } + }, + elevation{ + { + edit = true; + draggable = true; + } - @Override - public void touched(MapEditor editor, int x, int y){ - editor.elevate(x, y); - } - }, - line{ - { + @Override + public void touched(MapEditor editor, int x, int y){ + editor.elevate(x, y); + } + }, + line{ + { - } - }, - fill{ - { - edit = true; - } - - public void touched(MapEditor editor, int x, int y){ - if(editor.getDrawBlock().isMultiblock()){ - //don't fill multiblocks, thanks - pencil.touched(editor, x, y); - return; - } + } + }, + fill{ + { + edit = true; + } - boolean floor = editor.getDrawBlock() instanceof Floor; + public void touched(MapEditor editor, int x, int y){ + if(editor.getDrawBlock().isMultiblock()){ + //don't fill multiblocks, thanks + pencil.touched(editor, x, y); + return; + } - byte bf = editor.getMap().read(x, y, DataPosition.floor); - byte bw = editor.getMap().read(x, y, DataPosition.wall); - boolean synth = editor.getDrawBlock().synthetic(); - byte brt = Bits.packByte((byte)editor.getDrawRotation(), (byte)editor.getDrawTeam().ordinal()); + boolean floor = editor.getDrawBlock() instanceof Floor; - byte dest = floor ? bf: bw; - byte draw = (byte)editor.getDrawBlock().id; + byte bf = editor.getMap().read(x, y, DataPosition.floor); + byte bw = editor.getMap().read(x, y, DataPosition.wall); + boolean synth = editor.getDrawBlock().synthetic(); + byte brt = Bits.packByte((byte) editor.getDrawRotation(), (byte) editor.getDrawTeam().ordinal()); - int width = editor.getMap().width(); - int height = editor.getMap().height(); + byte dest = floor ? bf : bw; + byte draw = (byte) editor.getDrawBlock().id; - IntSet set = new IntSet(); - IntArray points = new IntArray(); - points.add(asInt(x, y, editor.getMap().width())); + int width = editor.getMap().width(); + int height = editor.getMap().height(); - while(points.size != 0){ - int pos = points.pop(); - int px = pos % width; - int py = pos / width; - set.add(pos); + IntSet set = new IntSet(); + IntArray points = new IntArray(); + points.add(asInt(x, y, editor.getMap().width())); - byte nbf = editor.getMap().read(px, py, DataPosition.floor); - byte nbw = editor.getMap().read(px, py, DataPosition.wall); + while(points.size != 0){ + int pos = points.pop(); + int px = pos % width; + int py = pos / width; + set.add(pos); - if((floor ? nbf : nbw) == dest){ - TileDataMarker prev = editor.getPrev(px, py, false); + byte nbf = editor.getMap().read(px, py, DataPosition.floor); + byte nbw = editor.getMap().read(px, py, DataPosition.wall); - if(floor) { - editor.getMap().write(px, py, DataPosition.floor, draw); - }else { - editor.getMap().write(px, py, DataPosition.wall, draw); - } + if((floor ? nbf : nbw) == dest){ + TileDataMarker prev = editor.getPrev(px, py, false); - if(synth){ - editor.getMap().write(px, py, DataPosition.rotationTeam, brt); - } + if(floor){ + editor.getMap().write(px, py, DataPosition.floor, draw); + }else{ + editor.getMap().write(px, py, DataPosition.wall, draw); + } - if(px > 0 && !set.contains(asInt(px - 1, py, width))) points.add(asInt(px - 1, py, width)); - if(py > 0 && !set.contains(asInt(px, py - 1, width))) points.add(asInt(px, py - 1, width)); - if(px < width - 1 && !set.contains(asInt(px + 1, py, width))) points.add(asInt(px + 1, py, width)); - if(py < height - 1 && !set.contains(asInt(px, py + 1, width))) points.add(asInt(px, py + 1, width)); + if(synth){ + editor.getMap().write(px, py, DataPosition.rotationTeam, brt); + } - editor.onWrite(px, py, prev); - } - } - } - - int asInt(int x, int y, int width){ - return x+y*width; - } - }, - zoom; + if(px > 0 && !set.contains(asInt(px - 1, py, width))) points.add(asInt(px - 1, py, width)); + if(py > 0 && !set.contains(asInt(px, py - 1, width))) points.add(asInt(px, py - 1, width)); + if(px < width - 1 && !set.contains(asInt(px + 1, py, width))) points.add(asInt(px + 1, py, width)); + if(py < height - 1 && !set.contains(asInt(px, py + 1, width))) points.add(asInt(px, py + 1, width)); - boolean edit, draggable; - - public void touched(MapEditor editor, int x, int y){ - - } + editor.onWrite(px, py, prev); + } + } + } + + int asInt(int x, int y, int width){ + return x + y * width; + } + }, + zoom; + + boolean edit, draggable; + + public void touched(MapEditor editor, int x, int y){ + + } } diff --git a/core/src/io/anuke/mindustry/editor/MapEditor.java b/core/src/io/anuke/mindustry/editor/MapEditor.java index 6ad61bd62c..9d7c2fc44e 100644 --- a/core/src/io/anuke/mindustry/editor/MapEditor.java +++ b/core/src/io/anuke/mindustry/editor/MapEditor.java @@ -14,267 +14,267 @@ import io.anuke.ucore.util.Bits; import io.anuke.ucore.util.Mathf; public class MapEditor{ - public static final int minMapSize = 128, maxMapSize = 512; - public static final int[] brushSizes = {1, 2, 3, 4, 5, 9, 15}; - - private MapTileData map; - private ObjectMap tags = new ObjectMap<>(); - private MapRenderer renderer = new MapRenderer(this); + public static final int minMapSize = 128, maxMapSize = 512; + public static final int[] brushSizes = {1, 2, 3, 4, 5, 9, 15}; - private int brushSize = 1; - private byte elevation; - private int rotation; - private Block drawBlock = Blocks.stone; - private Team drawTeam = Team.none; - - public MapEditor(){ + private MapTileData map; + private ObjectMap tags = new ObjectMap<>(); + private MapRenderer renderer = new MapRenderer(this); - } - - public MapTileData getMap(){ - return map; - } + private int brushSize = 1; + private byte elevation; + private int rotation; + private Block drawBlock = Blocks.stone; + private Team drawTeam = Team.none; - public ObjectMap getTags() { - return tags; - } + public MapEditor(){ - public void beginEdit(MapTileData map, ObjectMap tags, boolean clear){ - this.map = map; - this.brushSize = 1; - this.tags = tags; + } - if(clear) { - for (int x = 0; x < map.width(); x++) { - for (int y = 0; y < map.height(); y++) { - map.write(x, y, DataPosition.floor, (byte) Blocks.stone.id); - } - } - } + public MapTileData getMap(){ + return map; + } - drawBlock = Blocks.stone; - renderer.resize(map.width(), map.height()); - } + public ObjectMap getTags(){ + return tags; + } - public void setDrawElevation(int elevation){ - this.elevation = (byte)elevation; - } + public void beginEdit(MapTileData map, ObjectMap tags, boolean clear){ + this.map = map; + this.brushSize = 1; + this.tags = tags; - public byte getDrawElevation(){ - return elevation; - } + if(clear){ + for(int x = 0; x < map.width(); x++){ + for(int y = 0; y < map.height(); y++){ + map.write(x, y, DataPosition.floor, (byte) Blocks.stone.id); + } + } + } - public int getDrawRotation(){ - return rotation; - } + drawBlock = Blocks.stone; + renderer.resize(map.width(), map.height()); + } - public void setDrawRotation(int rotation){ - this.rotation = rotation; - } + public byte getDrawElevation(){ + return elevation; + } - public void setDrawTeam(Team team){ - this.drawTeam = team; - } + public void setDrawElevation(int elevation){ + this.elevation = (byte) elevation; + } - public Team getDrawTeam() { - return drawTeam; - } + public int getDrawRotation(){ + return rotation; + } - public Block getDrawBlock(){ - return drawBlock; - } - - public void setDrawBlock(Block block){ - this.drawBlock = block; - } - - public void setBrushSize(int size){ - this.brushSize = size; - } + public void setDrawRotation(int rotation){ + this.rotation = rotation; + } - public int getBrushSize() { - return brushSize; - } + public Team getDrawTeam(){ + return drawTeam; + } - public void draw(int x, int y){ - draw(x, y, drawBlock); - } + public void setDrawTeam(Team team){ + this.drawTeam = team; + } - public void draw(int x, int y, Block drawBlock){ - if(x < 0 || y < 0 || x >= map.width() || y >= map.height()){ - return; - } + public Block getDrawBlock(){ + return drawBlock; + } - byte writeID = (byte)drawBlock.id; - byte partID = (byte)Blocks.blockpart.id; - byte rotationTeam = Bits.packByte(drawBlock.rotate ? (byte)rotation : 0, drawBlock.synthetic() ? (byte)drawTeam.ordinal() : 0); + public void setDrawBlock(Block block){ + this.drawBlock = block; + } - boolean isfloor = drawBlock instanceof Floor && drawBlock != Blocks.air; + public int getBrushSize(){ + return brushSize; + } - if(drawBlock.isMultiblock()) { + public void setBrushSize(int size){ + this.brushSize = size; + } - int offsetx = -(drawBlock.size - 1) / 2; - int offsety = -(drawBlock.size - 1) / 2; + public void draw(int x, int y){ + draw(x, y, drawBlock); + } - for(int i = 0; i < 2; i ++){ - for (int dx = 0; dx < drawBlock.size; dx++) { - for (int dy = 0; dy < drawBlock.size; dy++) { - int worldx = dx + offsetx + x; - int worldy = dy + offsety + y; + public void draw(int x, int y, Block drawBlock){ + if(x < 0 || y < 0 || x >= map.width() || y >= map.height()){ + return; + } - if (Mathf.inBounds(worldx, worldy, map.width(), map.height())) { - TileDataMarker prev = getPrev(worldx, worldy, false); + byte writeID = (byte) drawBlock.id; + byte partID = (byte) Blocks.blockpart.id; + byte rotationTeam = Bits.packByte(drawBlock.rotate ? (byte) rotation : 0, drawBlock.synthetic() ? (byte) drawTeam.ordinal() : 0); - if(i == 1) { - map.write(worldx, worldy, DataPosition.wall, partID); - map.write(worldx, worldy, DataPosition.rotationTeam, rotationTeam); - map.write(worldx, worldy, DataPosition.link, Bits.packByte((byte) (dx + offsetx + 8), (byte) (dy + offsety + 8))); - }else{ - byte link = map.read(worldx, worldy, DataPosition.link); - byte block = map.read(worldx, worldy, DataPosition.wall); + boolean isfloor = drawBlock instanceof Floor && drawBlock != Blocks.air; - if (link != 0) { - removeLinked(worldx - (Bits.getLeftByte(link) - 8), worldy - (Bits.getRightByte(link) - 8)); - }else if(Block.getByID(block).isMultiblock()){ - removeLinked(worldx, worldy); - } - } + if(drawBlock.isMultiblock()){ - onWrite(worldx, worldy, prev); - } - } - } - } + int offsetx = -(drawBlock.size - 1) / 2; + int offsety = -(drawBlock.size - 1) / 2; - TileDataMarker prev = getPrev(x, y, false); + for(int i = 0; i < 2; i++){ + for(int dx = 0; dx < drawBlock.size; dx++){ + for(int dy = 0; dy < drawBlock.size; dy++){ + int worldx = dx + offsetx + x; + int worldy = dy + offsety + y; - map.write(x, y, DataPosition.wall, writeID); - map.write(x, y, DataPosition.link, (byte)0); - map.write(x, y, DataPosition.rotationTeam, rotationTeam); + if(Mathf.inBounds(worldx, worldy, map.width(), map.height())){ + TileDataMarker prev = getPrev(worldx, worldy, false); - onWrite(x, y, prev); - }else{ + if(i == 1){ + map.write(worldx, worldy, DataPosition.wall, partID); + map.write(worldx, worldy, DataPosition.rotationTeam, rotationTeam); + map.write(worldx, worldy, DataPosition.link, Bits.packByte((byte) (dx + offsetx + 8), (byte) (dy + offsety + 8))); + }else{ + byte link = map.read(worldx, worldy, DataPosition.link); + byte block = map.read(worldx, worldy, DataPosition.wall); - for (int rx = -brushSize; rx <= brushSize; rx++) { - for (int ry = -brushSize; ry <= brushSize; ry++) { - if (Mathf.dst(rx, ry) <= brushSize - 0.5f) { - int wx = x + rx, wy = y + ry; + if(link != 0){ + removeLinked(worldx - (Bits.getLeftByte(link) - 8), worldy - (Bits.getRightByte(link) - 8)); + }else if(Block.getByID(block).isMultiblock()){ + removeLinked(worldx, worldy); + } + } - if (wx < 0 || wy < 0 || wx >= map.width() || wy >= map.height()) { - continue; - } + onWrite(worldx, worldy, prev); + } + } + } + } - TileDataMarker prev = getPrev(wx, wy, true); + TileDataMarker prev = getPrev(x, y, false); - if(!isfloor) { - byte link = map.read(wx, wy, DataPosition.link); + map.write(x, y, DataPosition.wall, writeID); + map.write(x, y, DataPosition.link, (byte) 0); + map.write(x, y, DataPosition.rotationTeam, rotationTeam); - if (link != 0) { - removeLinked(wx - (Bits.getLeftByte(link) - 8), wy - (Bits.getRightByte(link) - 8)); - } - } + onWrite(x, y, prev); + }else{ - if(isfloor){ - map.write(wx, wy, DataPosition.floor, writeID); - map.write(wx, wy, DataPosition.elevation, elevation); - }else{ - map.write(wx, wy, DataPosition.wall, writeID); - map.write(wx, wy, DataPosition.link, (byte)0); - map.write(wx, wy, DataPosition.rotationTeam, rotationTeam); - } + for(int rx = -brushSize; rx <= brushSize; rx++){ + for(int ry = -brushSize; ry <= brushSize; ry++){ + if(Mathf.dst(rx, ry) <= brushSize - 0.5f){ + int wx = x + rx, wy = y + ry; - onWrite(x + rx, y + ry, prev); - } - } - } - } - } + if(wx < 0 || wy < 0 || wx >= map.width() || wy >= map.height()){ + continue; + } - public void elevate(int x, int y){ - if(x < 0 || y < 0 || x >= map.width() || y >= map.height()){ - return; - } + TileDataMarker prev = getPrev(wx, wy, true); - for (int rx = -brushSize; rx <= brushSize; rx++) { - for (int ry = -brushSize; ry <= brushSize; ry++) { - if (Mathf.dst(rx, ry) <= brushSize - 0.5f) { - int wx = x + rx, wy = y + ry; + if(!isfloor){ + byte link = map.read(wx, wy, DataPosition.link); - if (wx < 0 || wy < 0 || wx >= map.width() || wy >= map.height()) { - continue; - } + if(link != 0){ + removeLinked(wx - (Bits.getLeftByte(link) - 8), wy - (Bits.getRightByte(link) - 8)); + } + } - TileDataMarker prev = getPrev(wx, wy, true); + if(isfloor){ + map.write(wx, wy, DataPosition.floor, writeID); + map.write(wx, wy, DataPosition.elevation, elevation); + }else{ + map.write(wx, wy, DataPosition.wall, writeID); + map.write(wx, wy, DataPosition.link, (byte) 0); + map.write(wx, wy, DataPosition.rotationTeam, rotationTeam); + } - map.write(wx, wy, DataPosition.elevation, elevation); + onWrite(x + rx, y + ry, prev); + } + } + } + } + } - onWrite(x + rx, y + ry, prev); - } - } - } - } + public void elevate(int x, int y){ + if(x < 0 || y < 0 || x >= map.width() || y >= map.height()){ + return; + } - private void removeLinked(int x, int y){ - Block block = Block.getByID(map.read(x, y, DataPosition.wall)); + for(int rx = -brushSize; rx <= brushSize; rx++){ + for(int ry = -brushSize; ry <= brushSize; ry++){ + if(Mathf.dst(rx, ry) <= brushSize - 0.5f){ + int wx = x + rx, wy = y + ry; - int offsetx = -(block.size-1)/2; - int offsety = -(block.size-1)/2; - for(int dx = 0; dx < block.size; dx ++){ - for(int dy = 0; dy < block.size; dy ++){ - int worldx = x + dx + offsetx, worldy = y + dy + offsety; - if(Mathf.inBounds(worldx, worldy, map.width(), map.height())){ - TileDataMarker prev = getPrev(worldx, worldy, false); + if(wx < 0 || wy < 0 || wx >= map.width() || wy >= map.height()){ + continue; + } - map.write(worldx, worldy, DataPosition.link, (byte)0); - map.write(worldx, worldy, DataPosition.rotationTeam, (byte)0); - map.write(worldx, worldy, DataPosition.wall, (byte)0); + TileDataMarker prev = getPrev(wx, wy, true); - onWrite(worldx, worldy, prev); - } - } - } - } + map.write(wx, wy, DataPosition.elevation, elevation); - boolean checkDupes(int x, int y){ - return Vars.ui.editor.getView().checkForDuplicates((short) x, (short) y); - } + onWrite(x + rx, y + ry, prev); + } + } + } + } - void onWrite(int x, int y, TileDataMarker previous){ - if(previous == null){ - renderer.updatePoint(x, y); - return; - } + private void removeLinked(int x, int y){ + Block block = Block.getByID(map.read(x, y, DataPosition.wall)); - TileDataMarker current = map.new TileDataMarker(); - map.position(x, y); - map.read(current); + int offsetx = -(block.size - 1) / 2; + int offsety = -(block.size - 1) / 2; + for(int dx = 0; dx < block.size; dx++){ + for(int dy = 0; dy < block.size; dy++){ + int worldx = x + dx + offsetx, worldy = y + dy + offsety; + if(Mathf.inBounds(worldx, worldy, map.width(), map.height())){ + TileDataMarker prev = getPrev(worldx, worldy, false); - Vars.ui.editor.getView().addTileOp(new TileOperation((short) x, (short) y, previous, current)); - renderer.updatePoint(x, y); - } + map.write(worldx, worldy, DataPosition.link, (byte) 0); + map.write(worldx, worldy, DataPosition.rotationTeam, (byte) 0); + map.write(worldx, worldy, DataPosition.wall, (byte) 0); - TileDataMarker getPrev(int x, int y, boolean checkDupes){ - if(checkDupes && checkDupes(x, y)){ - return null; - }else{ - TileDataMarker marker = map.newDataMarker(); - map.position(x, y); - map.read(marker); - return marker; - } - } + onWrite(worldx, worldy, prev); + } + } + } + } - public MapRenderer renderer() { - return renderer; - } + boolean checkDupes(int x, int y){ + return Vars.ui.editor.getView().checkForDuplicates((short) x, (short) y); + } - public void resize(int width, int height){ - map = new MapTileData(width, height); - for (int x = 0; x < map.width(); x++) { - for (int y = 0; y < map.height(); y++) { - map.write(x, y, DataPosition.floor, (byte)Blocks.stone.id); - } - } - renderer.resize(width, height); - } + void onWrite(int x, int y, TileDataMarker previous){ + if(previous == null){ + renderer.updatePoint(x, y); + return; + } + + TileDataMarker current = map.new TileDataMarker(); + map.position(x, y); + map.read(current); + + Vars.ui.editor.getView().addTileOp(new TileOperation((short) x, (short) y, previous, current)); + renderer.updatePoint(x, y); + } + + TileDataMarker getPrev(int x, int y, boolean checkDupes){ + if(checkDupes && checkDupes(x, y)){ + return null; + }else{ + TileDataMarker marker = map.newDataMarker(); + map.position(x, y); + map.read(marker); + return marker; + } + } + + public MapRenderer renderer(){ + return renderer; + } + + public void resize(int width, int height){ + map = new MapTileData(width, height); + for(int x = 0; x < map.width(); x++){ + for(int y = 0; y < map.height(); y++){ + map.write(x, y, DataPosition.floor, (byte) Blocks.stone.id); + } + } + renderer.resize(width, height); + } } diff --git a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java index 25c3191b95..f004d59c63 100644 --- a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java +++ b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java @@ -48,72 +48,72 @@ import java.io.InputStream; import static io.anuke.mindustry.Vars.*; public class MapEditorDialog extends Dialog implements Disposable{ - private MapEditor editor; - private MapView view; - private MapInfoDialog infoDialog; - private MapLoadDialog loadDialog; - private MapResizeDialog resizeDialog; - private ScrollPane pane; - private FloatingDialog menu; - private boolean saved = false; - private boolean shownWithMap = false; - - private ButtonGroup blockgroup; - - public MapEditorDialog(){ - super("$text.mapeditor", "dialog"); + private MapEditor editor; + private MapView view; + private MapInfoDialog infoDialog; + private MapLoadDialog loadDialog; + private MapResizeDialog resizeDialog; + private ScrollPane pane; + private FloatingDialog menu; + private boolean saved = false; + private boolean shownWithMap = false; - editor = new MapEditor(); - view = new MapView(editor); + private ButtonGroup blockgroup; - infoDialog = new MapInfoDialog(editor); + public MapEditorDialog(){ + super("$text.mapeditor", "dialog"); - menu = new FloatingDialog("$text.menu"); - menu.addCloseButton(); + editor = new MapEditor(); + view = new MapView(editor); - float isize = 16*2f; - float swidth = 180f; + infoDialog = new MapInfoDialog(editor); - menu.content().table(t -> { - t.defaults().size(swidth, 60f).padBottom(5).padRight(5).padLeft(5); + menu = new FloatingDialog("$text.menu"); + menu.addCloseButton(); - t.addImageTextButton("$text.editor.savemap", "icon-floppy-16", isize, this::save).size(swidth*2f + 10, 60f).colspan(2); + float isize = 16 * 2f; + float swidth = 180f; - t.row(); + menu.content().table(t -> { + t.defaults().size(swidth, 60f).padBottom(5).padRight(5).padLeft(5); - t.addImageTextButton("$text.editor.mapinfo", "icon-pencil", isize, () -> { - infoDialog.show(); - menu.hide(); - }); + t.addImageTextButton("$text.editor.savemap", "icon-floppy-16", isize, this::save).size(swidth * 2f + 10, 60f).colspan(2); - t.addImageTextButton("$text.editor.resize", "icon-resize", isize, () -> { - resizeDialog.show(); - menu.hide(); - }); + t.row(); - t.row(); + t.addImageTextButton("$text.editor.mapinfo", "icon-pencil", isize, () -> { + infoDialog.show(); + menu.hide(); + }); - t.addImageTextButton("$text.editor.import", "icon-load-map", isize, () -> - createDialog("$text.editor.import", - "$text.editor.importmap", "$text.editor.importmap.description", "icon-load-map", (Listenable)loadDialog::show, - "$text.editor.importfile", "$text.editor.importfile.description", "icon-file", (Listenable)() -> { - Platform.instance.showFileChooser("$text.loadimage", "Map Files", file -> { - ui.loadAnd(() -> { - try{ - DataInputStream stream = new DataInputStream(file.read()); + t.addImageTextButton("$text.editor.resize", "icon-resize", isize, () -> { + resizeDialog.show(); + menu.hide(); + }); - MapMeta meta = MapIO.readMapMeta(stream); - MapTileData data = MapIO.readTileData(stream, meta, false); + t.row(); - editor.beginEdit(data, meta.tags, false); - view.clearStack(); - }catch (Exception e){ - ui.showError(Bundles.format("text.editor.errorimageload", Strings.parseException(e, false))); - Log.err(e); - } - }); - }, true, mapExtension); - }/*, + t.addImageTextButton("$text.editor.import", "icon-load-map", isize, () -> + createDialog("$text.editor.import", + "$text.editor.importmap", "$text.editor.importmap.description", "icon-load-map", (Listenable) loadDialog::show, + "$text.editor.importfile", "$text.editor.importfile.description", "icon-file", (Listenable) () -> { + Platform.instance.showFileChooser("$text.loadimage", "Map Files", file -> { + ui.loadAnd(() -> { + try{ + DataInputStream stream = new DataInputStream(file.read()); + + MapMeta meta = MapIO.readMapMeta(stream); + MapTileData data = MapIO.readTileData(stream, meta, false); + + editor.beginEdit(data, meta.tags, false); + view.clearStack(); + }catch(Exception e){ + ui.showError(Bundles.format("text.editor.errorimageload", Strings.parseException(e, false))); + Log.err(e); + } + }); + }, true, mapExtension); + }/*, "$text.editor.importimage", "$text.editor.importimage.description", "icon-file-image", (Listenable)() -> { if(gwt){ ui.showError("$text.web.unsupported"); @@ -134,36 +134,36 @@ public class MapEditorDialog extends Dialog implements Disposable{ } }*/)); - t.addImageTextButton("$text.editor.export", "icon-save-map", isize, () -> createDialog("$text.editor.export", - "$text.editor.exportfile", "$text.editor.exportfile.description", "icon-file", (Listenable)() -> { - if(!gwt) { - Platform.instance.showFileChooser("$text.saveimage", "Map Files", file -> { - file = file.parent().child(file.nameWithoutExtension() + "." + mapExtension); - FileHandle result = file; - ui.loadAnd(() -> { + t.addImageTextButton("$text.editor.export", "icon-save-map", isize, () -> createDialog("$text.editor.export", + "$text.editor.exportfile", "$text.editor.exportfile.description", "icon-file", (Listenable) () -> { + if(!gwt){ + Platform.instance.showFileChooser("$text.saveimage", "Map Files", file -> { + file = file.parent().child(file.nameWithoutExtension() + "." + mapExtension); + FileHandle result = file; + ui.loadAnd(() -> { - try { - if (!editor.getTags().containsKey("name")) { - editor.getTags().put("name", result.nameWithoutExtension()); - } - MapIO.writeMap(result.write(false), editor.getTags(), editor.getMap()); - } catch (Exception e) { - ui.showError(Bundles.format("text.editor.errorimagesave", Strings.parseException(e, false))); - Log.err(e); - } - }); - }, false, mapExtension); - }else{ - try { - ByteArrayOutputStream ba = new ByteArrayOutputStream(); - MapIO.writeMap(ba, editor.getTags(), editor.getMap()); - Platform.instance.downloadFile(editor.getTags().get("name", "unknown") + "." + mapExtension, ba.toByteArray()); - }catch (IOException e){ - ui.showError(Bundles.format("text.editor.errorimagesave", Strings.parseException(e, false))); - Log.err(e); - } - } - }/*, + try{ + if(!editor.getTags().containsKey("name")){ + editor.getTags().put("name", result.nameWithoutExtension()); + } + MapIO.writeMap(result.write(false), editor.getTags(), editor.getMap()); + }catch(Exception e){ + ui.showError(Bundles.format("text.editor.errorimagesave", Strings.parseException(e, false))); + Log.err(e); + } + }); + }, false, mapExtension); + }else{ + try{ + ByteArrayOutputStream ba = new ByteArrayOutputStream(); + MapIO.writeMap(ba, editor.getTags(), editor.getMap()); + Platform.instance.downloadFile(editor.getTags().get("name", "unknown") + "." + mapExtension, ba.toByteArray()); + }catch(IOException e){ + ui.showError(Bundles.format("text.editor.errorimagesave", Strings.parseException(e, false))); + Log.err(e); + } + } + }/*, "$text.editor.exportimage", "$text.editor.exportimage.description", "icon-file-image", (Listenable)() -> { if(gwt){ ui.showError("$text.web.unsupported"); @@ -183,438 +183,441 @@ public class MapEditorDialog extends Dialog implements Disposable{ } }*/)); - t.row(); - - t.row(); - }); - - menu.content().row(); - - menu.content().addImageTextButton("$text.quit", "icon-back", isize, () -> { - tryExit(); - menu.hide(); - }).padTop(-5).size(swidth*2f + 10, 60f); - - resizeDialog = new MapResizeDialog(editor, (x, y) -> { - if(!(editor.getMap().width() == x && editor.getMap().height() == y)){ - ui.loadAnd(() -> { - editor.resize(x, y); - view.clearStack(); - }); - } - }); - - loadDialog = new MapLoadDialog(map -> { - - ui.loadAnd(() -> { - try (DataInputStream stream = new DataInputStream(map.stream.get())){ - MapMeta meta = MapIO.readMapMeta(stream); - MapTileData data = MapIO.readTileData(stream, meta, false); - - editor.beginEdit(data, meta.tags, false); - view.clearStack(); - }catch (IOException e){ - ui.showError(Bundles.format("text.editor.errormapload", Strings.parseException(e, false))); - Log.err(e); - } - }); - }); - - setFillParent(true); - - clearChildren(); - margin(0); - build.begin(this); - build(); - build.end(); - - update(() -> { - if(Core.scene.getKeyboardFocus() instanceof Dialog && Core.scene.getKeyboardFocus() != this) { - return; - } - - Vector2 v = pane.stageToLocalCoordinates(Graphics.mouse()); - - if(v.x >= 0 && v.y >= 0 && v.x <= pane.getWidth() && v.y <= pane.getHeight()){ - Core.scene.setScrollFocus(pane); - }else{ - Core.scene.setScrollFocus(null); - } - - if(Core.scene != null && Core.scene.getKeyboardFocus() == this){ - doInput(); - } - }); - - shown(() -> { - saved = true; - Platform.instance.beginForceLandscape(); - view.clearStack(); - Core.scene.setScrollFocus(view); - if(!shownWithMap){ - editor.beginEdit(new MapTileData(256, 256), new ObjectMap<>(), true); - } - shownWithMap = false; - - Timers.runTask(10f, Platform.instance::updateRPC); - }); - - hidden(() -> { - Platform.instance.updateRPC(); - Platform.instance.endForceLandscape(); - }); - } - - private void save(){ - String name = editor.getTags().get("name", ""); - - if(name.isEmpty()){ - ui.showError("$text.editor.save.noname"); - }else{ - Map map = world.maps().getByName(name); - if(map != null && !map.custom){ - ui.showError("$text.editor.save.overwrite"); - }else{ - world.maps().saveMap(name, editor.getMap(), editor.getTags()); - ui.showInfoFade("$text.editor.saved"); - } - } - - menu.hide(); - saved = true; - } - - /**Argument format: - * 0) button name - * 1) description - * 2) icon name - * 3) listener */ - private FloatingDialog createDialog(String title, Object... arguments){ - FloatingDialog dialog = new FloatingDialog(title); - - float h = 90f; - - dialog.content().defaults().size(360f, h).padBottom(5).padRight(5).padLeft(5); - - for(int i = 0; i < arguments.length; i += 4){ - String name = (String)arguments[i]; - String description = (String)arguments[i + 1]; - String iconname = (String)arguments[i + 2]; - Listenable listenable = (Listenable)arguments[i + 3]; - - TextButton button = dialog.content().addButton(name, () -> { - listenable.listen(); - dialog.hide(); - menu.hide(); - }).left().get(); - - button.clearChildren(); - button.table("button", t -> { - t.addImage(iconname).size(16*3); - t.update(() -> t.background(button.getClickListener().isOver() ? "button-over" : "button")); - }).padLeft(-10).padBottom(-3).size(h); - button.table(t -> { - t.add(name).growX().wrap(); - t.row(); - t.add(description).color(Color.GRAY).growX().wrap(); - }).growX().padLeft(8); - - button.row(); - - dialog.content().row(); - } - - dialog.addCloseButton(); - dialog.show(); - - return dialog; - } - - @Override - public Dialog show(){ - return super.show(Core.scene, Actions.sequence(Actions.alpha(0f), Actions.scaleTo(1f, 1f), Actions.fadeIn(0.3f))); - } - - @Override - public void dispose(){ - editor.renderer().dispose(); - } - - public void beginEditMap(InputStream is){ - ui.loadAnd(() -> { - try { - shownWithMap = true; - DataInputStream stream = new DataInputStream(is); - MapMeta meta = MapIO.readMapMeta(stream); - editor.beginEdit(MapIO.readTileData(stream, meta, false), meta.tags, false); - is.close(); - show(); - }catch (Exception e){ - Log.err(e); - ui.showError(Bundles.format("text.editor.errorimageload", Strings.parseException(e, false))); - } - }); - } - - public MapView getView() { - return view; - } - - public void resetSaved(){ - saved = false; - } - - public void updateSelectedBlock(){ - Block block = editor.getDrawBlock(); - for(int j = 0; j < Block.all().size; j ++){ - if(block.id == j && j < blockgroup.getButtons().size){ - blockgroup.getButtons().get(j).setChecked(true); - break; - } - } - } - - public boolean hasPane(){ - return Core.scene.getScrollFocus() == pane || Core.scene.getKeyboardFocus() != this; - } - - public void build(){ - float amount = 10f, baseSize = 60f; - - float size = mobile ? (int)(Math.min(Gdx.graphics.getHeight(), Gdx.graphics.getWidth()) / amount / Unit.dp.scl(1f)) : - Math.min(Gdx.graphics.getDisplayMode().height / amount, baseSize); - - new table(){{ - aleft(); - - new table("button"){{ - margin(0); - Table tools = new Table(); - tools.top(); - atop(); - - ButtonGroup group = new ButtonGroup<>(); - - Consumer addTool = tool -> { - ImageButton button = new ImageButton("icon-" + tool.name(), "toggle"); - button.clicked(() -> view.setTool(tool)); - button.resizeImage(16*2f); - button.update(() -> button.setChecked(view.getTool() == tool)); - group.add(button); - if (tool == EditorTool.pencil) - button.setChecked(true); - - tools.add(button).padBottom(-5.1f); - }; - - tools.defaults().size(size, size + 4f).padBottom(-5.1f); - - //tools.addImageButton("icon-back", 16*2, () -> tryExit()); - - tools.addImageButton("icon-menu-large", 16*2f, menu::show); - - ImageButton grid = tools.addImageButton("icon-grid", "toggle", 16*2f, () -> view.setGrid(!view.isGrid())).get(); + t.row(); + + t.row(); + }); + + menu.content().row(); + + menu.content().addImageTextButton("$text.quit", "icon-back", isize, () -> { + tryExit(); + menu.hide(); + }).padTop(-5).size(swidth * 2f + 10, 60f); + + resizeDialog = new MapResizeDialog(editor, (x, y) -> { + if(!(editor.getMap().width() == x && editor.getMap().height() == y)){ + ui.loadAnd(() -> { + editor.resize(x, y); + view.clearStack(); + }); + } + }); + + loadDialog = new MapLoadDialog(map -> { + + ui.loadAnd(() -> { + try(DataInputStream stream = new DataInputStream(map.stream.get())){ + MapMeta meta = MapIO.readMapMeta(stream); + MapTileData data = MapIO.readTileData(stream, meta, false); + + editor.beginEdit(data, meta.tags, false); + view.clearStack(); + }catch(IOException e){ + ui.showError(Bundles.format("text.editor.errormapload", Strings.parseException(e, false))); + Log.err(e); + } + }); + }); + + setFillParent(true); + + clearChildren(); + margin(0); + build.begin(this); + build(); + build.end(); + + update(() -> { + if(Core.scene.getKeyboardFocus() instanceof Dialog && Core.scene.getKeyboardFocus() != this){ + return; + } + + Vector2 v = pane.stageToLocalCoordinates(Graphics.mouse()); + + if(v.x >= 0 && v.y >= 0 && v.x <= pane.getWidth() && v.y <= pane.getHeight()){ + Core.scene.setScrollFocus(pane); + }else{ + Core.scene.setScrollFocus(null); + } + + if(Core.scene != null && Core.scene.getKeyboardFocus() == this){ + doInput(); + } + }); + + shown(() -> { + saved = true; + Platform.instance.beginForceLandscape(); + view.clearStack(); + Core.scene.setScrollFocus(view); + if(!shownWithMap){ + editor.beginEdit(new MapTileData(256, 256), new ObjectMap<>(), true); + } + shownWithMap = false; + + Timers.runTask(10f, Platform.instance::updateRPC); + }); + + hidden(() -> { + Platform.instance.updateRPC(); + Platform.instance.endForceLandscape(); + }); + } + + private void save(){ + String name = editor.getTags().get("name", ""); + + if(name.isEmpty()){ + ui.showError("$text.editor.save.noname"); + }else{ + Map map = world.maps().getByName(name); + if(map != null && !map.custom){ + ui.showError("$text.editor.save.overwrite"); + }else{ + world.maps().saveMap(name, editor.getMap(), editor.getTags()); + ui.showInfoFade("$text.editor.saved"); + } + } + + menu.hide(); + saved = true; + } + + /** + * Argument format: + * 0) button name + * 1) description + * 2) icon name + * 3) listener + */ + private FloatingDialog createDialog(String title, Object... arguments){ + FloatingDialog dialog = new FloatingDialog(title); + + float h = 90f; + + dialog.content().defaults().size(360f, h).padBottom(5).padRight(5).padLeft(5); + + for(int i = 0; i < arguments.length; i += 4){ + String name = (String) arguments[i]; + String description = (String) arguments[i + 1]; + String iconname = (String) arguments[i + 2]; + Listenable listenable = (Listenable) arguments[i + 3]; + + TextButton button = dialog.content().addButton(name, () -> { + listenable.listen(); + dialog.hide(); + menu.hide(); + }).left().get(); + + button.clearChildren(); + button.table("button", t -> { + t.addImage(iconname).size(16 * 3); + t.update(() -> t.background(button.getClickListener().isOver() ? "button-over" : "button")); + }).padLeft(-10).padBottom(-3).size(h); + button.table(t -> { + t.add(name).growX().wrap(); + t.row(); + t.add(description).color(Color.GRAY).growX().wrap(); + }).growX().padLeft(8); + + button.row(); + + dialog.content().row(); + } + + dialog.addCloseButton(); + dialog.show(); + + return dialog; + } + + @Override + public Dialog show(){ + return super.show(Core.scene, Actions.sequence(Actions.alpha(0f), Actions.scaleTo(1f, 1f), Actions.fadeIn(0.3f))); + } + + @Override + public void dispose(){ + editor.renderer().dispose(); + } + + public void beginEditMap(InputStream is){ + ui.loadAnd(() -> { + try{ + shownWithMap = true; + DataInputStream stream = new DataInputStream(is); + MapMeta meta = MapIO.readMapMeta(stream); + editor.beginEdit(MapIO.readTileData(stream, meta, false), meta.tags, false); + is.close(); + show(); + }catch(Exception e){ + Log.err(e); + ui.showError(Bundles.format("text.editor.errorimageload", Strings.parseException(e, false))); + } + }); + } + + public MapView getView(){ + return view; + } + + public void resetSaved(){ + saved = false; + } + + public void updateSelectedBlock(){ + Block block = editor.getDrawBlock(); + for(int j = 0; j < Block.all().size; j++){ + if(block.id == j && j < blockgroup.getButtons().size){ + blockgroup.getButtons().get(j).setChecked(true); + break; + } + } + } + + public boolean hasPane(){ + return Core.scene.getScrollFocus() == pane || Core.scene.getKeyboardFocus() != this; + } - addTool.accept(EditorTool.zoom); + public void build(){ + float amount = 10f, baseSize = 60f; + + float size = mobile ? (int) (Math.min(Gdx.graphics.getHeight(), Gdx.graphics.getWidth()) / amount / Unit.dp.scl(1f)) : + Math.min(Gdx.graphics.getDisplayMode().height / amount, baseSize); + + new table(){{ + aleft(); + + new table("button"){{ + margin(0); + Table tools = new Table(); + tools.top(); + atop(); - tools.row(); + ButtonGroup group = new ButtonGroup<>(); - ImageButton undo = tools.addImageButton("icon-undo", 16*2f, () -> view.undo()).get(); - ImageButton redo = tools.addImageButton("icon-redo", 16*2f, () -> view.redo()).get(); - - addTool.accept(EditorTool.pick); - - tools.row(); - - undo.setDisabled(() -> !view.getStack().canUndo()); - redo.setDisabled(() -> !view.getStack().canRedo()); - - undo.update(() -> undo.getImage().setColor(undo.isDisabled() ? Color.GRAY : Color.WHITE)); - redo.update(() -> redo.getImage().setColor(redo.isDisabled() ? Color.GRAY : Color.WHITE)); - grid.update(() -> grid.setChecked(view.isGrid())); - - addTool.accept(EditorTool.line); - addTool.accept(EditorTool.pencil); - addTool.accept(EditorTool.eraser); - - tools.row(); - - addTool.accept(EditorTool.fill); - addTool.accept(EditorTool.elevation); - - ImageButton rotate = tools.addImageButton("icon-arrow-16", 16*2f, () -> editor.setDrawRotation((editor.getDrawRotation() + 1)%4)).get(); - rotate.getImage().update(() ->{ - rotate.getImage().setRotation(editor.getDrawRotation() * 90); - rotate.getImage().setOrigin(Align.center); - }); - - tools.row(); - - tools.table("button", t -> { - t.add("$text.editor.teams"); - }).colspan(3).height(40).width(size*3f); + Consumer addTool = tool -> { + ImageButton button = new ImageButton("icon-" + tool.name(), "toggle"); + button.clicked(() -> view.setTool(tool)); + button.resizeImage(16 * 2f); + button.update(() -> button.setChecked(view.getTool() == tool)); + group.add(button); + if(tool == EditorTool.pencil) + button.setChecked(true); - tools.row(); + tools.add(button).padBottom(-5.1f); + }; - ButtonGroup teamgroup = new ButtonGroup<>(); + tools.defaults().size(size, size + 4f).padBottom(-5.1f); - int i = 0; + //tools.addImageButton("icon-back", 16*2, () -> tryExit()); - for(Team team : Team.all){ - ImageButton button = new ImageButton("white", "toggle"); - button.margin(4f, 4f, 10f, 4f); - button.getImageCell().grow(); - button.getStyle().imageUpColor = team.color; - button.clicked(() -> editor.setDrawTeam(team)); - button.update(() -> button.setChecked(editor.getDrawTeam() == team)); - teamgroup.add(button); - tools.add(button).padBottom(-5.1f); + tools.addImageButton("icon-menu-large", 16 * 2f, menu::show); - if(i++ % 3 == 2) tools.row(); - } + ImageButton grid = tools.addImageButton("icon-grid", "toggle", 16 * 2f, () -> view.setGrid(!view.isGrid())).get(); - add(tools).top().padBottom(-6); + addTool.accept(EditorTool.zoom); - row(); + tools.row(); - new table("button"){{ - atop(); - Slider slider = new Slider(0, MapEditor.brushSizes.length-1, 1, false); - slider.moved(f -> editor.setBrushSize(MapEditor.brushSizes[(int)(float)f])); - new label("brush"); - row(); - add(slider).width(size*3f-20).padTop(4f); - }}.padTop(5).growX().growY().top().end(); - - row(); - - get().table("button", t -> { - t.add("$text.editor.elevation"); - }).colspan(3).height(40).width(size*3f); + ImageButton undo = tools.addImageButton("icon-undo", 16 * 2f, () -> view.undo()).get(); + ImageButton redo = tools.addImageButton("icon-redo", 16 * 2f, () -> view.redo()).get(); - row(); - - get().table("button", t -> { - t.margin(0); - t.addImageButton("icon-arrow-left", 16*2f, () -> editor.setDrawElevation(editor.getDrawElevation() - 1)) - .disabled(b -> editor.getDrawElevation() <= -1).size(size); - - t.label(() -> editor.getDrawElevation() == -1 ? "$text.editor.slope" : (editor.getDrawElevation() + "")) - .size(size).get().setAlignment(Align.center, Align.center); - - t.addImageButton("icon-arrow-right", 16*2f, () -> editor.setDrawElevation(editor.getDrawElevation() + 1)) - .disabled(b -> editor.getDrawElevation() >= 127).size(size); - }).colspan(3).height(size).padTop(-5).width(size*3f); - - }}.left().growY().end(); - - - new table("button"){{ - margin(5); - marginBottom(10); - add(view).grow(); - }}.grow().end(); - - new table(){{ - - row(); - - addBlockSelection(get()); - - row(); - - }}.right().growY().end(); - }}.grow().end(); - } - - private void doInput(){ - //tool select - for(int i = 0; i < EditorTool.values().length; i ++){ - if(Inputs.keyTap(Input.valueOf("NUM_" + (i+1)))){ - view.setTool(EditorTool.values()[i]); - break; - } - } - - if(Inputs.keyTap(Input.R)){ - editor.setDrawRotation((editor.getDrawRotation() + 1)%4); - } - - if(Inputs.keyTap(Input.E)){ - editor.setDrawRotation(Mathf.mod((editor.getDrawRotation() + 1), 4)); - } - - //ctrl keys (undo, redo, save) - if(UIUtils.ctrl()){ - if(Inputs.keyTap(Input.Z)){ - view.undo(); - } - - if(Inputs.keyTap(Input.Y)){ - view.redo(); - } - - if(Inputs.keyTap(Input.S)){ - save(); - } - - if(Inputs.keyTap(Input.G)){ - view.setGrid(!view.isGrid()); - } - } - } - - private void tryExit(){ - if(!saved){ - ui.showConfirm("$text.confirm", "$text.editor.unsaved", this::hide); - }else{ - hide(); - } - } - - private void addBlockSelection(Table table){ - Table content = new Table(); - pane = new ScrollPane(content, "volume"); - pane.setFadeScrollBars(false); - pane.setOverscroll(true, false); - ButtonGroup group = new ButtonGroup<>(); - blockgroup = group; - - int i = 0; - - for(Block block : Block.all()){ - TextureRegion[] regions = block.getCompactIcon(); - if((block.synthetic() && (Recipe.getByResult(block) == null || !control.database().isUnlocked(Recipe.getByResult(block)))) && !debug && block != StorageBlocks.core) continue; - - if(regions.length == 0 || regions[0] == Draw.region("jjfgj")) continue; - - Stack stack = new Stack(); - - for(TextureRegion region : regions){ - stack.add(new Image(region)); - } - - ImageButton button = new ImageButton("white", "toggle"); - button.clicked(() -> editor.setDrawBlock(block)); - button.resizeImage(8*4f); - button.getImageCell().setActor(stack); - button.addChild(stack); - button.getImage().remove(); - button.update(() -> button.setChecked(editor.getDrawBlock() == block)); - group.add(button); - content.add(button).pad(4f).size(53f, 58f); - - if(i++ % 3 == 2){ - content.row(); - } - } - - group.getButtons().get(2).setChecked(true); - - Table extra = new Table("button"); - extra.labelWrap(() -> editor.getDrawBlock().formalName).width(220f).center(); - table.add(extra).growX(); - table.row(); - table.add(pane).growY().fillX(); - } + addTool.accept(EditorTool.pick); + + tools.row(); + + undo.setDisabled(() -> !view.getStack().canUndo()); + redo.setDisabled(() -> !view.getStack().canRedo()); + + undo.update(() -> undo.getImage().setColor(undo.isDisabled() ? Color.GRAY : Color.WHITE)); + redo.update(() -> redo.getImage().setColor(redo.isDisabled() ? Color.GRAY : Color.WHITE)); + grid.update(() -> grid.setChecked(view.isGrid())); + + addTool.accept(EditorTool.line); + addTool.accept(EditorTool.pencil); + addTool.accept(EditorTool.eraser); + + tools.row(); + + addTool.accept(EditorTool.fill); + addTool.accept(EditorTool.elevation); + + ImageButton rotate = tools.addImageButton("icon-arrow-16", 16 * 2f, () -> editor.setDrawRotation((editor.getDrawRotation() + 1) % 4)).get(); + rotate.getImage().update(() -> { + rotate.getImage().setRotation(editor.getDrawRotation() * 90); + rotate.getImage().setOrigin(Align.center); + }); + + tools.row(); + + tools.table("button", t -> { + t.add("$text.editor.teams"); + }).colspan(3).height(40).width(size * 3f); + + tools.row(); + + ButtonGroup teamgroup = new ButtonGroup<>(); + + int i = 0; + + for(Team team : Team.all){ + ImageButton button = new ImageButton("white", "toggle"); + button.margin(4f, 4f, 10f, 4f); + button.getImageCell().grow(); + button.getStyle().imageUpColor = team.color; + button.clicked(() -> editor.setDrawTeam(team)); + button.update(() -> button.setChecked(editor.getDrawTeam() == team)); + teamgroup.add(button); + tools.add(button).padBottom(-5.1f); + + if(i++ % 3 == 2) tools.row(); + } + + add(tools).top().padBottom(-6); + + row(); + + new table("button"){{ + atop(); + Slider slider = new Slider(0, MapEditor.brushSizes.length - 1, 1, false); + slider.moved(f -> editor.setBrushSize(MapEditor.brushSizes[(int) (float) f])); + new label("brush"); + row(); + add(slider).width(size * 3f - 20).padTop(4f); + }}.padTop(5).growX().growY().top().end(); + + row(); + + get().table("button", t -> { + t.add("$text.editor.elevation"); + }).colspan(3).height(40).width(size * 3f); + + row(); + + get().table("button", t -> { + t.margin(0); + t.addImageButton("icon-arrow-left", 16 * 2f, () -> editor.setDrawElevation(editor.getDrawElevation() - 1)) + .disabled(b -> editor.getDrawElevation() <= -1).size(size); + + t.label(() -> editor.getDrawElevation() == -1 ? "$text.editor.slope" : (editor.getDrawElevation() + "")) + .size(size).get().setAlignment(Align.center, Align.center); + + t.addImageButton("icon-arrow-right", 16 * 2f, () -> editor.setDrawElevation(editor.getDrawElevation() + 1)) + .disabled(b -> editor.getDrawElevation() >= 127).size(size); + }).colspan(3).height(size).padTop(-5).width(size * 3f); + + }}.left().growY().end(); + + + new table("button"){{ + margin(5); + marginBottom(10); + add(view).grow(); + }}.grow().end(); + + new table(){{ + + row(); + + addBlockSelection(get()); + + row(); + + }}.right().growY().end(); + }}.grow().end(); + } + + private void doInput(){ + //tool select + for(int i = 0; i < EditorTool.values().length; i++){ + if(Inputs.keyTap(Input.valueOf("NUM_" + (i + 1)))){ + view.setTool(EditorTool.values()[i]); + break; + } + } + + if(Inputs.keyTap(Input.R)){ + editor.setDrawRotation((editor.getDrawRotation() + 1) % 4); + } + + if(Inputs.keyTap(Input.E)){ + editor.setDrawRotation(Mathf.mod((editor.getDrawRotation() + 1), 4)); + } + + //ctrl keys (undo, redo, save) + if(UIUtils.ctrl()){ + if(Inputs.keyTap(Input.Z)){ + view.undo(); + } + + if(Inputs.keyTap(Input.Y)){ + view.redo(); + } + + if(Inputs.keyTap(Input.S)){ + save(); + } + + if(Inputs.keyTap(Input.G)){ + view.setGrid(!view.isGrid()); + } + } + } + + private void tryExit(){ + if(!saved){ + ui.showConfirm("$text.confirm", "$text.editor.unsaved", this::hide); + }else{ + hide(); + } + } + + private void addBlockSelection(Table table){ + Table content = new Table(); + pane = new ScrollPane(content, "volume"); + pane.setFadeScrollBars(false); + pane.setOverscroll(true, false); + ButtonGroup group = new ButtonGroup<>(); + blockgroup = group; + + int i = 0; + + for(Block block : Block.all()){ + TextureRegion[] regions = block.getCompactIcon(); + if((block.synthetic() && (Recipe.getByResult(block) == null || !control.database().isUnlocked(Recipe.getByResult(block)))) && !debug && block != StorageBlocks.core) + continue; + + if(regions.length == 0 || regions[0] == Draw.region("jjfgj")) continue; + + Stack stack = new Stack(); + + for(TextureRegion region : regions){ + stack.add(new Image(region)); + } + + ImageButton button = new ImageButton("white", "toggle"); + button.clicked(() -> editor.setDrawBlock(block)); + button.resizeImage(8 * 4f); + button.getImageCell().setActor(stack); + button.addChild(stack); + button.getImage().remove(); + button.update(() -> button.setChecked(editor.getDrawBlock() == block)); + group.add(button); + content.add(button).pad(4f).size(53f, 58f); + + if(i++ % 3 == 2){ + content.row(); + } + } + + group.getButtons().get(2).setChecked(true); + + Table extra = new Table("button"); + extra.labelWrap(() -> editor.getDrawBlock().formalName).width(220f).center(); + table.add(extra).growX(); + table.row(); + table.add(pane).growY().fillX(); + } } diff --git a/core/src/io/anuke/mindustry/editor/MapLoadDialog.java b/core/src/io/anuke/mindustry/editor/MapLoadDialog.java index 52fd0eb2a3..c9c249636e 100644 --- a/core/src/io/anuke/mindustry/editor/MapLoadDialog.java +++ b/core/src/io/anuke/mindustry/editor/MapLoadDialog.java @@ -14,68 +14,68 @@ import io.anuke.ucore.scene.ui.layout.Table; import static io.anuke.mindustry.Vars.world; public class MapLoadDialog extends FloatingDialog{ - private Map selected = null; + private Map selected = null; - public MapLoadDialog(Consumer loader) { - super("$text.editor.loadmap"); + public MapLoadDialog(Consumer loader){ + super("$text.editor.loadmap"); - shown(this::rebuild); - rebuild(); + shown(this::rebuild); + rebuild(); - TextButton button = new TextButton("$text.load"); - button.setDisabled(() -> selected == null); - button.clicked(() -> { - if (selected != null) { - loader.accept(selected); - hide(); - } - }); + TextButton button = new TextButton("$text.load"); + button.setDisabled(() -> selected == null); + button.clicked(() -> { + if(selected != null){ + loader.accept(selected); + hide(); + } + }); - buttons().defaults().size(200f, 50f); - buttons().addButton("$text.cancel", this::hide); - buttons().add(button); - } + buttons().defaults().size(200f, 50f); + buttons().addButton("$text.cancel", this::hide); + buttons().add(button); + } - public void rebuild(){ - content().clear(); - if(world.maps().all().size > 0){ - selected = world.maps().all().first(); - } + public void rebuild(){ + content().clear(); + if(world.maps().all().size > 0){ + selected = world.maps().all().first(); + } - ButtonGroup group = new ButtonGroup<>(); + ButtonGroup group = new ButtonGroup<>(); - int maxcol = 3; + int maxcol = 3; - int i = 0; + int i = 0; - Table table = new Table(); - table.defaults().size(200f, 90f).pad(4f); - table.margin(10f); + Table table = new Table(); + table.defaults().size(200f, 90f).pad(4f); + table.margin(10f); - ScrollPane pane = new ScrollPane(table, "horizontal"); - pane.setFadeScrollBars(false); + ScrollPane pane = new ScrollPane(table, "horizontal"); + pane.setFadeScrollBars(false); - for (Map map : world.maps().all()) { + for(Map map : world.maps().all()){ - TextButton button = new TextButton(map.getDisplayName(), "toggle"); - button.add(new BorderImage(map.texture, 2f)).size(16 * 4f); - button.getCells().reverse(); - button.clicked(() -> selected = map); - button.getLabelCell().grow().left().padLeft(5f); - group.add(button); - table.add(button); - if (++i % maxcol == 0) table.row(); - } + TextButton button = new TextButton(map.getDisplayName(), "toggle"); + button.add(new BorderImage(map.texture, 2f)).size(16 * 4f); + button.getCells().reverse(); + button.clicked(() -> selected = map); + button.getLabelCell().grow().left().padLeft(5f); + group.add(button); + table.add(button); + if(++i % maxcol == 0) table.row(); + } - if(world.maps().all().size == 0){ - pane.setStyle(Core.skin.get("clear", ScrollPaneStyle.class)); - table.add("$text.maps.none").center(); - }else { - content().add("$text.editor.loadmap"); - } + if(world.maps().all().size == 0){ + pane.setStyle(Core.skin.get("clear", ScrollPaneStyle.class)); + table.add("$text.maps.none").center(); + }else{ + content().add("$text.editor.loadmap"); + } - content().row(); - content().add(pane); - } + content().row(); + content().add(pane); + } } diff --git a/core/src/io/anuke/mindustry/editor/MapRenderer.java b/core/src/io/anuke/mindustry/editor/MapRenderer.java index 16fc993438..b86c8bf7e1 100644 --- a/core/src/io/anuke/mindustry/editor/MapRenderer.java +++ b/core/src/io/anuke/mindustry/editor/MapRenderer.java @@ -34,18 +34,18 @@ public class MapRenderer implements Disposable{ public void resize(int width, int height){ if(chunks != null){ - for(int x = 0; x < chunks.length; x ++){ - for(int y = 0; y < chunks[0].length; y ++){ + for(int x = 0; x < chunks.length; x++){ + for(int y = 0; y < chunks[0].length; y++){ chunks[x][y].dispose(); } } } - chunks = new IndexedRenderer[(int)Math.ceil((float)width/chunksize)][(int)Math.ceil((float)height/chunksize )]; + chunks = new IndexedRenderer[(int) Math.ceil((float) width / chunksize)][(int) Math.ceil((float) height / chunksize)]; - for(int x = 0; x < chunks.length; x ++){ - for(int y = 0; y < chunks[0].length; y ++){ - chunks[x][y] = new IndexedRenderer(chunksize*chunksize*2); + for(int x = 0; x < chunks.length; x++){ + for(int y = 0; y < chunks[0].length; y++){ + chunks[x][y] = new IndexedRenderer(chunksize * chunksize * 2); } } this.width = width; @@ -69,8 +69,8 @@ public class MapRenderer implements Disposable{ updates.addAll(delayedUpdates); delayedUpdates.clear(); - for(int x = 0; x < chunks.length; x ++){ - for(int y = 0; y < chunks[0].length; y ++){ + for(int x = 0; x < chunks.length; x++){ + for(int y = 0; y < chunks[0].length; y++){ IndexedRenderer mesh = chunks[x][y]; mesh.getTransformMatrix().setToTranslation(tx, ty, 0).scl(tw / (width * tilesize), @@ -86,19 +86,19 @@ public class MapRenderer implements Disposable{ public void updatePoint(int x, int y){ //TODO spread out over multiple frames? - updates.add(x + y*width); + updates.add(x + y * width); } public void updateAll(){ - for(int x = 0; x < width; x ++){ - for(int y = 0; y < height; y ++){ + for(int x = 0; x < width; x++){ + for(int y = 0; y < height; y++){ render(x, y); } } } private void render(int wx, int wy){ - int x = wx/chunksize, y = wy/chunksize; + int x = wx / chunksize, y = wy / chunksize; IndexedRenderer mesh = chunks[x][y]; //TileDataMarker data = editor.getMap().readAt(wx, wy); byte bf = editor.getMap().read(wx, wy, DataPosition.floor); @@ -111,19 +111,19 @@ public class MapRenderer implements Disposable{ Block floor = Block.getByID(bf); Block wall = Block.getByID(bw); - int offsetx = -(wall.size-1)/2; - int offsety = -(wall.size-1)/2; + int offsetx = -(wall.size - 1) / 2; + int offsety = -(wall.size - 1) / 2; TextureRegion region; - if(bw != 0) { + if(bw != 0){ region = wall.getEditorIcon(); - if (wall.rotate) { + if(wall.rotate){ mesh.draw((wx % chunksize) + (wy % chunksize) * chunksize, region, wx * tilesize + offsetx * tilesize, wy * tilesize + offsety * tilesize, region.getRegionWidth(), region.getRegionHeight(), rotation * 90 - 90); - } else { + }else{ mesh.draw((wx % chunksize) + (wy % chunksize) * chunksize, region, wx * tilesize + offsetx * tilesize, wy * tilesize + offsety * tilesize, region.getRegionWidth(), region.getRegionHeight()); @@ -131,16 +131,16 @@ public class MapRenderer implements Disposable{ }else{ region = floor.getEditorIcon(); - mesh.draw((wx % chunksize) + (wy % chunksize)*chunksize, region, wx * tilesize, wy * tilesize, 8, 8); + mesh.draw((wx % chunksize) + (wy % chunksize) * chunksize, region, wx * tilesize, wy * tilesize, 8, 8); } boolean check = checkElevation(elev, wx, wy); - if(wall.update || wall.destructible) { + if(wall.update || wall.destructible){ mesh.setColor(team.color); region = Draw.region("block-border"); }else if(elev > 0 && check){ - mesh.setColor(tmpColor.fromHsv((360f * elev/127f * 4f) % 360f, 0.5f + (elev / 4f) % 0.5f, 1f)); + mesh.setColor(tmpColor.fromHsv((360f * elev / 127f * 4f) % 360f, 0.5f + (elev / 4f) % 0.5f, 1f)); region = Draw.region("block-elevation"); }else if(elev == -1){ region = Draw.region("block-slope"); @@ -148,8 +148,8 @@ public class MapRenderer implements Disposable{ region = Draw.region("clear"); } - mesh.draw((wx % chunksize) + (wy % chunksize)*chunksize + chunksize*chunksize, region, - wx * tilesize + offsetx*tilesize, wy * tilesize + offsety * tilesize, + mesh.draw((wx % chunksize) + (wy % chunksize) * chunksize + chunksize * chunksize, region, + wx * tilesize + offsetx * tilesize, wy * tilesize + offsety * tilesize, region.getRegionWidth(), region.getRegionHeight()); mesh.setColor(Color.WHITE); } @@ -165,19 +165,19 @@ public class MapRenderer implements Disposable{ if(value < elev){ return true; }else if(value > elev){ - delayedUpdates.add(wx + wy*width); + delayedUpdates.add(wx + wy * width); } } return false; } @Override - public void dispose() { + public void dispose(){ if(chunks == null){ return; } - for(int x = 0; x < chunks.length; x ++){ - for(int y = 0; y < chunks[0].length; y ++){ + for(int x = 0; x < chunks.length; x++){ + for(int y = 0; y < chunks[0].length; y++){ if(chunks[x][y] != null){ chunks[x][y].dispose(); } diff --git a/core/src/io/anuke/mindustry/editor/MapResizeDialog.java b/core/src/io/anuke/mindustry/editor/MapResizeDialog.java index 3daebbd825..a5ffc63835 100644 --- a/core/src/io/anuke/mindustry/editor/MapResizeDialog.java +++ b/core/src/io/anuke/mindustry/editor/MapResizeDialog.java @@ -9,55 +9,55 @@ import io.anuke.ucore.scene.ui.layout.Table; import io.anuke.ucore.util.Mathf; public class MapResizeDialog extends FloatingDialog{ - int[] validMapSizes = {200, 300, 400, 500}; - int width, height; - - public MapResizeDialog(MapEditor editor, BiConsumer cons){ - super("$text.editor.resizemap"); - shown(() -> { - content().clear(); - MapTileData data = editor.getMap(); - width = data.width(); - height = data.height(); - - Table table = new Table(); - - for(boolean w : Mathf.booleans){ - int curr = w ? data.width() : data.height(); - int idx = 0; - for(int i = 0; i < validMapSizes.length; i ++) { - if (validMapSizes[i] == curr) idx = i; - } - - table.add(w ? "$text.width": "$text.height").padRight(8f); - ButtonGroup group = new ButtonGroup<>(); - for(int i = 0; i < validMapSizes.length; i ++){ - int size = validMapSizes[i]; - TextButton button = new TextButton(size + "", "toggle"); - button.clicked(() -> { - if(w) - width = size; - else - height = size; - }); - group.add(button); - if(i == idx) button.setChecked(true); - table.add(button).size(100f, 54f).pad(2f); - } - - table.row(); - } - content().row(); - content().add(table); - - }); - - buttons().defaults().size(200f, 50f); - buttons().addButton("$text.cancel", this::hide); - buttons().addButton("$text.editor.resize", () -> { - cons.accept(width, height); - hide(); - }); - - } + int[] validMapSizes = {200, 300, 400, 500}; + int width, height; + + public MapResizeDialog(MapEditor editor, BiConsumer cons){ + super("$text.editor.resizemap"); + shown(() -> { + content().clear(); + MapTileData data = editor.getMap(); + width = data.width(); + height = data.height(); + + Table table = new Table(); + + for(boolean w : Mathf.booleans){ + int curr = w ? data.width() : data.height(); + int idx = 0; + for(int i = 0; i < validMapSizes.length; i++){ + if(validMapSizes[i] == curr) idx = i; + } + + table.add(w ? "$text.width" : "$text.height").padRight(8f); + ButtonGroup group = new ButtonGroup<>(); + for(int i = 0; i < validMapSizes.length; i++){ + int size = validMapSizes[i]; + TextButton button = new TextButton(size + "", "toggle"); + button.clicked(() -> { + if(w) + width = size; + else + height = size; + }); + group.add(button); + if(i == idx) button.setChecked(true); + table.add(button).size(100f, 54f).pad(2f); + } + + table.row(); + } + content().row(); + content().add(table); + + }); + + buttons().defaults().size(200f, 50f); + buttons().addButton("$text.cancel", this::hide); + buttons().addButton("$text.editor.resize", () -> { + cons.accept(width, height); + hide(); + }); + + } } diff --git a/core/src/io/anuke/mindustry/editor/MapSaveDialog.java b/core/src/io/anuke/mindustry/editor/MapSaveDialog.java index cc4956ef5a..c11318298b 100644 --- a/core/src/io/anuke/mindustry/editor/MapSaveDialog.java +++ b/core/src/io/anuke/mindustry/editor/MapSaveDialog.java @@ -1,7 +1,7 @@ package io.anuke.mindustry.editor; -import io.anuke.mindustry.io.Map; import io.anuke.mindustry.core.Platform; +import io.anuke.mindustry.io.Map; import io.anuke.mindustry.ui.dialogs.FloatingDialog; import io.anuke.ucore.function.Consumer; import io.anuke.ucore.scene.ui.TextButton; @@ -11,65 +11,65 @@ import static io.anuke.mindustry.Vars.ui; import static io.anuke.mindustry.Vars.world; public class MapSaveDialog extends FloatingDialog{ - private TextField field; - private Consumer listener; - - public MapSaveDialog(Consumer cons){ - super("$text.editor.savemap"); - field = new TextField(); - listener = cons; - - Platform.instance.addDialog(field); - - shown(() -> { - content().clear(); - content().label(() ->{ - Map map = world.maps().getByName(field.getText()); - if(map != null){ - if(map.custom){ - return "$text.editor.overwrite"; - }else{ - return "$text.editor.failoverwrite"; - } - } - return ""; - }).colspan(2); - content().row(); - content().add("$text.editor.mapname").padRight(14f); - content().add(field).size(220f, 48f); - }); - - buttons().defaults().size(200f, 50f).pad(2f); - buttons().addButton("$text.cancel", this::hide); - - TextButton button = new TextButton("$text.save"); - button.clicked(() -> { - if(!invalid()){ - cons.accept(field.getText()); - hide(); - } - }); - button.setDisabled(this::invalid); - buttons().add(button); - } + private TextField field; + private Consumer listener; - public void save(){ - if(!invalid()){ - listener.accept(field.getText()); - }else{ - ui.showError("$text.editor.failoverwrite"); - } - } - - public void setFieldText(String text){ - field.setText(text); - } - - private boolean invalid(){ - if(field.getText().isEmpty()){ - return true; - } - Map map = world.maps().getByName(field.getText()); - return map != null && !map.custom; - } + public MapSaveDialog(Consumer cons){ + super("$text.editor.savemap"); + field = new TextField(); + listener = cons; + + Platform.instance.addDialog(field); + + shown(() -> { + content().clear(); + content().label(() -> { + Map map = world.maps().getByName(field.getText()); + if(map != null){ + if(map.custom){ + return "$text.editor.overwrite"; + }else{ + return "$text.editor.failoverwrite"; + } + } + return ""; + }).colspan(2); + content().row(); + content().add("$text.editor.mapname").padRight(14f); + content().add(field).size(220f, 48f); + }); + + buttons().defaults().size(200f, 50f).pad(2f); + buttons().addButton("$text.cancel", this::hide); + + TextButton button = new TextButton("$text.save"); + button.clicked(() -> { + if(!invalid()){ + cons.accept(field.getText()); + hide(); + } + }); + button.setDisabled(this::invalid); + buttons().add(button); + } + + public void save(){ + if(!invalid()){ + listener.accept(field.getText()); + }else{ + ui.showError("$text.editor.failoverwrite"); + } + } + + public void setFieldText(String text){ + field.setText(text); + } + + private boolean invalid(){ + if(field.getText().isEmpty()){ + return true; + } + Map map = world.maps().getByName(field.getText()); + return map != null && !map.custom; + } } diff --git a/core/src/io/anuke/mindustry/editor/MapView.java b/core/src/io/anuke/mindustry/editor/MapView.java index acab432e5b..e50885787a 100644 --- a/core/src/io/anuke/mindustry/editor/MapView.java +++ b/core/src/io/anuke/mindustry/editor/MapView.java @@ -33,261 +33,261 @@ import static io.anuke.mindustry.Vars.mobile; import static io.anuke.mindustry.Vars.ui; public class MapView extends Element implements GestureListener{ - private MapEditor editor; - private EditorTool tool = EditorTool.pencil; - private OperationStack stack = new OperationStack(); - private DrawOperation op; - private Bresenham2 br = new Bresenham2(); - private boolean updated = false; - private float offsetx, offsety; - private float zoom = 1f; - private boolean grid = false; - private GridImage image = new GridImage(0, 0); - private Vector2 vec = new Vector2(); - private Rectangle rect = new Rectangle(); - private Vector2[][] brushPolygons = new Vector2[MapEditor.brushSizes.length][0]; + private MapEditor editor; + private EditorTool tool = EditorTool.pencil; + private OperationStack stack = new OperationStack(); + private DrawOperation op; + private Bresenham2 br = new Bresenham2(); + private boolean updated = false; + private float offsetx, offsety; + private float zoom = 1f; + private boolean grid = false; + private GridImage image = new GridImage(0, 0); + private Vector2 vec = new Vector2(); + private Rectangle rect = new Rectangle(); + private Vector2[][] brushPolygons = new Vector2[MapEditor.brushSizes.length][0]; - private boolean drawing; - private int lastx, lasty; - private int startx, starty; - private float mousex, mousey; - private EditorTool lastTool; + private boolean drawing; + private int lastx, lasty; + private int startx, starty; + private float mousex, mousey; + private EditorTool lastTool; - public void setTool(EditorTool tool){ - this.tool = tool; - } + public MapView(MapEditor editor){ + this.editor = editor; - public EditorTool getTool() { - return tool; - } - - public void clearStack(){ - stack.clear(); - //TODO clear und obuffer - } - - public OperationStack getStack() { - return stack; - } - - public void setGrid(boolean grid) { - this.grid = grid; - } - - public boolean isGrid() { - return grid; - } - - public void undo(){ - if(stack.canUndo()){ - stack.undo(editor); - } - } - - public void redo(){ - if(stack.canRedo()){ - stack.redo(editor); - } - } - - public void addTileOp(TileOperation t){ - op.addOperation(t); - } - - public boolean checkForDuplicates(short x, short y){ - return op.checkDuplicate(x, y); - } - - public MapView(MapEditor editor){ - this.editor = editor; - - for(int i = 0; i < MapEditor.brushSizes.length; i ++){ + for(int i = 0; i < MapEditor.brushSizes.length; i++){ float size = MapEditor.brushSizes[i]; brushPolygons[i] = Geometry.pixelCircle(size, (index, x, y) -> Vector2.dst(x, y, index, index) <= index - 0.5f); } - - Inputs.addProcessor(0, new GestureDetector(20, 0.5f, 2, 0.15f, this)); - setTouchable(Touchable.enabled); - - addListener(new InputListener(){ - @Override - public boolean mouseMoved (InputEvent event, float x, float y) { - mousex = x; - mousey = y; + Inputs.addProcessor(0, new GestureDetector(20, 0.5f, 2, 0.15f, this)); + setTouchable(Touchable.enabled); - return false; + addListener(new InputListener(){ + + @Override + public boolean mouseMoved(InputEvent event, float x, float y){ + mousex = x; + mousey = y; + + return false; } - - @Override - public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) { - if(pointer != 0){ - return false; - } - if(!mobile && button != Buttons.LEFT && button != Buttons.MIDDLE){ - return true; - } + @Override + public boolean touchDown(InputEvent event, float x, float y, int pointer, int button){ + if(pointer != 0){ + return false; + } - if(button == Buttons.MIDDLE){ - lastTool = tool; - tool = EditorTool.zoom; - } + if(!mobile && button != Buttons.LEFT && button != Buttons.MIDDLE){ + return true; + } - mousex = x; - mousey = y; + if(button == Buttons.MIDDLE){ + lastTool = tool; + tool = EditorTool.zoom; + } - op = new DrawOperation(editor.getMap()); + mousex = x; + mousey = y; - updated = false; + op = new DrawOperation(editor.getMap()); - GridPoint2 p = project(x, y); - lastx = p.x; - lasty = p.y; - startx = p.x; - starty = p.y; - tool.touched(editor, p.x, p.y); - - if(tool.edit){ - updated = true; - ui.editor.resetSaved(); - } + updated = false; - drawing = true; - return true; - } - - @Override - public void touchUp (InputEvent event, float x, float y, int pointer, int button) { - if(!mobile && button != Buttons.LEFT && button != Buttons.MIDDLE){ - return; - } + GridPoint2 p = project(x, y); + lastx = p.x; + lasty = p.y; + startx = p.x; + starty = p.y; + tool.touched(editor, p.x, p.y); - drawing = false; + if(tool.edit){ + updated = true; + ui.editor.resetSaved(); + } - GridPoint2 p = project(x, y); + drawing = true; + return true; + } - if(tool == EditorTool.line){ - ui.editor.resetSaved(); - Array points = br.line(startx, starty, p.x, p.y); - for(GridPoint2 point : points){ - editor.draw(point.x, point.y); - } - updated = true; - } + @Override + public void touchUp(InputEvent event, float x, float y, int pointer, int button){ + if(!mobile && button != Buttons.LEFT && button != Buttons.MIDDLE){ + return; + } - if(op != null && updated){ - if(!op.isEmpty()){ - stack.add(op); - } - op = null; - } + drawing = false; - if(lastTool != null){ - tool = lastTool; - lastTool = null; - } + GridPoint2 p = project(x, y); - } - - @Override - public void touchDragged (InputEvent event, float x, float y, int pointer) { - mousex = x; - mousey = y; + if(tool == EditorTool.line){ + ui.editor.resetSaved(); + Array points = br.line(startx, starty, p.x, p.y); + for(GridPoint2 point : points){ + editor.draw(point.x, point.y); + } + updated = true; + } - GridPoint2 p = project(x, y); - - if(drawing && tool.draggable){ - ui.editor.resetSaved(); - Array points = br.line(lastx, lasty, p.x, p.y); - for(GridPoint2 point : points){ - tool.touched(editor, point.x, point.y); - } - updated = true; - } - lastx = p.x; - lasty = p.y; - } - }); - } - - @Override - public void act(float delta){ - super.act(delta); + if(op != null && updated){ + if(!op.isEmpty()){ + stack.add(op); + } + op = null; + } - if(Core.scene.getKeyboardFocus() == null || !(Core.scene.getKeyboardFocus() instanceof TextField) && - !Inputs.keyDown(io.anuke.ucore.input.Input.CONTROL_LEFT)) { - float ax = Inputs.getAxis("move_x"); - float ay = Inputs.getAxis("move_y"); - offsetx -= ax * 15f / zoom; - offsety -= ay * 15f / zoom; - } + if(lastTool != null){ + tool = lastTool; + lastTool = null; + } - if(ui.editor.hasPane()) return; - - zoom += Inputs.scroll()/10f * zoom; - clampZoom(); - } - - private void clampZoom(){ - zoom = Mathf.clamp(zoom, 0.2f, 12f); - } - - private GridPoint2 project(float x, float y){ - float ratio = 1f / ((float)editor.getMap().width() / editor.getMap().height()); - float size = Math.min(width, height); - float sclwidth = size * zoom; - float sclheight = size * zoom * ratio; - x = (x - getWidth()/2 + sclwidth/2 - offsetx*zoom) / sclwidth * editor.getMap().width(); - y = (y - getHeight()/2 + sclheight/2 - offsety*zoom) / sclheight * editor.getMap().height(); + } - if(editor.getDrawBlock().size % 2 == 0 && tool != EditorTool.eraser){ - return Tmp.g1.set((int)(x - 0.5f), (int)(y - 0.5f)); - }else{ - return Tmp.g1.set((int)x, (int)y); - } - } + @Override + public void touchDragged(InputEvent event, float x, float y, int pointer){ + mousex = x; + mousey = y; - private Vector2 unproject(int x, int y){ - float ratio = 1f / ((float)editor.getMap().width() / editor.getMap().height()); - float size = Math.min(width, height); - float sclwidth = size * zoom; - float sclheight = size * zoom * ratio; - float px = ((float)x / editor.getMap().width()) * sclwidth + offsetx*zoom - sclwidth/2 + getWidth()/2; - float py = ((float)(y) / editor.getMap().height()) * sclheight - + offsety*zoom - sclheight/2 + getHeight()/2; - return vec.set(px, py); - } + GridPoint2 p = project(x, y); - @Override - public void draw(Batch batch, float alpha){ - float ratio = 1f / ((float)editor.getMap().width() / editor.getMap().height()); - float size = Math.min(width, height); - float sclwidth = size * zoom; - float sclheight = size * zoom * ratio; - float centerx = x + width/2 + offsetx * zoom; - float centery = y + height/2 + offsety * zoom; + if(drawing && tool.draggable){ + ui.editor.resetSaved(); + Array points = br.line(lastx, lasty, p.x, p.y); + for(GridPoint2 point : points){ + tool.touched(editor, point.x, point.y); + } + updated = true; + } + lastx = p.x; + lasty = p.y; + } + }); + } - image.setImageSize(editor.getMap().width(), editor.getMap().height()); - - batch.flush(); - boolean pop = ScissorStack.pushScissors(rect.set(x, y, width, height)); + public EditorTool getTool(){ + return tool; + } - Draw.color(Color.LIGHT_GRAY); - Lines.stroke(-2f); - Lines.rect(centerx - sclwidth/2 - 1, centery - sclheight/2 - 1, sclwidth + 2, sclheight + 2); - editor.renderer().draw(centerx - sclwidth/2, centery - sclheight/2, sclwidth, sclheight); - Draw.reset(); + public void setTool(EditorTool tool){ + this.tool = tool; + } - if(grid){ - Draw.color(Color.GRAY); - image.setBounds(centerx - sclwidth/2, centery - sclheight/2, sclwidth, sclheight); - image.draw(batch, alpha); - Draw.color(); - } + public void clearStack(){ + stack.clear(); + //TODO clear und obuffer + } + + public OperationStack getStack(){ + return stack; + } + + public boolean isGrid(){ + return grid; + } + + public void setGrid(boolean grid){ + this.grid = grid; + } + + public void undo(){ + if(stack.canUndo()){ + stack.undo(editor); + } + } + + public void redo(){ + if(stack.canRedo()){ + stack.redo(editor); + } + } + + public void addTileOp(TileOperation t){ + op.addOperation(t); + } + + public boolean checkForDuplicates(short x, short y){ + return op.checkDuplicate(x, y); + } + + @Override + public void act(float delta){ + super.act(delta); + + if(Core.scene.getKeyboardFocus() == null || !(Core.scene.getKeyboardFocus() instanceof TextField) && + !Inputs.keyDown(io.anuke.ucore.input.Input.CONTROL_LEFT)){ + float ax = Inputs.getAxis("move_x"); + float ay = Inputs.getAxis("move_y"); + offsetx -= ax * 15f / zoom; + offsety -= ay * 15f / zoom; + } + + if(ui.editor.hasPane()) return; + + zoom += Inputs.scroll() / 10f * zoom; + clampZoom(); + } + + private void clampZoom(){ + zoom = Mathf.clamp(zoom, 0.2f, 12f); + } + + private GridPoint2 project(float x, float y){ + float ratio = 1f / ((float) editor.getMap().width() / editor.getMap().height()); + float size = Math.min(width, height); + float sclwidth = size * zoom; + float sclheight = size * zoom * ratio; + x = (x - getWidth() / 2 + sclwidth / 2 - offsetx * zoom) / sclwidth * editor.getMap().width(); + y = (y - getHeight() / 2 + sclheight / 2 - offsety * zoom) / sclheight * editor.getMap().height(); + + if(editor.getDrawBlock().size % 2 == 0 && tool != EditorTool.eraser){ + return Tmp.g1.set((int) (x - 0.5f), (int) (y - 0.5f)); + }else{ + return Tmp.g1.set((int) x, (int) y); + } + } + + private Vector2 unproject(int x, int y){ + float ratio = 1f / ((float) editor.getMap().width() / editor.getMap().height()); + float size = Math.min(width, height); + float sclwidth = size * zoom; + float sclheight = size * zoom * ratio; + float px = ((float) x / editor.getMap().width()) * sclwidth + offsetx * zoom - sclwidth / 2 + getWidth() / 2; + float py = ((float) (y) / editor.getMap().height()) * sclheight + + offsety * zoom - sclheight / 2 + getHeight() / 2; + return vec.set(px, py); + } + + @Override + public void draw(Batch batch, float alpha){ + float ratio = 1f / ((float) editor.getMap().width() / editor.getMap().height()); + float size = Math.min(width, height); + float sclwidth = size * zoom; + float sclheight = size * zoom * ratio; + float centerx = x + width / 2 + offsetx * zoom; + float centery = y + height / 2 + offsety * zoom; + + image.setImageSize(editor.getMap().width(), editor.getMap().height()); + + batch.flush(); + boolean pop = ScissorStack.pushScissors(rect.set(x, y, width, height)); + + Draw.color(Color.LIGHT_GRAY); + Lines.stroke(-2f); + Lines.rect(centerx - sclwidth / 2 - 1, centery - sclheight / 2 - 1, sclwidth + 2, sclheight + 2); + editor.renderer().draw(centerx - sclwidth / 2, centery - sclheight / 2, sclwidth, sclheight); + Draw.reset(); + + if(grid){ + Draw.color(Color.GRAY); + image.setBounds(centerx - sclwidth / 2, centery - sclheight / 2, sclwidth, sclheight); + image.draw(batch, alpha); + Draw.color(); + } int index = 0; - for(int i = 0; i < MapEditor.brushSizes.length; i ++){ + for(int i = 0; i < MapEditor.brushSizes.length; i++){ if(editor.getBrushSize() == MapEditor.brushSizes[i]){ index = i; break; @@ -297,102 +297,102 @@ public class MapView extends Element implements GestureListener{ //todo is it really math.max? float scaling = zoom * Math.min(width, height) / Math.max(editor.getMap().width(), editor.getMap().height()); - Draw.color(Palette.accent); - Lines.stroke(Unit.dp.scl(1f * zoom)); + Draw.color(Palette.accent); + Lines.stroke(Unit.dp.scl(1f * zoom)); - if(!editor.getDrawBlock().isMultiblock() || tool == EditorTool.eraser) { - if (tool == EditorTool.line && drawing) { - Vector2 v1 = unproject(startx, starty).add(x, y); - float sx = v1.x, sy = v1.y; - Vector2 v2 = unproject(lastx, lasty).add(x, y); + if(!editor.getDrawBlock().isMultiblock() || tool == EditorTool.eraser){ + if(tool == EditorTool.line && drawing){ + Vector2 v1 = unproject(startx, starty).add(x, y); + float sx = v1.x, sy = v1.y; + Vector2 v2 = unproject(lastx, lasty).add(x, y); - Lines.poly(brushPolygons[index], sx, sy, scaling); - Lines.poly(brushPolygons[index], v2.x, v2.y, scaling); - } + Lines.poly(brushPolygons[index], sx, sy, scaling); + Lines.poly(brushPolygons[index], v2.x, v2.y, scaling); + } - if (tool.edit && (!mobile || drawing)) { - GridPoint2 p = project(mousex, mousey); - Vector2 v = unproject(p.x, p.y).add(x, y); - Lines.poly(brushPolygons[index], v.x, v.y, scaling); - } - }else{ - if((tool.edit || tool == EditorTool.line) && (!mobile || drawing)){ - GridPoint2 p = project(mousex, mousey); - Vector2 v = unproject(p.x, p.y).add(x, y); - float offset = (editor.getDrawBlock().size % 2 == 0 ? scaling/2f : 0f); - Lines.square( - v.x + scaling/2f + offset, - v.y + scaling/2f + offset, - scaling * editor.getDrawBlock().size /2f); - } - } + if(tool.edit && (!mobile || drawing)){ + GridPoint2 p = project(mousex, mousey); + Vector2 v = unproject(p.x, p.y).add(x, y); + Lines.poly(brushPolygons[index], v.x, v.y, scaling); + } + }else{ + if((tool.edit || tool == EditorTool.line) && (!mobile || drawing)){ + GridPoint2 p = project(mousex, mousey); + Vector2 v = unproject(p.x, p.y).add(x, y); + float offset = (editor.getDrawBlock().size % 2 == 0 ? scaling / 2f : 0f); + Lines.square( + v.x + scaling / 2f + offset, + v.y + scaling / 2f + offset, + scaling * editor.getDrawBlock().size / 2f); + } + } - batch.flush(); - - if(pop) ScissorStack.popScissors(); - - Draw.color(Palette.accent); - Lines.stroke(Unit.dp.scl(3f)); - Lines.rect(x, y, width, height); - Draw.reset(); - } - - private boolean active(){ - return Core.scene.getKeyboardFocus() != null - && Core.scene.getKeyboardFocus().isDescendantOf(ui.editor) - && ui.editor.isShown() && tool == EditorTool.zoom && - Core.scene.hit(Graphics.mouse().x, Graphics.mouse().y, true) == this; - } + batch.flush(); - @Override - public boolean touchDown(float x, float y, int pointer, int button){ - return false; - } + if(pop) ScissorStack.popScissors(); - @Override - public boolean tap(float x, float y, int count, int button){ - return false; - } + Draw.color(Palette.accent); + Lines.stroke(Unit.dp.scl(3f)); + Lines.rect(x, y, width, height); + Draw.reset(); + } - @Override - public boolean longPress(float x, float y){ - return false; - } + private boolean active(){ + return Core.scene.getKeyboardFocus() != null + && Core.scene.getKeyboardFocus().isDescendantOf(ui.editor) + && ui.editor.isShown() && tool == EditorTool.zoom && + Core.scene.hit(Graphics.mouse().x, Graphics.mouse().y, true) == this; + } - @Override - public boolean fling(float velocityX, float velocityY, int button){ - return false; - } + @Override + public boolean touchDown(float x, float y, int pointer, int button){ + return false; + } - @Override - public boolean pan(float x, float y, float deltaX, float deltaY){ - if(!active()) return false; - offsetx += deltaX / zoom; - offsety -= deltaY / zoom; - return false; - } + @Override + public boolean tap(float x, float y, int count, int button){ + return false; + } - @Override - public boolean panStop(float x, float y, int pointer, int button){ - return false; - } + @Override + public boolean longPress(float x, float y){ + return false; + } - @Override - public boolean zoom(float initialDistance, float distance){ - if(!active()) return false; - float nzoom = distance - initialDistance; - zoom += nzoom / 10000f / Unit.dp.scl(1f) * zoom; - clampZoom(); - return false; - } + @Override + public boolean fling(float velocityX, float velocityY, int button){ + return false; + } - @Override - public boolean pinch(Vector2 initialPointer1, Vector2 initialPointer2, Vector2 pointer1, Vector2 pointer2){ - return false; - } + @Override + public boolean pan(float x, float y, float deltaX, float deltaY){ + if(!active()) return false; + offsetx += deltaX / zoom; + offsety -= deltaY / zoom; + return false; + } - @Override - public void pinchStop(){ + @Override + public boolean panStop(float x, float y, int pointer, int button){ + return false; + } - } + @Override + public boolean zoom(float initialDistance, float distance){ + if(!active()) return false; + float nzoom = distance - initialDistance; + zoom += nzoom / 10000f / Unit.dp.scl(1f) * zoom; + clampZoom(); + return false; + } + + @Override + public boolean pinch(Vector2 initialPointer1, Vector2 initialPointer2, Vector2 pointer1, Vector2 pointer2){ + return false; + } + + @Override + public void pinchStop(){ + + } } diff --git a/core/src/io/anuke/mindustry/editor/OperationStack.java b/core/src/io/anuke/mindustry/editor/OperationStack.java index 5c739608ee..402da3cf5e 100755 --- a/core/src/io/anuke/mindustry/editor/OperationStack.java +++ b/core/src/io/anuke/mindustry/editor/OperationStack.java @@ -3,49 +3,49 @@ package io.anuke.mindustry.editor; import com.badlogic.gdx.utils.Array; public class OperationStack{ - private final static int maxSize = 10; - private Array stack = new Array<>(); - private int index = 0; - - public OperationStack(){ - - } - - public void clear(){ - stack.clear(); - index = 0; - } - - public void add(DrawOperation action){ - stack.truncate(stack.size + index); - index = 0; - stack.add(action); + private final static int maxSize = 10; + private Array stack = new Array<>(); + private int index = 0; - if(stack.size > maxSize){ + public OperationStack(){ + + } + + public void clear(){ + stack.clear(); + index = 0; + } + + public void add(DrawOperation action){ + stack.truncate(stack.size + index); + index = 0; + stack.add(action); + + if(stack.size > maxSize){ stack.removeIndex(0); } - } - - public boolean canUndo(){ - return !(stack.size - 1 + index < 0); - } - - public boolean canRedo(){ - return !(index > -1 || stack.size + index < 0); - } + } - public void undo(MapEditor editor){ - if(!canUndo()) return; + public boolean canUndo(){ + return !(stack.size - 1 + index < 0); + } - stack.get(stack.size - 1 + index).undo(editor); - index --; - } + public boolean canRedo(){ + return !(index > -1 || stack.size + index < 0); + } - public void redo(MapEditor editor){ - if(!canRedo()) return; - - index ++; - stack.get(stack.size - 1 + index).redo(editor); - - } + public void undo(MapEditor editor){ + if(!canUndo()) return; + + stack.get(stack.size - 1 + index).undo(editor); + index--; + } + + public void redo(MapEditor editor){ + if(!canRedo()) return; + + index++; + stack.get(stack.size - 1 + index).redo(editor); + + } } diff --git a/core/src/io/anuke/mindustry/entities/Damage.java b/core/src/io/anuke/mindustry/entities/Damage.java index 6ba29a3de3..0c7ac56454 100644 --- a/core/src/io/anuke/mindustry/entities/Damage.java +++ b/core/src/io/anuke/mindustry/entities/Damage.java @@ -24,84 +24,90 @@ import io.anuke.ucore.util.Translator; import static io.anuke.mindustry.Vars.*; -/**Utility class for damaging in an area.*/ -public class Damage { - private static Rectangle rect = new Rectangle(); - private static Rectangle hitrect = new Rectangle(); - private static Translator tr = new Translator(); +/** + * Utility class for damaging in an area. + */ +public class Damage{ + private static Rectangle rect = new Rectangle(); + private static Rectangle hitrect = new Rectangle(); + private static Translator tr = new Translator(); - /**Creates a dynamic explosion based on specified parameters.*/ - public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, Color color){ - for(int i = 0; i < Mathf.clamp(power / 20, 0, 6); i ++){ - int branches = 5 + Mathf.clamp((int)(power/30), 1, 20); - Timers.run(i*2f + Mathf.random(4f), () -> Lightning.create(Team.none, Fx.none, Palette.power, 3, - x, y, Mathf.random(360f), branches + Mathf.range(2))); - } + /** + * Creates a dynamic explosion based on specified parameters. + */ + public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, Color color){ + for(int i = 0; i < Mathf.clamp(power / 20, 0, 6); i++){ + int branches = 5 + Mathf.clamp((int) (power / 30), 1, 20); + Timers.run(i * 2f + Mathf.random(4f), () -> Lightning.create(Team.none, Fx.none, Palette.power, 3, + x, y, Mathf.random(360f), branches + Mathf.range(2))); + } - for(int i = 0; i < Mathf.clamp(flammability / 4, 0, 30); i ++){ - Timers.run(i/2, () -> CallEntity.createBullet(TurretBullets.fireball, x, y, Mathf.random(360f))); - } + for(int i = 0; i < Mathf.clamp(flammability / 4, 0, 30); i++){ + Timers.run(i / 2, () -> CallEntity.createBullet(TurretBullets.fireball, x, y, Mathf.random(360f))); + } - int waves = Mathf.clamp((int)(explosiveness / 4), 0, 30); + int waves = Mathf.clamp((int) (explosiveness / 4), 0, 30); - for(int i = 0; i < waves; i ++){ - int f = i; - Timers.run(i*2f, () -> { - Damage.damage(x, y, Mathf.clamp(radius + explosiveness, 0, 50f) * ((f + 1f)/waves), explosiveness/2f); - Effects.effect(ExplosionFx.blockExplosionSmoke, x + Mathf.range(radius), y + Mathf.range(radius)); - }); - } + for(int i = 0; i < waves; i++){ + int f = i; + Timers.run(i * 2f, () -> { + Damage.damage(x, y, Mathf.clamp(radius + explosiveness, 0, 50f) * ((f + 1f) / waves), explosiveness / 2f); + Effects.effect(ExplosionFx.blockExplosionSmoke, x + Mathf.range(radius), y + Mathf.range(radius)); + }); + } - if(explosiveness > 15f){ - Effects.effect(ExplosionFx.shockwave, x, y); - } + if(explosiveness > 15f){ + Effects.effect(ExplosionFx.shockwave, x, y); + } - if(explosiveness > 30f){ - Effects.effect(ExplosionFx.bigShockwave, x, y); - } + if(explosiveness > 30f){ + Effects.effect(ExplosionFx.bigShockwave, x, y); + } - float shake = Math.min(explosiveness/4f + 3f, 9f); - Effects.shake(shake, shake, x, y); - Effects.effect(ExplosionFx.blockExplosion, x, y); - } + float shake = Math.min(explosiveness / 4f + 3f, 9f); + Effects.shake(shake, shake, x, y); + Effects.effect(ExplosionFx.blockExplosion, x, y); + } - public static void createIncend(float x, float y, float range, int amount){ - for (int i = 0; i < amount; i++) { - float cx = x + Mathf.range(range); - float cy = y + Mathf.range(range); - Tile tile = world.tileWorld(cx, cy); - if(tile != null){ - Fire.create(tile); - } - } - } + public static void createIncend(float x, float y, float range, int amount){ + for(int i = 0; i < amount; i++){ + float cx = x + Mathf.range(range); + float cy = y + Mathf.range(range); + Tile tile = world.tileWorld(cx, cy); + if(tile != null){ + Fire.create(tile); + } + } + } - /**Damages entities in a line. - * Only enemies of the specified team are damaged.*/ - public static void collideLine(SolidEntity hitter, Team team, Effect effect, float x, float y, float angle, float length){ - tr.trns(angle, length); - rect.setPosition(x, y).setSize(tr.x, tr.y); - float x2 = tr.x + x, y2 = tr.y + y; + /** + * Damages entities in a line. + * Only enemies of the specified team are damaged. + */ + public static void collideLine(SolidEntity hitter, Team team, Effect effect, float x, float y, float angle, float length){ + tr.trns(angle, length); + rect.setPosition(x, y).setSize(tr.x, tr.y); + float x2 = tr.x + x, y2 = tr.y + y; - if(rect.width < 0){ - rect.x += rect.width; - rect.width *= -1; - } + if(rect.width < 0){ + rect.x += rect.width; + rect.width *= -1; + } - if(rect.height < 0){ - rect.y += rect.height; - rect.height *= -1; - } + if(rect.height < 0){ + rect.y += rect.height; + rect.height *= -1; + } - float expand = 3f; + float expand = 3f; - rect.y -= expand; - rect.x -= expand; - rect.width += expand*2; - rect.height += expand*2; + rect.y -= expand; + rect.x -= expand; + rect.width += expand * 2; + rect.height += expand * 2; Consumer cons = e -> { - e.getHitbox(hitrect); + e.getHitbox(hitrect); Rectangle other = hitrect; other.y -= expand; other.x -= expand; @@ -110,79 +116,85 @@ public class Damage { Vector2 vec = Physics.raycastRect(x, y, x2, y2, other); - if (vec != null) { + if(vec != null){ Effects.effect(effect, vec.x, vec.y); e.collision(hitter, vec.x, vec.y); hitter.collision(e, vec.x, vec.y); } }; - Units.getNearbyEnemies(team, rect, cons); - } + Units.getNearbyEnemies(team, rect, cons); + } - /**Damages all entities and blocks in a radius that are enemies of the team.*/ - public static void damageUnits(Team team, float x, float y, float size, float damage, Predicate predicate, Consumer acceptor) { - Consumer cons = entity -> { - if(!predicate.test(entity)) return; + /** + * Damages all entities and blocks in a radius that are enemies of the team. + */ + public static void damageUnits(Team team, float x, float y, float size, float damage, Predicate predicate, Consumer acceptor){ + Consumer cons = entity -> { + if(!predicate.test(entity)) return; - entity.getHitbox(hitrect); - if (!hitrect.overlaps(rect)) { - return; - } - entity.damage(damage); - acceptor.accept(entity); - }; + entity.getHitbox(hitrect); + if(!hitrect.overlaps(rect)){ + return; + } + entity.damage(damage); + acceptor.accept(entity); + }; - rect.setSize(size * 2).setCenter(x, y); - if (team != null) { - Units.getNearbyEnemies(team, rect, cons); - } else { - Units.getNearby(rect, cons); - } - } + rect.setSize(size * 2).setCenter(x, y); + if(team != null){ + Units.getNearbyEnemies(team, rect, cons); + }else{ + Units.getNearby(rect, cons); + } + } - /**Damages everything in a radius.*/ - public static void damage(float x, float y, float radius, float damage){ - damage(null, x, y, radius, damage); - } + /** + * Damages everything in a radius. + */ + public static void damage(float x, float y, float radius, float damage){ + damage(null, x, y, radius, damage); + } - /**Damages all entities and blocks in a radius that are enemies of the team.*/ - public static void damage(Team team, float x, float y, float radius, float damage){ - Consumer cons = entity -> { - if(entity.team == team || entity.distanceTo(x, y) > radius){ - return; - } - float amount = calculateDamage(x, y, entity.x, entity.y, radius, damage); - entity.damage(amount); - //TODO better velocity displacement - float dst = tr.set(entity.x - x, entity.y - y).len(); - entity.getVelocity().add(tr.setLength((1f-dst/radius) * 2f)); - }; + /** + * Damages all entities and blocks in a radius that are enemies of the team. + */ + public static void damage(Team team, float x, float y, float radius, float damage){ + Consumer cons = entity -> { + if(entity.team == team || entity.distanceTo(x, y) > radius){ + return; + } + float amount = calculateDamage(x, y, entity.x, entity.y, radius, damage); + entity.damage(amount); + //TODO better velocity displacement + float dst = tr.set(entity.x - x, entity.y - y).len(); + entity.getVelocity().add(tr.setLength((1f - dst / radius) * 2f)); + }; - rect.setSize(radius *2).setCenter(x, y); - if(team != null) { - Units.getNearbyEnemies(team, rect, cons); - }else{ - Units.getNearby(rect, cons); - } + rect.setSize(radius * 2).setCenter(x, y); + if(team != null){ + Units.getNearbyEnemies(team, rect, cons); + }else{ + Units.getNearby(rect, cons); + } - int trad = (int)(radius / tilesize); - for(int dx = -trad; dx <= trad; dx ++){ - for(int dy= -trad; dy <= trad; dy ++){ - Tile tile = world.tile(Mathf.scl2(x, tilesize) + dx, Mathf.scl2(y, tilesize) + dy); - if(tile != null && tile.entity != null && (team == null || state.teams.areEnemies(team, tile.getTeam())) && Vector2.dst(dx, dy, 0, 0) <= trad){ - float amount = calculateDamage(x, y, tile.worldx(), tile.worldy(), radius, damage); - tile.entity.damage(amount); - } - } - } + int trad = (int) (radius / tilesize); + for(int dx = -trad; dx <= trad; dx++){ + for(int dy = -trad; dy <= trad; dy++){ + Tile tile = world.tile(Mathf.scl2(x, tilesize) + dx, Mathf.scl2(y, tilesize) + dy); + if(tile != null && tile.entity != null && (team == null || state.teams.areEnemies(team, tile.getTeam())) && Vector2.dst(dx, dy, 0, 0) <= trad){ + float amount = calculateDamage(x, y, tile.worldx(), tile.worldy(), radius, damage); + tile.entity.damage(amount); + } + } + } - } - - private static float calculateDamage(float x, float y, float tx, float ty, float radius, float damage){ - float dist = Vector2.dst(x, y, tx, ty); - float falloff = 0.4f; - float scaled = Mathf.lerp(1f - dist/radius, 1f, falloff); - return damage * scaled; - } + } + + private static float calculateDamage(float x, float y, float tx, float ty, float radius, float damage){ + float dist = Vector2.dst(x, y, tx, ty); + float falloff = 0.4f; + float scaled = Mathf.lerp(1f - dist / radius, 1f, falloff); + return damage * scaled; + } } diff --git a/core/src/io/anuke/mindustry/entities/Player.java b/core/src/io/anuke/mindustry/entities/Player.java index 064377e7d3..d5b3b34a05 100644 --- a/core/src/io/anuke/mindustry/entities/Player.java +++ b/core/src/io/anuke/mindustry/entities/Player.java @@ -26,7 +26,10 @@ import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.Floor; import io.anuke.mindustry.world.blocks.storage.CoreBlock.CoreEntity; import io.anuke.mindustry.world.blocks.units.MechFactory; -import io.anuke.ucore.core.*; +import io.anuke.ucore.core.Core; +import io.anuke.ucore.core.Graphics; +import io.anuke.ucore.core.Inputs; +import io.anuke.ucore.core.Timers; import io.anuke.ucore.entities.EntityGroup; import io.anuke.ucore.entities.trait.SolidTrait; import io.anuke.ucore.graphics.Draw; @@ -39,87 +42,109 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.*; -public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTrait { - private static final int timerShootLeft = 0; - private static final int timerShootRight = 1; +public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTrait{ + public static final int timerSync = 2; + private static final int timerShootLeft = 0; + private static final int timerShootRight = 1; - public static final int timerSync = 2; + //region instance variables, constructor + public float baseRotation; - //region instance variables, constructor + public float pointerX, pointerY; + public String name = "name"; + public String uuid, usid; + public boolean isAdmin, isTransferring, isShooting, isBoosting, isMobile; + public float boostHeat; + public Color color = new Color(); + public Mech mech; + public int spawner; - public float baseRotation; + public NetConnection con; + public int playerIndex = 0; + public boolean isLocal = false; + public Timer timer = new Timer(4); + public TargetTrait target; + public TargetTrait moveTarget; - public float pointerX, pointerY; - public String name = "name"; - public String uuid, usid; - public boolean isAdmin, isTransferring, isShooting, isBoosting, isMobile; - public float boostHeat; - public Color color = new Color(); - public Mech mech; - public int spawner; + private float walktime; + private Queue placeQueue = new ThreadQueue<>(); + private Tile mining; + private CarriableTrait carrying; + private Trail trail = new Trail(12); + private Vector2 movement = new Vector2(); + private boolean moved; - public NetConnection con; - public int playerIndex = 0; - public boolean isLocal = false; - public Timer timer = new Timer(4); - public TargetTrait target; - public TargetTrait moveTarget; + public Player(){ + hitbox.setSize(5); + hitboxTile.setSize(4f); + } - private float walktime; - private Queue placeQueue = new ThreadQueue<>(); - private Tile mining; - private CarriableTrait carrying; - private Trail trail = new Trail(12); - private Vector2 movement = new Vector2(); - private boolean moved; - - public Player(){ - hitbox.setSize(5); - hitboxTile.setSize(4f); - } + //endregion - //endregion + //region unit and event overrides, utility methods - //region unit and event overrides, utility methods + @Remote(in = In.entities, targets = Loc.server, called = Loc.server) + public static void onPlayerDamage(Player player, float amount){ + if(player == null) return; + player.hitTime = hitDuration; + player.health -= amount; + } - @Override - public Timer getTimer() { - return timer; - } + @Remote(in = In.entities, targets = Loc.server, called = Loc.server) + public static void onPlayerDeath(Player player){ + if(player == null) return; - @Override - public int getShootTimer(boolean left) { - return left ? timerShootLeft : timerShootRight; - } + player.dead = true; + player.placeQueue.clear(); - @Override - public Weapon getWeapon() { - return mech.weapon; - } + player.dropCarry(); - @Override - public float getMinePower() { - return mech.mineSpeed; - } + float explosiveness = 2f + (player.inventory.hasItem() ? player.inventory.getItem().item.explosiveness * player.inventory.getItem().amount : 0f); + float flammability = (player.inventory.hasItem() ? player.inventory.getItem().item.flammability * player.inventory.getItem().amount : 0f); + Damage.dynamicExplosion(player.x, player.y, flammability, explosiveness, 0f, player.getSize() / 2f, Palette.darkFlame); - @Override - public TextureRegion getIconRegion() { - return mech.iconRegion; - } + ScorchDecal.create(player.x, player.y); + player.onDeath(); + } - @Override - public int getItemCapacity() { - return mech.itemCapacity; - } + @Override + public Timer getTimer(){ + return timer; + } - @Override - public int getAmmoCapacity() { - return mech.ammoCapacity; - } + @Override + public int getShootTimer(boolean left){ + return left ? timerShootLeft : timerShootRight; + } - @Override - public void interpolate() { + @Override + public Weapon getWeapon(){ + return mech.weapon; + } + + @Override + public float getMinePower(){ + return mech.mineSpeed; + } + + @Override + public TextureRegion getIconRegion(){ + return mech.iconRegion; + } + + @Override + public int getItemCapacity(){ + return mech.itemCapacity; + } + + @Override + public int getAmmoCapacity(){ + return mech.ammoCapacity; + } + + @Override + public void interpolate(){ super.interpolate(); if(interpolator.values.length > 1){ @@ -131,579 +156,558 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra } } - @Override - public CarriableTrait getCarry() { - return carrying; - } - - @Override - public void setCarry(CarriableTrait unit) { - this.carrying = unit; - } - - @Override - public float getCarryWeight() { - return mech.carryWeight; - } - - @Override - public float getBuildPower(Tile tile) { - return mech.buildPower; - } - - @Override - public float maxHealth() { - return 200; - } - - @Override - public Tile getMineTile() { - return mining; - } - - @Override - public void setMineTile(Tile tile) { - this.mining = tile; - } - - @Override - public float getArmor() { - return mech.armor; - } - - @Override - public boolean acceptsAmmo(Item item) { - return mech.weapon.getAmmoType(item) != null && inventory.canAcceptAmmo(mech.weapon.getAmmoType(item)); - } - - @Override - public void added() { - baseRotation = 90f; - } - - @Override - public void addAmmo(Item item) { - inventory.addAmmo(mech.weapon.getAmmoType(item)); - } - - @Override - public float getMass(){ - return mech.mass; + @Override + public CarriableTrait getCarry(){ + return carrying; } @Override - public boolean isFlying(){ - return mech.flying || noclip || isCarried(); - } + public void setCarry(CarriableTrait unit){ + this.carrying = unit; + } - @Override - public float getSize() { - return 8; - } + @Override + public float getCarryWeight(){ + return mech.carryWeight; + } - @Override - public void damage(float amount){ - CallEntity.onPlayerDamage(this, calculateDamage(amount)); + @Override + public float getBuildPower(Tile tile){ + return mech.buildPower; + } - if(health <= 0 && !dead){ - CallEntity.onPlayerDeath(this); - } - } + @Override + public float maxHealth(){ + return 200; + } - @Override - public boolean collides(SolidTrait other) { - return super.collides(other) || other instanceof ItemDrop; - } + @Override + public Tile getMineTile(){ + return mining; + } - @Remote(in = In.entities, targets = Loc.server, called = Loc.server) - public static void onPlayerDamage(Player player, float amount){ - if(player == null) return; + @Override + public void setMineTile(Tile tile){ + this.mining = tile; + } - player.hitTime = hitDuration; - player.health -= amount; - } + @Override + public float getArmor(){ + return mech.armor; + } - @Remote(in = In.entities, targets = Loc.server, called = Loc.server) - public static void onPlayerDeath(Player player){ - if(player == null) return; + @Override + public boolean acceptsAmmo(Item item){ + return mech.weapon.getAmmoType(item) != null && inventory.canAcceptAmmo(mech.weapon.getAmmoType(item)); + } - player.dead = true; - player.placeQueue.clear(); + @Override + public void added(){ + baseRotation = 90f; + } - player.dropCarry(); + @Override + public void addAmmo(Item item){ + inventory.addAmmo(mech.weapon.getAmmoType(item)); + } - float explosiveness = 2f + (player.inventory.hasItem() ? player.inventory.getItem().item.explosiveness * player.inventory.getItem().amount : 0f); - float flammability = (player.inventory.hasItem() ? player.inventory.getItem().item.flammability * player.inventory.getItem().amount : 0f); - Damage.dynamicExplosion(player.x, player.y, flammability, explosiveness, 0f, player.getSize()/2f, Palette.darkFlame); + @Override + public float getMass(){ + return mech.mass; + } - ScorchDecal.create(player.x, player.y); - player.onDeath(); - } + @Override + public boolean isFlying(){ + return mech.flying || noclip || isCarried(); + } - @Override - public void set(float x, float y){ - this.x = x; - this.y = y; + @Override + public float getSize(){ + return 8; + } - if(isFlying() && isLocal){ - Core.camera.position.set(x, y, 0f); - } - } + @Override + public void damage(float amount){ + CallEntity.onPlayerDamage(this, calculateDamage(amount)); - @Override - public void removed() { + if(health <= 0 && !dead){ + CallEntity.onPlayerDeath(this); + } + } + + @Override + public boolean collides(SolidTrait other){ + return super.collides(other) || other instanceof ItemDrop; + } + + @Override + public void set(float x, float y){ + this.x = x; + this.y = y; + + if(isFlying() && isLocal){ + Core.camera.position.set(x, y, 0f); + } + } + + @Override + public void removed(){ dropCarryLocal(); TileEntity core = getClosestCore(); - if(core != null && ((CoreEntity)core).currentUnit == this){ - ((CoreEntity)core).currentUnit = null; - } - } + if(core != null && ((CoreEntity) core).currentUnit == this){ + ((CoreEntity) core).currentUnit = null; + } + } - @Override - public EntityGroup targetGroup() { - return playerGroup; - } + @Override + public EntityGroup targetGroup(){ + return playerGroup; + } - //endregion + //endregion - //region draw methods + //region draw methods - @Override - public float drawSize() { - return isLocal ? Float.MAX_VALUE : 40; - } + @Override + public float drawSize(){ + return isLocal ? Float.MAX_VALUE : 40; + } - @Override - public void drawShadow(){ - Draw.rect(mech.iconRegion, x + elevation*elevationScale, y - elevation*elevationScale, rotation - 90); - } + @Override + public void drawShadow(){ + Draw.rect(mech.iconRegion, x + elevation * elevationScale, y - elevation * elevationScale, rotation - 90); + } - @Override - public void draw(){ - if((debug && (!showPlayer || !showUI)) || dead) return; + @Override + public void draw(){ + if((debug && (!showPlayer || !showUI)) || dead) return; - if(!movement.isZero() && moved){ - walktime += Timers.delta() * movement.len()/0.7f * getFloorOn().speedMultiplier; - baseRotation = Mathf.slerpDelta(baseRotation, movement.angle(), 0.13f); - } + if(!movement.isZero() && moved){ + walktime += Timers.delta() * movement.len() / 0.7f * getFloorOn().speedMultiplier; + baseRotation = Mathf.slerpDelta(baseRotation, movement.angle(), 0.13f); + } - boostHeat = Mathf.lerpDelta(boostHeat, isBoosting && ((!movement.isZero() && moved) || !isLocal) ? 1f : 0f, 0.08f); + boostHeat = Mathf.lerpDelta(boostHeat, isBoosting && ((!movement.isZero() && moved) || !isLocal) ? 1f : 0f, 0.08f); boolean snap = snapCamera && isLocal; - float px = x, py =y; + float px = x, py = y; - if(snap){ - x = (int)(x + 0.0001f); - y = (int)(y + 0.0001f); - } - - float ft = Mathf.sin(walktime, 6f, 2f) * (1f-boostHeat); - - Floor floor = getFloorOn(); - - Draw.color(); - Draw.alpha(hitTime / hitDuration); - - if(!mech.flying) { - if(floor.isLiquid){ - Draw.tint(Color.WHITE, floor.liquidColor, 0.5f); - } - - float boostTrnsY = -boostHeat * 3f; - float boostTrnsX = boostHeat * 3f; - float boostAng = boostHeat*40f; - - for (int i : Mathf.signs) { - Draw.rect(mech.legRegion, - x + Angles.trnsx(baseRotation, ft * i + boostTrnsY, -boostTrnsX*i), - y + Angles.trnsy(baseRotation, ft * i + boostTrnsY, -boostTrnsX*i), - 12f * i, 12f - Mathf.clamp(ft * i, 0, 2), baseRotation - 90 + boostAng*i); - } - - Draw.rect(mech.baseRegion, x, y, baseRotation- 90); - } - - if(floor.isLiquid) { - Draw.tint(Color.WHITE, floor.liquidColor, drownTime * 0.4f); - }else { - Draw.tint(Color.WHITE); - } - - Draw.rect(mech.region, x, y, rotation -90); - - for (int i : Mathf.signs) { - float tra = rotation - 90, trY = - mech.weapon.getRecoil(this, i > 0) + mech.weaponOffsetY; - float w = i > 0 ? -12 : 12; - Draw.rect(mech.weapon.equipRegion, - x + Angles.trnsx(tra, mech.weaponOffsetX * i, trY), - y + Angles.trnsy(tra, mech.weaponOffsetX * i, trY), w, 12, rotation - 90); - } - - float backTrns = 4f, itemSize = 5f; - if(inventory.hasItem()){ - ItemStack stack = inventory.getItem(); - int stored = Mathf.clamp(stack.amount / 6, 1, 8); - - for(int i = 0; i < stored; i ++) { - float angT = i == 0 ? 0 : Mathf.randomSeedRange(i + 1, 60f); - float lenT = i == 0 ? 0 : Mathf.randomSeedRange(i + 2, 1f) - 1f; - Draw.rect(stack.item.region, - x + Angles.trnsx(rotation + 180f + angT, backTrns + lenT), - y + Angles.trnsy(rotation + 180f + angT, backTrns + lenT), - itemSize, itemSize, rotation); - } - } - - Draw.alpha(1f); - - x = px; - y = py; - } - - @Override - public void drawOver(){ - if(dead) return; - - if(!isShooting()) { - drawBuilding(this); + if(snap){ + x = (int) (x + 0.0001f); + y = (int) (y + 0.0001f); } - if(mech.flying || boostHeat > 0.001f){ - float wobblyness = 0.6f; - trail.update(x + Angles.trnsx(rotation + 180f, 5f) + Mathf.range(wobblyness), - y + Angles.trnsy(rotation + 180f, 5f) + Mathf.range(wobblyness)); - trail.draw(mech.trailColor, 5f * (isFlying() ? 1f : boostHeat)); - }else{ - trail.clear(); - } + float ft = Mathf.sin(walktime, 6f, 2f) * (1f - boostHeat); + + Floor floor = getFloorOn(); + + Draw.color(); + Draw.alpha(hitTime / hitDuration); + + if(!mech.flying){ + if(floor.isLiquid){ + Draw.tint(Color.WHITE, floor.liquidColor, 0.5f); + } + + float boostTrnsY = -boostHeat * 3f; + float boostTrnsX = boostHeat * 3f; + float boostAng = boostHeat * 40f; + + for(int i : Mathf.signs){ + Draw.rect(mech.legRegion, + x + Angles.trnsx(baseRotation, ft * i + boostTrnsY, -boostTrnsX * i), + y + Angles.trnsy(baseRotation, ft * i + boostTrnsY, -boostTrnsX * i), + 12f * i, 12f - Mathf.clamp(ft * i, 0, 2), baseRotation - 90 + boostAng * i); + } + + Draw.rect(mech.baseRegion, x, y, baseRotation - 90); + } + + if(floor.isLiquid){ + Draw.tint(Color.WHITE, floor.liquidColor, drownTime * 0.4f); + }else{ + Draw.tint(Color.WHITE); + } + + Draw.rect(mech.region, x, y, rotation - 90); + + for(int i : Mathf.signs){ + float tra = rotation - 90, trY = -mech.weapon.getRecoil(this, i > 0) + mech.weaponOffsetY; + float w = i > 0 ? -12 : 12; + Draw.rect(mech.weapon.equipRegion, + x + Angles.trnsx(tra, mech.weaponOffsetX * i, trY), + y + Angles.trnsy(tra, mech.weaponOffsetX * i, trY), w, 12, rotation - 90); + } + + float backTrns = 4f, itemSize = 5f; + if(inventory.hasItem()){ + ItemStack stack = inventory.getItem(); + int stored = Mathf.clamp(stack.amount / 6, 1, 8); + + for(int i = 0; i < stored; i++){ + float angT = i == 0 ? 0 : Mathf.randomSeedRange(i + 1, 60f); + float lenT = i == 0 ? 0 : Mathf.randomSeedRange(i + 2, 1f) - 1f; + Draw.rect(stack.item.region, + x + Angles.trnsx(rotation + 180f + angT, backTrns + lenT), + y + Angles.trnsy(rotation + 180f + angT, backTrns + lenT), + itemSize, itemSize, rotation); + } + } + + Draw.alpha(1f); + + x = px; + y = py; + } + + @Override + public void drawOver(){ + if(dead) return; + + if(!isShooting()){ + drawBuilding(this); + } + + if(mech.flying || boostHeat > 0.001f){ + float wobblyness = 0.6f; + trail.update(x + Angles.trnsx(rotation + 180f, 5f) + Mathf.range(wobblyness), + y + Angles.trnsy(rotation + 180f, 5f) + Mathf.range(wobblyness)); + trail.draw(mech.trailColor, 5f * (isFlying() ? 1f : boostHeat)); + }else{ + trail.clear(); + } } public void drawName(){ - GlyphLayout layout = Pools.obtain(GlyphLayout.class); - - Draw.tscl(0.25f/2); - layout.setText(Core.font, name); - Draw.color(0f, 0f, 0f, 0.3f); - Draw.rect("blank", x, y + 8 - layout.height/2, layout.width + 2, layout.height + 2); - Draw.color(); - Draw.tcolor(color); - Draw.text(name, x, y + 8); - - if(isAdmin){ - Draw.color(color); - float s = 3f; - Draw.rect("icon-admin-small", x + layout.width/2f + 2 + 1, y + 7f, s, s); - } - - Draw.reset(); - Pools.free(layout); - Draw.tscl(fontScale); - } - - /**Draw all current build requests. Does not draw the beam effect, only the positions.*/ - public void drawBuildRequests(){ - synchronized (getPlaceQueue()) { - for (BuildRequest request : getPlaceQueue()) { - - if (request.remove) { - Block block = world.tile(request.x, request.y).target().block(); - - //draw removal request - Draw.color(Palette.remove); - - Lines.stroke((1f - request.progress)); - - Lines.poly(request.x * tilesize + block.offset(), - request.y * tilesize + block.offset(), - 4, block.size * tilesize / 2f, 45 + 15); - } else { - //draw place request - Draw.color(Palette.accent); - - Lines.stroke((1f - request.progress)); - - Lines.poly(request.x * tilesize + request.recipe.result.offset(), - request.y * tilesize + request.recipe.result.offset(), - 4, request.recipe.result.size * tilesize / 2f, 45 + 15); - } - } - - Draw.reset(); - } - } - - //endregion - - //region update methods - - @Override - public void update(){ - hitTime = Math.max(0f, hitTime - Timers.delta()); - - if(isDead()){ - isBoosting = false; - boostHeat = 0f; - updateRespawning(); - return; - }else{ - spawner = -1; - } - - if(!isLocal){ - interpolate(); - updateBuilding(this); //building happens even with non-locals - status.update(this); //status effect updating also happens with non locals for effect purposes - - if(getCarrier() != null){ - x = getCarrier().getX(); - y = getCarrier().getY(); - } - - if(Net.server()){ - updateShooting(); //server simulates player shooting - } - return; - } - - if(mobile){ - updateFlying(); - }else{ - updateMech(); - } - - if(isLocal) { - avoidOthers(8f); - } - - if(!isShooting()) { - updateBuilding(this); - } - - x = Mathf.clamp(x, 0, world.width() * tilesize); - y = Mathf.clamp(y, 0, world.height() * tilesize); - } - - protected void updateMech(){ - Tile tile = world.tileWorld(x, y); - - //if player is in solid block - if(!mech.flying && tile != null && tile.solid() && !noclip) { - damage(health + 1); //die instantly - } - - if(ui.chatfrag.chatOpen()) return; - - float speed = isBoosting && !mech.flying ? debug ? 5f : mech.boostSpeed : mech.speed; - //fraction of speed when at max load - float carrySlowdown = 0.7f; - - speed *= ((inventory.hasItem() ? Mathf.lerp(1f, carrySlowdown, (float)inventory.getItem().amount/inventory.capacity()) : 1f)); - - if(mech.flying){ - //prevent strafing backwards, have a penalty for doing so - float angDist = Angles.angleDist(rotation, velocity.angle()) / 180f; - float penalty = 0.2f; //when going 180 degrees backwards, reduce speed to 0.2x - speed *= Mathf.lerp(1f, penalty, angDist); - } - - //drop from carrier on key press - if(Inputs.keyTap("drop_unit")){ - if(!mech.flying) { - if (getCarrier() != null) { - CallEntity.dropSelf(this); - } - }else if(getCarry() != null){ - dropCarry(); - }else{ - Unit unit = Units.getClosest(team, x, y, 8f, - u -> !u.isFlying() && u.getMass() <= mech.carryWeight); - - if(unit != null){ - carry(unit); - } - } - } - - movement.setZero(); - - String section = control.input(playerIndex).section; - - float xa = Inputs.getAxis(section, "move_x"); - float ya = Inputs.getAxis(section, "move_y"); - - movement.y += ya*speed; - movement.x += xa*speed; - - Vector2 vec = Graphics.world(Vars.control.input(playerIndex).getMouseX(), - Vars.control.input(playerIndex).getMouseY()); - pointerX = vec.x; - pointerY = vec.y; - updateShooting(); - - movement.limit(speed * Timers.delta()); - - if(getCarrier() == null){ - velocity.add(movement); - float prex = x, prey = y; - updateVelocityStatus(mech.drag, 10f); - moved = distanceTo(prex, prey) > 0.01f; - }else{ - velocity.setZero(); - x = Mathf.lerpDelta(x, getCarrier().getX(), 0.1f); - y = Mathf.lerpDelta(y, getCarrier().getY(), 0.1f); - } - - if(!isShooting()){ - if(!movement.isZero()) { - rotation = Mathf.slerpDelta(rotation, movement.angle(), 0.13f); - } - }else{ - float angle = control.input(playerIndex).mouseAngle(x, y); - this.rotation = Mathf.slerpDelta(this.rotation, angle, 0.1f); - } - } - - protected void updateShooting(){ - if(isShooting()){ - mech.weapon.update(this, pointerX, pointerY); - } - } - - protected void updateFlying(){ - if(Units.invalidateTarget(target, this)){ - target = null; - } - - float targetX = Core.camera.position.x, targetY = Core.camera.position.y; - float attractDst = 15f; - - if(moveTarget != null && !moveTarget.isDead()){ - targetX = moveTarget.getX(); - targetY = moveTarget.getY(); - attractDst = 0f; - - if(distanceTo(moveTarget) < 2f){ - if(moveTarget instanceof CarriableTrait){ - carry((CarriableTrait) moveTarget); - }else if(moveTarget instanceof TileEntity && ((TileEntity) moveTarget).tile.block() instanceof MechFactory){ - Tile tile = ((TileEntity) moveTarget).tile; - tile.block().tapped(tile, this); - } - - moveTarget = null; - } - }else{ - moveTarget = null; - } - - movement.set(targetX - x, targetY - y).limit(mech.speed); - movement.setAngle(Mathf.slerp(movement.angle(), velocity.angle(), 0.05f)); - - if(distanceTo(targetX, targetY) < attractDst){ - movement.setZero(); - } - - velocity.add(movement); - - if(velocity.len() <= 0.2f){ - rotation += Mathf.sin(Timers.time() + id * 99, 10f, 1f); - }else{ - rotation = Mathf.slerpDelta(rotation, velocity.angle(), velocity.len()/10f); - } - - updateVelocityStatus(mech.drag, mech.maxSpeed); - - //hovering effect - x += Mathf.sin(Timers.time() + id * 999, 25f, 0.08f); - y += Mathf.cos(Timers.time() + id * 999, 25f, 0.08f); - - //update shooting if not building, not mining and there's ammo left - if(!isBuilding() && inventory.hasAmmo() && getMineTile() == null){ - - //autofire: mobile only! - if(mobile) { - - if (target == null) { - isShooting = false; - target = Units.getClosestTarget(team, x, y, inventory.getAmmoRange()); - } else if(target.isValid()){ - //rotate toward and shoot the target - rotation = Mathf.slerpDelta(rotation, angleTo(target), 0.2f); - - Vector2 intercept = - Predict.intercept(x, y, target.getX(), target.getY(), target.getVelocity().x - velocity.x, target.getVelocity().y - velocity.y, inventory.getAmmo().bullet.speed); - - pointerX = intercept.x; - pointerY = intercept.y; - - updateShooting(); - isShooting = true; - } - - }else if(isShooting()){ - Vector2 vec = Graphics.world(Vars.control.input(playerIndex).getMouseX(), - Vars.control.input(playerIndex).getMouseY()); - pointerX = vec.x; - pointerY = vec.y; - - updateShooting(); - } - } - } - - //endregion - - //region utility methods - - public void toggleTeam(){ - team = (team == Team.blue ? Team.red : Team.blue); - } - - /**Resets all values of the player.*/ - public void reset(){ - status.clear(); - team = Team.blue; - inventory.clear(); - placeQueue.clear(); - dead = true; - trail.clear(); - health = maxHealth(); - mech = (mobile ? Mechs.starterMobile : Mechs.starterDesktop); - placeQueue.clear(); - - add(); - } - - public boolean isShooting(){ - return isShooting && inventory.hasAmmo() && (!isBoosting || mech.flying); - } - - public void updateRespawning(){ - - if (spawner != -1 && world.tile(spawner) != null && world.tile(spawner).entity instanceof SpawnerTrait) { - ((SpawnerTrait) world.tile(spawner).entity).updateSpawning(this); - }else{ - CoreEntity entity = (CoreEntity)getClosestCore(); - if(entity != null){ - this.spawner = entity.tile.id(); - } - } - } - - public void beginRespawning(SpawnerTrait spawner){ - this.spawner = spawner.getTile().packedPosition(); - this.dead = true; - } - - @Override - public Queue getPlaceQueue(){ - return placeQueue; - } + GlyphLayout layout = Pools.obtain(GlyphLayout.class); + + Draw.tscl(0.25f / 2); + layout.setText(Core.font, name); + Draw.color(0f, 0f, 0f, 0.3f); + Draw.rect("blank", x, y + 8 - layout.height / 2, layout.width + 2, layout.height + 2); + Draw.color(); + Draw.tcolor(color); + Draw.text(name, x, y + 8); + + if(isAdmin){ + Draw.color(color); + float s = 3f; + Draw.rect("icon-admin-small", x + layout.width / 2f + 2 + 1, y + 7f, s, s); + } + + Draw.reset(); + Pools.free(layout); + Draw.tscl(fontScale); + } + + /** + * Draw all current build requests. Does not draw the beam effect, only the positions. + */ + public void drawBuildRequests(){ + synchronized(getPlaceQueue()){ + for(BuildRequest request : getPlaceQueue()){ + + if(request.remove){ + Block block = world.tile(request.x, request.y).target().block(); + + //draw removal request + Draw.color(Palette.remove); + + Lines.stroke((1f - request.progress)); + + Lines.poly(request.x * tilesize + block.offset(), + request.y * tilesize + block.offset(), + 4, block.size * tilesize / 2f, 45 + 15); + }else{ + //draw place request + Draw.color(Palette.accent); + + Lines.stroke((1f - request.progress)); + + Lines.poly(request.x * tilesize + request.recipe.result.offset(), + request.y * tilesize + request.recipe.result.offset(), + 4, request.recipe.result.size * tilesize / 2f, 45 + 15); + } + } + + Draw.reset(); + } + } + + //endregion + + //region update methods @Override - public String toString() { + public void update(){ + hitTime = Math.max(0f, hitTime - Timers.delta()); + + if(isDead()){ + isBoosting = false; + boostHeat = 0f; + updateRespawning(); + return; + }else{ + spawner = -1; + } + + if(!isLocal){ + interpolate(); + updateBuilding(this); //building happens even with non-locals + status.update(this); //status effect updating also happens with non locals for effect purposes + + if(getCarrier() != null){ + x = getCarrier().getX(); + y = getCarrier().getY(); + } + + if(Net.server()){ + updateShooting(); //server simulates player shooting + } + return; + } + + if(mobile){ + updateFlying(); + }else{ + updateMech(); + } + + if(isLocal){ + avoidOthers(8f); + } + + if(!isShooting()){ + updateBuilding(this); + } + + x = Mathf.clamp(x, 0, world.width() * tilesize); + y = Mathf.clamp(y, 0, world.height() * tilesize); + } + + protected void updateMech(){ + Tile tile = world.tileWorld(x, y); + + //if player is in solid block + if(!mech.flying && tile != null && tile.solid() && !noclip){ + damage(health + 1); //die instantly + } + + if(ui.chatfrag.chatOpen()) return; + + float speed = isBoosting && !mech.flying ? debug ? 5f : mech.boostSpeed : mech.speed; + //fraction of speed when at max load + float carrySlowdown = 0.7f; + + speed *= ((inventory.hasItem() ? Mathf.lerp(1f, carrySlowdown, (float) inventory.getItem().amount / inventory.capacity()) : 1f)); + + if(mech.flying){ + //prevent strafing backwards, have a penalty for doing so + float angDist = Angles.angleDist(rotation, velocity.angle()) / 180f; + float penalty = 0.2f; //when going 180 degrees backwards, reduce speed to 0.2x + speed *= Mathf.lerp(1f, penalty, angDist); + } + + //drop from carrier on key press + if(Inputs.keyTap("drop_unit")){ + if(!mech.flying){ + if(getCarrier() != null){ + CallEntity.dropSelf(this); + } + }else if(getCarry() != null){ + dropCarry(); + }else{ + Unit unit = Units.getClosest(team, x, y, 8f, + u -> !u.isFlying() && u.getMass() <= mech.carryWeight); + + if(unit != null){ + carry(unit); + } + } + } + + movement.setZero(); + + String section = control.input(playerIndex).section; + + float xa = Inputs.getAxis(section, "move_x"); + float ya = Inputs.getAxis(section, "move_y"); + + movement.y += ya * speed; + movement.x += xa * speed; + + Vector2 vec = Graphics.world(Vars.control.input(playerIndex).getMouseX(), + Vars.control.input(playerIndex).getMouseY()); + pointerX = vec.x; + pointerY = vec.y; + updateShooting(); + + movement.limit(speed * Timers.delta()); + + if(getCarrier() == null){ + velocity.add(movement); + float prex = x, prey = y; + updateVelocityStatus(mech.drag, 10f); + moved = distanceTo(prex, prey) > 0.01f; + }else{ + velocity.setZero(); + x = Mathf.lerpDelta(x, getCarrier().getX(), 0.1f); + y = Mathf.lerpDelta(y, getCarrier().getY(), 0.1f); + } + + if(!isShooting()){ + if(!movement.isZero()){ + rotation = Mathf.slerpDelta(rotation, movement.angle(), 0.13f); + } + }else{ + float angle = control.input(playerIndex).mouseAngle(x, y); + this.rotation = Mathf.slerpDelta(this.rotation, angle, 0.1f); + } + } + + protected void updateShooting(){ + if(isShooting()){ + mech.weapon.update(this, pointerX, pointerY); + } + } + + protected void updateFlying(){ + if(Units.invalidateTarget(target, this)){ + target = null; + } + + float targetX = Core.camera.position.x, targetY = Core.camera.position.y; + float attractDst = 15f; + + if(moveTarget != null && !moveTarget.isDead()){ + targetX = moveTarget.getX(); + targetY = moveTarget.getY(); + attractDst = 0f; + + if(distanceTo(moveTarget) < 2f){ + if(moveTarget instanceof CarriableTrait){ + carry((CarriableTrait) moveTarget); + }else if(moveTarget instanceof TileEntity && ((TileEntity) moveTarget).tile.block() instanceof MechFactory){ + Tile tile = ((TileEntity) moveTarget).tile; + tile.block().tapped(tile, this); + } + + moveTarget = null; + } + }else{ + moveTarget = null; + } + + movement.set(targetX - x, targetY - y).limit(mech.speed); + movement.setAngle(Mathf.slerp(movement.angle(), velocity.angle(), 0.05f)); + + if(distanceTo(targetX, targetY) < attractDst){ + movement.setZero(); + } + + velocity.add(movement); + + if(velocity.len() <= 0.2f){ + rotation += Mathf.sin(Timers.time() + id * 99, 10f, 1f); + }else{ + rotation = Mathf.slerpDelta(rotation, velocity.angle(), velocity.len() / 10f); + } + + updateVelocityStatus(mech.drag, mech.maxSpeed); + + //hovering effect + x += Mathf.sin(Timers.time() + id * 999, 25f, 0.08f); + y += Mathf.cos(Timers.time() + id * 999, 25f, 0.08f); + + //update shooting if not building, not mining and there's ammo left + if(!isBuilding() && inventory.hasAmmo() && getMineTile() == null){ + + //autofire: mobile only! + if(mobile){ + + if(target == null){ + isShooting = false; + target = Units.getClosestTarget(team, x, y, inventory.getAmmoRange()); + }else if(target.isValid()){ + //rotate toward and shoot the target + rotation = Mathf.slerpDelta(rotation, angleTo(target), 0.2f); + + Vector2 intercept = + Predict.intercept(x, y, target.getX(), target.getY(), target.getVelocity().x - velocity.x, target.getVelocity().y - velocity.y, inventory.getAmmo().bullet.speed); + + pointerX = intercept.x; + pointerY = intercept.y; + + updateShooting(); + isShooting = true; + } + + }else if(isShooting()){ + Vector2 vec = Graphics.world(Vars.control.input(playerIndex).getMouseX(), + Vars.control.input(playerIndex).getMouseY()); + pointerX = vec.x; + pointerY = vec.y; + + updateShooting(); + } + } + } + + //endregion + + //region utility methods + + public void toggleTeam(){ + team = (team == Team.blue ? Team.red : Team.blue); + } + + /** + * Resets all values of the player. + */ + public void reset(){ + status.clear(); + team = Team.blue; + inventory.clear(); + placeQueue.clear(); + dead = true; + trail.clear(); + health = maxHealth(); + mech = (mobile ? Mechs.starterMobile : Mechs.starterDesktop); + placeQueue.clear(); + + add(); + } + + public boolean isShooting(){ + return isShooting && inventory.hasAmmo() && (!isBoosting || mech.flying); + } + + public void updateRespawning(){ + + if(spawner != -1 && world.tile(spawner) != null && world.tile(spawner).entity instanceof SpawnerTrait){ + ((SpawnerTrait) world.tile(spawner).entity).updateSpawning(this); + }else{ + CoreEntity entity = (CoreEntity) getClosestCore(); + if(entity != null){ + this.spawner = entity.tile.id(); + } + } + } + + public void beginRespawning(SpawnerTrait spawner){ + this.spawner = spawner.getTile().packedPosition(); + this.dead = true; + } + + @Override + public Queue getPlaceQueue(){ + return placeQueue; + } + + @Override + public String toString(){ return "Player{" + id + ", mech=" + mech.name + ", local=" + isLocal + ", " + x + ", " + y + "}\n"; } @@ -711,84 +715,84 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra //region read and write methods - @Override - public void writeSave(DataOutput stream) throws IOException { - stream.writeBoolean(isLocal); + @Override + public void writeSave(DataOutput stream) throws IOException{ + stream.writeBoolean(isLocal); - if(isLocal){ - stream.writeByte(mech.id); - stream.writeByte(playerIndex); - super.writeSave(stream, false); - } - } + if(isLocal){ + stream.writeByte(mech.id); + stream.writeByte(playerIndex); + super.writeSave(stream, false); + } + } - @Override - public void readSave(DataInput stream) throws IOException { - boolean local = stream.readBoolean(); + @Override + public void readSave(DataInput stream) throws IOException{ + boolean local = stream.readBoolean(); - if(local && !headless){ - byte mechid = stream.readByte(); - int index = stream.readByte(); - players[index].readSaveSuper(stream); - players[index].mech = Upgrade.getByID(mechid); - players[index].dead = false; - }else if(local){ - byte mechid = stream.readByte(); - stream.readByte(); - readSaveSuper(stream); - mech = Upgrade.getByID(mechid); - dead = false; - } - } + if(local && !headless){ + byte mechid = stream.readByte(); + int index = stream.readByte(); + players[index].readSaveSuper(stream); + players[index].mech = Upgrade.getByID(mechid); + players[index].dead = false; + }else if(local){ + byte mechid = stream.readByte(); + stream.readByte(); + readSaveSuper(stream); + mech = Upgrade.getByID(mechid); + dead = false; + } + } - private void readSaveSuper(DataInput stream) throws IOException { - super.readSave(stream); + private void readSaveSuper(DataInput stream) throws IOException{ + super.readSave(stream); - add(); - } + add(); + } - @Override - public void write(DataOutput buffer) throws IOException { - super.writeSave(buffer, !isLocal); - buffer.writeUTF(name); //TODO writing strings is very inefficient - buffer.writeByte(Bits.toByte(isAdmin) | (Bits.toByte(dead) << 1) | (Bits.toByte(isBoosting) << 2)); - buffer.writeInt(Color.rgba8888(color)); - buffer.writeByte(mech.id); - buffer.writeInt(mining == null ? -1 : mining.packedPosition()); - buffer.writeInt(spawner); - buffer.writeShort((short)(baseRotation * 2)); + @Override + public void write(DataOutput buffer) throws IOException{ + super.writeSave(buffer, !isLocal); + buffer.writeUTF(name); //TODO writing strings is very inefficient + buffer.writeByte(Bits.toByte(isAdmin) | (Bits.toByte(dead) << 1) | (Bits.toByte(isBoosting) << 2)); + buffer.writeInt(Color.rgba8888(color)); + buffer.writeByte(mech.id); + buffer.writeInt(mining == null ? -1 : mining.packedPosition()); + buffer.writeInt(spawner); + buffer.writeShort((short) (baseRotation * 2)); - writeBuilding(buffer); - } + writeBuilding(buffer); + } - @Override - public void read(DataInput buffer, long time) throws IOException { - float lastx = x, lasty = y, lastrot = rotation; - super.readSave(buffer); - name = buffer.readUTF(); - byte bools = buffer.readByte(); - isAdmin = (bools & 1) != 0; - dead = (bools & 2) != 0; - boolean boosting = (bools & 4) != 0; - color.set(buffer.readInt()); - mech = Upgrade.getByID(buffer.readByte()); - int mine = buffer.readInt(); - spawner = buffer.readInt(); - float baseRotation = buffer.readShort()/2f; + @Override + public void read(DataInput buffer, long time) throws IOException{ + float lastx = x, lasty = y, lastrot = rotation; + super.readSave(buffer); + name = buffer.readUTF(); + byte bools = buffer.readByte(); + isAdmin = (bools & 1) != 0; + dead = (bools & 2) != 0; + boolean boosting = (bools & 4) != 0; + color.set(buffer.readInt()); + mech = Upgrade.getByID(buffer.readByte()); + int mine = buffer.readInt(); + spawner = buffer.readInt(); + float baseRotation = buffer.readShort() / 2f; - readBuilding(buffer, !isLocal); + readBuilding(buffer, !isLocal); - interpolator.read(lastx, lasty, x, y, time, rotation, baseRotation); - rotation = lastrot; + interpolator.read(lastx, lasty, x, y, time, rotation, baseRotation); + rotation = lastrot; - if(isLocal){ - x = lastx; - y = lasty; - }else{ - mining = world.tile(mine); - isBoosting = boosting; - } - } + if(isLocal){ + x = lastx; + y = lasty; + }else{ + mining = world.tile(mine); + isBoosting = boosting; + } + } - //endregion + //endregion } diff --git a/core/src/io/anuke/mindustry/entities/Predict.java b/core/src/io/anuke/mindustry/entities/Predict.java index ba27f50426..06f86b540c 100644 --- a/core/src/io/anuke/mindustry/entities/Predict.java +++ b/core/src/io/anuke/mindustry/entities/Predict.java @@ -4,12 +4,16 @@ import com.badlogic.gdx.math.Vector2; import io.anuke.mindustry.entities.traits.TargetTrait; import io.anuke.ucore.util.Mathf; -/**Class for predicting shoot angles based on velocities of targets.*/ -public class Predict { +/** + * Class for predicting shoot angles based on velocities of targets. + */ +public class Predict{ private static Vector2 vec = new Vector2(); private static Vector2 vresult = new Vector2(); - /**Calculates of intercept of a stationary and moving target. Do not call from multiple threads! + /** + * Calculates of intercept of a stationary and moving target. Do not call from multiple threads! + * * @param srcx X of shooter * @param srcy Y of shooter * @param dstx X of target @@ -17,52 +21,55 @@ public class Predict { * @param dstvx X velocity of target (subtract shooter X velocity if needed) * @param dstvy Y velocity of target (subtract shooter Y velocity if needed) * @param v speed of bullet - * @return the intercept location*/ - public static Vector2 intercept(float srcx, float srcy, float dstx, float dsty, float dstvx, float dstvy, float v) { + * @return the intercept location + */ + public static Vector2 intercept(float srcx, float srcy, float dstx, float dsty, float dstvx, float dstvy, float v){ float tx = dstx - srcx, ty = dsty - srcy; // Get quadratic equation components - float a = dstvx * dstvx + dstvy * dstvy - v*v; + float a = dstvx * dstvx + dstvy * dstvy - v * v; float b = 2 * (dstvx * tx + dstvy * ty); - float c = tx*tx + ty*ty; + float c = tx * tx + ty * ty; // Solve quadratic Vector2 ts = quad(a, b, c); // Find smallest positive solution Vector2 sol = vresult.set(0, 0); - if (ts != null) { + if(ts != null){ float t0 = ts.x, t1 = ts.y; float t = Math.min(t0, t1); - if (t < 0) t = Math.max(t0, t1); - if (t > 0) { - sol.set(dstx + dstvx*t, dsty + dstvy*t); + if(t < 0) t = Math.max(t0, t1); + if(t > 0){ + sol.set(dstx + dstvx * t, dsty + dstvy * t); } } return sol; } - /**See {@link #intercept(float, float, float, float, float, float, float)}.*/ - public static Vector2 intercept(TargetTrait src, TargetTrait dst, float v) { + /** + * See {@link #intercept(float, float, float, float, float, float, float)}. + */ + public static Vector2 intercept(TargetTrait src, TargetTrait dst, float v){ return intercept(src.getX(), src.getY(), dst.getX(), dst.getY(), dst.getVelocity().x - src.getVelocity().x, dst.getVelocity().x - src.getVelocity().y, v); } - private static Vector2 quad(float a, float b, float c) { + private static Vector2 quad(float a, float b, float c){ Vector2 sol = null; - if (Math.abs(a) < 1e-6) { - if (Math.abs(b) < 1e-6) { - sol = Math.abs(c) < 1e-6 ? vec.set(0,0) : null; - } else { - vec.set(-c/b, -c/b); + if(Math.abs(a) < 1e-6){ + if(Math.abs(b) < 1e-6){ + sol = Math.abs(c) < 1e-6 ? vec.set(0, 0) : null; + }else{ + vec.set(-c / b, -c / b); } - } else { - float disc = b*b - 4*a*c; - if (disc >= 0) { + }else{ + float disc = b * b - 4 * a * c; + if(disc >= 0){ disc = Mathf.sqrt(disc); - a = 2*a; - sol = vec.set((-b-disc)/a, (-b+disc)/a); + a = 2 * a; + sol = vec.set((-b - disc) / a, (-b + disc) / a); } } return sol; diff --git a/core/src/io/anuke/mindustry/entities/StatusController.java b/core/src/io/anuke/mindustry/entities/StatusController.java index 374c009258..fe2e9e6248 100644 --- a/core/src/io/anuke/mindustry/entities/StatusController.java +++ b/core/src/io/anuke/mindustry/entities/StatusController.java @@ -12,7 +12,9 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; -/**Class for controlling status effects on an entity.*/ +/** + * Class for controlling status effects on an entity. + */ public class StatusController implements Saveable{ private static final StatusEntry globalResult = new StatusEntry(); private static final Array removals = new ThreadArray<>(); @@ -26,20 +28,20 @@ public class StatusController implements Saveable{ public void handleApply(Unit unit, StatusEffect effect, float intensity){ if(effect == StatusEffects.none) return; //don't apply empty effects - float newTime = effect.baseDuration*intensity; + float newTime = effect.baseDuration * intensity; if(statuses.size > 0){ //check for opposite effects for(StatusEntry entry : statuses){ //extend effect - if(entry.effect == effect) { + if(entry.effect == effect){ entry.time = Math.max(entry.time, newTime); return; }else if(entry.effect.isOpposite(effect)){ //find opposite entry.effect.getTransition(unit, effect, entry.time, newTime, globalResult); entry.time = globalResult.time; - if (globalResult.effect != entry.effect) { + if(globalResult.effect != entry.effect){ entry.effect.onTransition(unit, globalResult.effect); entry.effect = globalResult.effect; } @@ -94,7 +96,7 @@ public class StatusController implements Saveable{ return damageMultiplier; } - public float getArmorMultiplier() { + public float getArmorMultiplier(){ return armorMultiplier; } @@ -106,24 +108,24 @@ public class StatusController implements Saveable{ } @Override - public void writeSave(DataOutput stream) throws IOException { + public void writeSave(DataOutput stream) throws IOException{ stream.writeByte(statuses.size); for(StatusEntry entry : statuses){ stream.writeByte(entry.effect.id); - stream.writeShort((short)(entry.time * 2)); + stream.writeShort((short) (entry.time * 2)); } } @Override - public void readSave(DataInput stream) throws IOException { - for (StatusEntry effect : statuses){ + public void readSave(DataInput stream) throws IOException{ + for(StatusEntry effect : statuses){ Pooling.free(effect); } statuses.clear(); byte amount = stream.readByte(); - for (int i = 0; i < amount; i++) { + for(int i = 0; i < amount; i++){ byte id = stream.readByte(); float time = stream.readShort() / 2f; StatusEntry entry = Pooling.obtain(StatusEntry.class); @@ -132,7 +134,7 @@ public class StatusController implements Saveable{ } } - public static class StatusEntry { + public static class StatusEntry{ public StatusEffect effect; public float time; diff --git a/core/src/io/anuke/mindustry/entities/TileEntity.java b/core/src/io/anuke/mindustry/entities/TileEntity.java index 4c784bebd5..d4695604dc 100644 --- a/core/src/io/anuke/mindustry/entities/TileEntity.java +++ b/core/src/io/anuke/mindustry/entities/TileEntity.java @@ -35,213 +35,219 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.tileGroup; import static io.anuke.mindustry.Vars.world; -public class TileEntity extends BaseEntity implements TargetTrait { - public static final float timeToSleep = 60f*4; //4 seconds to fall asleep - /**This value is only used for debugging.*/ - public static int sleepingEntities = 0; +public class TileEntity extends BaseEntity implements TargetTrait{ + public static final float timeToSleep = 60f * 4; //4 seconds to fall asleep + private static final ObjectSet tmpTiles = new ObjectSet<>(); + /** + * This value is only used for debugging. + */ + public static int sleepingEntities = 0; + public Tile tile; + public Timer timer; + public float health; - private static final ObjectSet tmpTiles = new ObjectSet<>(); + public PowerModule power; + public InventoryModule items; + public LiquidModule liquids; + public ConsumeModule cons; - public Tile tile; - public Timer timer; - public float health; + /**List of (cached) tiles with entities in proximity, used for outputting to*/ + private Array proximity = new Array<>(8); + private boolean dead = false; + private boolean sleeping; + private float sleepTime; - public PowerModule power; - public InventoryModule items; - public LiquidModule liquids; - public ConsumeModule cons; + @Remote(called = Loc.server, in = In.blocks) + public static void onTileDamage(Tile tile, float health){ + if(tile.entity != null){ + tile.entity.health = health; + } + } - //list of (cached) tiles with entities in proximity, used for outputting to - //TODO implement - private Array proximity = new Array<>(8); - private boolean dead = false; - private boolean sleeping; - private float sleepTime; - - /**Sets this tile entity data to this tile, and adds it if necessary.*/ - public TileEntity init(Tile tile, boolean added){ - this.tile = tile; - x = tile.drawx(); - y = tile.drawy(); + @Remote(called = Loc.server, in = In.blocks) + public static void onTileDestroyed(Tile tile){ + if(tile.entity == null) return; + tile.entity.onDeath(); + } - health = tile.block().health; - - timer = new Timer(tile.block().timers); - - if(added){ - add(); - } - - return this; - } + /**Sets this tile entity data to this tile, and adds it if necessary.*/ + public TileEntity init(Tile tile, boolean added){ + this.tile = tile; + x = tile.drawx(); + y = tile.drawy(); - /**Call when nothing is happening to the entity. - * This increments the internal sleep timer.*/ - public void sleep(){ - sleepTime += Timers.delta(); - if(!sleeping && sleepTime >= timeToSleep){ - remove(); - sleeping = true; - sleepingEntities ++; - } - } + health = tile.block().health; - /**Call when something just happened to the entity. - * If the entity was sleeping, this enables it. This also resets the sleep timer.*/ - public void wakeUp(){ - sleepTime = 0f; - if(sleeping){ - add(); - sleeping = false; - sleepingEntities --; - } - } + timer = new Timer(tile.block().timers); - public boolean isSleeping(){ - return sleeping; - } + if(added){ + add(); + } - public boolean isDead() { - return dead; - } + return this; + } - public void write(DataOutputStream stream) throws IOException{} - public void read(DataInputStream stream) throws IOException{} + /** + * Call when nothing is happening to the entity. + * This increments the internal sleep timer. + */ + public void sleep(){ + sleepTime += Timers.delta(); + if(!sleeping && sleepTime >= timeToSleep){ + remove(); + sleeping = true; + sleepingEntities++; + } + } - private void onDeath(){ - if(!dead) { - dead = true; - Block block = tile.block(); + /** + * Call when something just happened to the entity. + * If the entity was sleeping, this enables it. This also resets the sleep timer. + */ + public void wakeUp(){ + sleepTime = 0f; + if(sleeping){ + add(); + sleeping = false; + sleepingEntities--; + } + } - block.onDestroyed(tile); - world.removeBlock(tile); - block.afterDestroyed(tile, this); - remove(); - } - } + public boolean isSleeping(){ + return sleeping; + } - public boolean collide(Bullet other){ - return true; - } - - public void collision(Bullet other){ - tile.block().handleBulletHit(this, other); - } - - public void damage(float damage){ - if(dead) return; + public boolean isDead(){ + return dead; + } - CallBlocks.onTileDamage(tile, health - tile.block().handleDamage(tile, damage)); + public void write(DataOutputStream stream) throws IOException{ + } - if(health <= 0){ - CallBlocks.onTileDestroyed(tile); - } - } + public void read(DataInputStream stream) throws IOException{ + } - public Tile getTile(){ - return tile; - } + private void onDeath(){ + if(!dead){ + dead = true; + Block block = tile.block(); - public boolean consumed(Class type){ - return tile.block().consumes.get(type).valid(tile.block(), this); - } + block.onDestroyed(tile); + world.removeBlock(tile); + block.afterDestroyed(tile, this); + remove(); + } + } - public void removeFromProximity(){ - GridPoint2[] nearby = Edges.getEdges(tile.block().size); - for (GridPoint2 point : nearby) { - Tile other = world.tile(tile.x + point.x, tile.y + point.y); - //remove this tile from all nearby tile's proximities - if(other != null){ - other = other.target(); + public boolean collide(Bullet other){ + return true; + } + + public void collision(Bullet other){ + tile.block().handleBulletHit(this, other); + } + + public void damage(float damage){ + if(dead) return; + + CallBlocks.onTileDamage(tile, health - tile.block().handleDamage(tile, damage)); + + if(health <= 0){ + CallBlocks.onTileDestroyed(tile); + } + } + + public Tile getTile(){ + return tile; + } + + public boolean consumed(Class type){ + return tile.block().consumes.get(type).valid(tile.block(), this); + } + + public void removeFromProximity(){ + GridPoint2[] nearby = Edges.getEdges(tile.block().size); + for(GridPoint2 point : nearby){ + Tile other = world.tile(tile.x + point.x, tile.y + point.y); + //remove this tile from all nearby tile's proximities + if(other != null){ + other = other.target(); other.block().onProximityUpdate(other); } - if(other != null && other.entity != null){ - other.entity.proximity.removeValue(tile, true); - } - } - } + if(other != null && other.entity != null){ + other.entity.proximity.removeValue(tile, true); + } + } + } - public void updateProximity(){ - tmpTiles.clear(); - proximity.clear(); + public void updateProximity(){ + tmpTiles.clear(); + proximity.clear(); - GridPoint2[] nearby = Edges.getEdges(tile.block().size); - for (GridPoint2 point : nearby) { - Tile other = world.tile(tile.x + point.x, tile.y + point.y); + GridPoint2[] nearby = Edges.getEdges(tile.block().size); + for(GridPoint2 point : nearby){ + Tile other = world.tile(tile.x + point.x, tile.y + point.y); - if(other != null){ + if(other != null){ other.block().onProximityUpdate(other); - other = other.target(); + other = other.target(); } - if(other != null && other.entity != null){ - tmpTiles.add(other); + if(other != null && other.entity != null){ + tmpTiles.add(other); - //add this tile to proximity of nearby tiles - if(!other.entity.proximity.contains(tile, true)){ - other.entity.proximity.add(tile); - } - } - } + //add this tile to proximity of nearby tiles + if(!other.entity.proximity.contains(tile, true)){ + other.entity.proximity.add(tile); + } + } + } - //using a set to prevent duplicates - for(Tile tile : tmpTiles){ - proximity.add(tile); - } + //using a set to prevent duplicates + for(Tile tile : tmpTiles){ + proximity.add(tile); + } - tile.block().onProximityUpdate(tile); - } + tile.block().onProximityUpdate(tile); + } - public Array proximity(){ - return proximity; - } + public Array proximity(){ + return proximity; + } - @Override - public Team getTeam() { - return tile.getTeam(); - } + @Override + public Team getTeam(){ + return tile.getTeam(); + } - @Override - public Vector2 getVelocity() { - return Vector2.Zero; - } + @Override + public Vector2 getVelocity(){ + return Vector2.Zero; + } - @Override - public void update(){ - synchronized (Tile.tileSetLock) { - //TODO better smoke effect, this one is awful - if (health != 0 && health < tile.block().health && !(tile.block() instanceof Wall) && - Mathf.chance(0.009f * Timers.delta() * (1f - health / tile.block().health))) { + @Override + public void update(){ + synchronized(Tile.tileSetLock){ + //TODO better smoke effect, this one is awful + if(health != 0 && health < tile.block().health && !(tile.block() instanceof Wall) && + Mathf.chance(0.009f * Timers.delta() * (1f - health / tile.block().health))){ - Effects.effect(Fx.smoke, x + Mathf.range(4), y + Mathf.range(4)); - } + Effects.effect(Fx.smoke, x + Mathf.range(4), y + Mathf.range(4)); + } - if (health <= 0) { - onDeath(); - } + if(health <= 0){ + onDeath(); + } - tile.block().update(tile); - if(cons != null){ - cons.update(this); - } - } - } + tile.block().update(tile); + if(cons != null){ + cons.update(this); + } + } + } - @Override - public EntityGroup targetGroup() { - return tileGroup; - } - - @Remote(called = Loc.server, in = In.blocks) - public static void onTileDamage(Tile tile, float health){ - if(tile.entity != null){ - tile.entity.health = health; - } - } - - @Remote(called = Loc.server, in = In.blocks) - public static void onTileDestroyed(Tile tile){ - if(tile.entity == null) return; - tile.entity.onDeath(); - } + @Override + public EntityGroup targetGroup(){ + return tileGroup; + } } diff --git a/core/src/io/anuke/mindustry/entities/Unit.java b/core/src/io/anuke/mindustry/entities/Unit.java index 60beccd8e8..8ad6360042 100644 --- a/core/src/io/anuke/mindustry/entities/Unit.java +++ b/core/src/io/anuke/mindustry/entities/Unit.java @@ -31,13 +31,19 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.state; import static io.anuke.mindustry.Vars.world; -public abstract class Unit extends DestructibleEntity implements SaveTrait, TargetTrait, SyncTrait, DrawTrait, TeamTrait, CarriableTrait, InventoryTrait { - /**total duration of hit flash effect*/ +public abstract class Unit extends DestructibleEntity implements SaveTrait, TargetTrait, SyncTrait, DrawTrait, TeamTrait, CarriableTrait, InventoryTrait{ + /** + * total duration of hit flash effect + */ public static final float hitDuration = 9f; - /**Percision divisor of velocity, used when writing. For example a value of '2' would mean the percision is 1/2 = 0.5-size chunks.*/ + /** + * Percision divisor of velocity, used when writing. For example a value of '2' would mean the percision is 1/2 = 0.5-size chunks. + */ public static final float velocityPercision = 8f; - /**Maximum absolute value of a velocity vector component.*/ - public static final float maxAbsVelocity = 127f/velocityPercision; + /** + * Maximum absolute value of a velocity vector component. + */ + public static final float maxAbsVelocity = 127f / velocityPercision; public static final float elevationScale = 4f; private static final Vector2 moveVector = new Vector2(); @@ -56,28 +62,28 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ protected float elevation; @Override - public UnitInventory getInventory() { + public UnitInventory getInventory(){ return inventory; } @Override - public float getRotation() { + public float getRotation(){ return rotation; } @Override - public void setRotation(float rotation) { + public void setRotation(float rotation){ this.rotation = rotation; } @Override - public void setCarrier(CarryTrait carrier) { - this.carrier = carrier; + public CarryTrait getCarrier(){ + return carrier; } @Override - public CarryTrait getCarrier() { - return carrier; + public void setCarrier(CarryTrait carrier){ + this.carrier = carrier; } @Override @@ -86,7 +92,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ } @Override - public void interpolate() { + public void interpolate(){ interpolator.update(); x = interpolator.pos.x; @@ -98,7 +104,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ } @Override - public Interpolator getInterpolator() { + public Interpolator getInterpolator(){ return interpolator; } @@ -115,31 +121,31 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ } @Override - public void onDeath() { + public void onDeath(){ inventory.clear(); drownTime = 0f; status.clear(); } @Override - public Vector2 getVelocity() { + public Vector2 getVelocity(){ return velocity; } @Override - public void writeSave(DataOutput stream) throws IOException { + public void writeSave(DataOutput stream) throws IOException{ writeSave(stream, false); } @Override - public void readSave(DataInput stream) throws IOException { + public void readSave(DataInput stream) throws IOException{ byte team = stream.readByte(); boolean dead = stream.readBoolean(); float x = stream.readFloat(); float y = stream.readFloat(); byte xv = stream.readByte(); byte yv = stream.readByte(); - float rotation = stream.readShort()/2f; + float rotation = stream.readShort() / 2f; int health = stream.readShort(); this.status.readSave(stream); @@ -153,21 +159,21 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ this.rotation = rotation; } - public void writeSave(DataOutput stream, boolean net) throws IOException { + public void writeSave(DataOutput stream, boolean net) throws IOException{ stream.writeByte(team.ordinal()); stream.writeBoolean(isDead()); stream.writeFloat(net ? interpolator.target.x : x); stream.writeFloat(net ? interpolator.target.y : y); - stream.writeByte((byte)(Mathf.clamp(velocity.x, -maxAbsVelocity, maxAbsVelocity) * velocityPercision)); - stream.writeByte((byte)(Mathf.clamp(velocity.y, -maxAbsVelocity, maxAbsVelocity) * velocityPercision)); - stream.writeShort((short)(rotation*2)); - stream.writeShort((short)health); + stream.writeByte((byte) (Mathf.clamp(velocity.x, -maxAbsVelocity, maxAbsVelocity) * velocityPercision)); + stream.writeByte((byte) (Mathf.clamp(velocity.y, -maxAbsVelocity, maxAbsVelocity) * velocityPercision)); + stream.writeShort((short) (rotation * 2)); + stream.writeShort((short) health); status.writeSave(stream); inventory.writeSave(stream); } public float calculateDamage(float amount){ - return amount * Mathf.clamp(1f-getArmor()/100f*status.getArmorMultiplier()); + return amount * Mathf.clamp(1f - getArmor() / 100f * status.getArmorMultiplier()); } public float getDamageMultipler(){ @@ -182,7 +188,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ if(state.teams.has(team)){ TeamData data = state.teams.get(team); - Tile tile = Geometry.findClosest(x, y, data.cores); + Tile tile = Geometry.findClosest(x, y, data.cores); if(tile == null){ return null; }else{ @@ -200,15 +206,18 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ public void avoidOthers(float avoidRange){ - EntityPhysics.getNearby(getGroup(), x, y, avoidRange*2f, t -> { - if(t == this || (t instanceof Unit && (((Unit) t).isDead() || (((Unit) t).isFlying() != isFlying()) || ((Unit) t).getCarrier() == this) || getCarrier() == t)) return; + EntityPhysics.getNearby(getGroup(), x, y, avoidRange * 2f, t -> { + if(t == this || (t instanceof Unit && (((Unit) t).isDead() || (((Unit) t).isFlying() != isFlying()) || ((Unit) t).getCarrier() == this) || getCarrier() == t)) + return; float dst = distanceTo(t); if(dst > avoidRange) return; velocity.add(moveVector.set(x, y).sub(t.getX(), t.getY()).setLength(1f * (1f - (dst / avoidRange)))); }); } - /**Updates velocity and status effects.*/ + /** + * Updates velocity and status effects. + */ public void updateVelocityStatus(float drag, float maxVelocity){ if(isCarried()){ //carried units do not take into account velocity normally set(carrier.getX(), carrier.getY()); @@ -223,7 +232,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ velocity.limit(maxVelocity).scl(status.getSpeedMultiplier()); - if(isFlying()) { + if(isFlying()){ x += velocity.x / getMass() * Timers.delta(); y += velocity.y / getMass() * Timers.delta(); @@ -245,7 +254,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ } } - if(onLiquid && velocity.len() > 0.4f && Timers.get(this, "flooreffect", 14 - (velocity.len() * floor.speedMultiplier)*2f)){ + if(onLiquid && velocity.len() > 0.4f && Timers.get(this, "flooreffect", 14 - (velocity.len() * floor.speedMultiplier) * 2f)){ Effects.effect(floor.walkEffect, floor.liquidColor, x, y); } @@ -256,7 +265,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ } if(onLiquid && floor.drownTime > 0){ - drownTime += Timers.delta() * 1f/floor.drownTime; + drownTime += Timers.delta() * 1f / floor.drownTime; if(Timers.get(this, "drowneffect", 15)){ Effects.effect(floor.drownUpdateEffect, floor.liquidColor, x, y); } @@ -276,7 +285,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ if(Math.abs(py - y) <= 0.0001f) velocity.y = 0f; } - velocity.scl(Mathf.clamp(1f-drag* floor.dragMultiplier* Timers.delta())); + velocity.scl(Mathf.clamp(1f - drag * floor.dragMultiplier * Timers.delta())); } public void applyEffect(StatusEffect effect, float intensity){ @@ -299,12 +308,17 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ } public float getAmmoFraction(){ - return inventory.totalAmmo() / (float)inventory.ammoCapacity(); + return inventory.totalAmmo() / (float) inventory.ammoCapacity(); } - public void drawUnder(){} - public void drawOver(){} - public void drawShadow(){} + public void drawUnder(){ + } + + public void drawOver(){ + } + + public void drawShadow(){ + } public void drawView(){ Fill.circle(x, y, getViewDistance()); @@ -319,12 +333,20 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ } public abstract TextureRegion getIconRegion(); + public abstract int getItemCapacity(); + public abstract int getAmmoCapacity(); + public abstract float getArmor(); + public abstract boolean acceptsAmmo(Item item); + public abstract void addAmmo(Item item); + public abstract float getMass(); + public abstract boolean isFlying(); + public abstract float getSize(); } diff --git a/core/src/io/anuke/mindustry/entities/UnitInventory.java b/core/src/io/anuke/mindustry/entities/UnitInventory.java index b72543bf74..53fd627f88 100644 --- a/core/src/io/anuke/mindustry/entities/UnitInventory.java +++ b/core/src/io/anuke/mindustry/entities/UnitInventory.java @@ -8,16 +8,17 @@ import io.anuke.mindustry.type.AmmoType; import io.anuke.mindustry.type.Item; import io.anuke.mindustry.type.ItemStack; -import java.io.*; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; public class UnitInventory implements Saveable{ + private final Unit unit; private Array ammos = new Array<>(); private int totalAmmo; private ItemStack item = new ItemStack(Items.stone, 0); - private final Unit unit; - - public UnitInventory(Unit unit) { + public UnitInventory(Unit unit){ this.unit = unit; } @@ -26,24 +27,24 @@ public class UnitInventory implements Saveable{ } @Override - public void writeSave(DataOutput stream) throws IOException { + public void writeSave(DataOutput stream) throws IOException{ stream.writeShort(item.amount); stream.writeByte(item.item.id); stream.writeShort(totalAmmo); stream.writeByte(ammos.size); - for(int i = 0; i < ammos.size; i ++){ + for(int i = 0; i < ammos.size; i++){ stream.writeByte(ammos.get(i).type.id); stream.writeShort(ammos.get(i).amount); } } @Override - public void readSave(DataInput stream) throws IOException { + public void readSave(DataInput stream) throws IOException{ short iamount = stream.readShort(); byte iid = stream.readByte(); this.totalAmmo = stream.readShort(); byte ammoa = stream.readByte(); - for(int i = 0; i < ammoa; i ++){ + for(int i = 0; i < ammoa; i++){ byte aid = stream.readByte(); int am = stream.readShort(); ammos.add(new AmmoEntry(AmmoType.getByID(aid), am)); @@ -53,12 +54,14 @@ public class UnitInventory implements Saveable{ item.amount = iamount; } - /**Returns ammo range, or MAX_VALUE if this inventory has no ammo.*/ + /** + * Returns ammo range, or MAX_VALUE if this inventory has no ammo. + */ public float getAmmoRange(){ return hasAmmo() ? getAmmo().getRange() : Float.MAX_VALUE; } - public AmmoType getAmmo() { + public AmmoType getAmmo(){ return ammos.size == 0 ? null : ammos.peek().type; } @@ -69,9 +72,9 @@ public class UnitInventory implements Saveable{ public void useAmmo(){ if(unit.isInfiniteAmmo()) return; AmmoEntry entry = ammos.peek(); - entry.amount --; + entry.amount--; if(entry.amount == 0) ammos.pop(); - totalAmmo --; + totalAmmo--; } public int totalAmmo(){ @@ -91,19 +94,19 @@ public class UnitInventory implements Saveable{ totalAmmo += type.quantityMultiplier; //find ammo entry by type - for(int i = ammos.size - 1; i >= 0; i --){ + for(int i = ammos.size - 1; i >= 0; i--){ AmmoEntry entry = ammos.get(i); //if found, put it to the right if(entry.type == type){ entry.amount += type.quantityMultiplier; - ammos.swap(i, ammos.size-1); + ammos.swap(i, ammos.size - 1); return; } } //must not be found - AmmoEntry entry = new AmmoEntry(type, (int)type.quantityMultiplier); + AmmoEntry entry = new AmmoEntry(type, (int) type.quantityMultiplier); ammos.add(entry); } diff --git a/core/src/io/anuke/mindustry/entities/Units.java b/core/src/io/anuke/mindustry/entities/Units.java index 291b80ca8c..1b9fcb27f2 100644 --- a/core/src/io/anuke/mindustry/entities/Units.java +++ b/core/src/io/anuke/mindustry/entities/Units.java @@ -15,15 +15,19 @@ import io.anuke.ucore.function.Predicate; import static io.anuke.mindustry.Vars.*; -/**Utility class for unit and team interactions.*/ -public class Units { +/** + * Utility class for unit and team interactions. + */ +public class Units{ private static Rectangle rect = new Rectangle(); private static Rectangle hitrect = new Rectangle(); private static Unit result; private static float cdist; private static boolean boolResult; - /**Validates a target. + /** + * Validates a target. + * * @param target The target to validate * @param team The team of the thing doing tha targeting * @param x The X position of the thing doign the targeting @@ -31,22 +35,28 @@ public class Units { * @param range The maximum distance from the target X/Y the targeter can be for it to be valid * @return whether the target is invalid */ - public static boolean invalidateTarget(TargetTrait target, Team team, float x, float y, float range) { + public static boolean invalidateTarget(TargetTrait target, Team team, float x, float y, float range){ return target == null || (range != Float.MAX_VALUE && target.distanceTo(x, y) > range) || target.getTeam() == team || !target.isValid(); } - /**See {@link #invalidateTarget(TargetTrait, Team, float, float, float)}*/ + /** + * See {@link #invalidateTarget(TargetTrait, Team, float, float, float)} + */ public static boolean invalidateTarget(TargetTrait target, Team team, float x, float y){ return invalidateTarget(target, team, x, y, Float.MAX_VALUE); } - /**See {@link #invalidateTarget(TargetTrait, Team, float, float, float)}*/ + /** + * See {@link #invalidateTarget(TargetTrait, Team, float, float, float)} + */ public static boolean invalidateTarget(TargetTrait target, Unit targeter){ return invalidateTarget(target, targeter.team, targeter.x, targeter.y, targeter.inventory.getAmmoRange()); } - /**Returns whether there are any entities on this tile.*/ + /** + * Returns whether there are any entities on this tile. + */ public static boolean anyEntities(Tile tile){ Block type = tile.block(); rect.setSize(type.size * tilesize, type.size * tilesize); @@ -55,11 +65,11 @@ public class Units { boolResult = false; Units.getNearby(rect, unit -> { - if (boolResult) return; - if (!unit.isFlying()) { + if(boolResult) return; + if(!unit.isFlying()){ unit.getHitbox(hitrect); - if (hitrect.overlaps(rect)) { + if(hitrect.overlaps(rect)){ boolResult = true; } } @@ -68,7 +78,9 @@ public class Units { return boolResult; } - /**Returns whether there are any entities on this tile, with the hitbox expanded.*/ + /** + * Returns whether there are any entities on this tile, with the hitbox expanded. + */ public static boolean anyEntities(Tile tile, float expansion, Predicate pred){ Block type = tile.block(); rect.setSize(type.size * tilesize + expansion, type.size * tilesize + expansion); @@ -81,7 +93,7 @@ public class Units { if(!unit.isFlying()){ unit.getHitbox(hitrect); - if(hitrect.overlaps(rect)) { + if(hitrect.overlaps(rect)){ value[0] = true; } } @@ -90,7 +102,9 @@ public class Units { return value[0]; } - /**Returns the neareset ally tile in a range.*/ + /** + * Returns the neareset ally tile in a range. + */ public static TileEntity findAllyTile(Team team, float x, float y, float range, Predicate pred){ for(Team enemy : state.teams.alliesOf(team)){ TileEntity entity = world.indexer().findTile(enemy, x, y, range, pred); @@ -101,7 +115,9 @@ public class Units { return null; } - /**Returns the neareset enemy tile in a range.*/ + /** + * Returns the neareset enemy tile in a range. + */ public static TileEntity findEnemyTile(Team team, float x, float y, float range, Predicate pred){ for(Team enemy : state.teams.enemiesOf(team)){ TileEntity entity = world.indexer().findTile(enemy, x, y, range, pred); @@ -112,7 +128,9 @@ public class Units { return null; } - /**Iterates over all units on all teams, including players.*/ + /** + * Iterates over all units on all teams, including players. + */ public static void allUnits(Consumer cons){ //check all unit groups first for(EntityGroup group : unitGroups){ @@ -129,7 +147,9 @@ public class Units { } } - /**Returns the closest target enemy. First, units are checked, then tile entities.*/ + /** + * Returns the closest target enemy. First, units are checked, then tile entities. + */ public static TargetTrait getClosestTarget(Team team, float x, float y, float range){ Unit unit = getClosestEnemy(team, x, y, range, u -> true); if(unit != null){ @@ -139,20 +159,22 @@ public class Units { } } - /**Returns the closest enemy of this team. Filter by predicate.*/ + /** + * Returns the closest enemy of this team. Filter by predicate. + */ public static Unit getClosestEnemy(Team team, float x, float y, float range, Predicate predicate){ result = null; cdist = 0f; - rect.setSize(range*2f).setCenter(x, y); + rect.setSize(range * 2f).setCenter(x, y); getNearbyEnemies(team, rect, e -> { - if (e.isDead() || !predicate.test(e)) + if(e.isDead() || !predicate.test(e)) return; float dist = Vector2.dst(e.x, e.y, x, y); - if (dist < range) { - if (result == null || dist < cdist) { + if(dist < range){ + if(result == null || dist < cdist){ result = e; cdist = dist; } @@ -162,20 +184,22 @@ public class Units { return result; } - /**Returns the closest ally of this team. Filter by predicate.*/ + /** + * Returns the closest ally of this team. Filter by predicate. + */ public static Unit getClosest(Team team, float x, float y, float range, Predicate predicate){ result = null; cdist = 0f; - rect.setSize(range*2f).setCenter(x, y); + rect.setSize(range * 2f).setCenter(x, y); getNearby(team, rect, e -> { - if (!predicate.test(e)) + if(!predicate.test(e)) return; float dist = Vector2.dst(e.x, e.y, x, y); - if (dist < range) { - if (result == null || dist < cdist) { + if(dist < range){ + if(result == null || dist < cdist){ result = e; cdist = dist; } @@ -185,28 +209,32 @@ public class Units { return result; } - /**Iterates over all units in a rectangle.*/ + /** + * Iterates over all units in a rectangle. + */ public static void getNearby(Team team, Rectangle rect, Consumer cons){ EntityGroup group = unitGroups[team.ordinal()]; if(!group.isEmpty()){ - EntityPhysics.getNearby(group, rect, entity -> cons.accept((Unit)entity)); + EntityPhysics.getNearby(group, rect, entity -> cons.accept((Unit) entity)); } //now check all players EntityPhysics.getNearby(playerGroup, rect, player -> { - if(((Unit)player).team == team) cons.accept((Unit)player); + if(((Unit) player).team == team) cons.accept((Unit) player); }); } - /**Iterates over all units in a circle around this position.*/ + /** + * Iterates over all units in a circle around this position. + */ public static void getNearby(Team team, float x, float y, float radius, Consumer cons){ rect.setSize(radius * 2).setCenter(x, y); EntityGroup group = unitGroups[team.ordinal()]; if(!group.isEmpty()){ EntityPhysics.getNearby(group, rect, entity -> { - if(entity.distanceTo(x, y) <= radius) { + if(entity.distanceTo(x, y) <= radius){ cons.accept((Unit) entity); } }); @@ -214,46 +242,52 @@ public class Units { //now check all players EntityPhysics.getNearby(playerGroup, rect, player -> { - if(((Unit)player).team == team && player.distanceTo(x, y) <= radius){ - cons.accept((Unit)player); + if(((Unit) player).team == team && player.distanceTo(x, y) <= radius){ + cons.accept((Unit) player); } }); } - /**Iterates over all units in a rectangle.*/ + /** + * Iterates over all units in a rectangle. + */ public static void getNearby(Rectangle rect, Consumer cons){ for(Team team : Team.all){ EntityGroup group = unitGroups[team.ordinal()]; if(!group.isEmpty()){ - EntityPhysics.getNearby(group, rect, entity -> cons.accept((Unit)entity)); + EntityPhysics.getNearby(group, rect, entity -> cons.accept((Unit) entity)); } } //now check all enemy players - EntityPhysics.getNearby(playerGroup, rect, player -> cons.accept((Unit)player)); + EntityPhysics.getNearby(playerGroup, rect, player -> cons.accept((Unit) player)); } - /**Iterates over all units that are enemies of this team.*/ + /** + * Iterates over all units that are enemies of this team. + */ public static void getNearbyEnemies(Team team, Rectangle rect, Consumer cons){ ObjectSet targets = state.teams.enemiesOf(team); for(Team other : targets){ EntityGroup group = unitGroups[other.ordinal()]; if(!group.isEmpty()){ - EntityPhysics.getNearby(group, rect, entity -> cons.accept((Unit)entity)); + EntityPhysics.getNearby(group, rect, entity -> cons.accept((Unit) entity)); } } //now check all enemy players EntityPhysics.getNearby(playerGroup, rect, player -> { - if(targets.contains(((Player)player).team)){ - cons.accept((Unit)player); + if(targets.contains(((Player) player).team)){ + cons.accept((Unit) player); } }); } - /**Iterates over all units.*/ + /** + * Iterates over all units. + */ public static void getAllUnits(Consumer cons){ for(Team team : Team.all){ diff --git a/core/src/io/anuke/mindustry/entities/bullet/ArtilleryBulletType.java b/core/src/io/anuke/mindustry/entities/bullet/ArtilleryBulletType.java index f298ab6ab4..52a1594c96 100644 --- a/core/src/io/anuke/mindustry/entities/bullet/ArtilleryBulletType.java +++ b/core/src/io/anuke/mindustry/entities/bullet/ArtilleryBulletType.java @@ -6,10 +6,10 @@ import io.anuke.ucore.core.Effects.Effect; import io.anuke.ucore.graphics.Draw; //TODO scale velocity depending on fslope() -public class ArtilleryBulletType extends BasicBulletType { +public class ArtilleryBulletType extends BasicBulletType{ protected Effect trailEffect = BulletFx.artilleryTrail; - public ArtilleryBulletType(float speed, float damage, String bulletSprite) { + public ArtilleryBulletType(float speed, float damage, String bulletSprite){ super(speed, damage, bulletSprite); collidesTiles = false; collides = false; @@ -17,18 +17,18 @@ public class ArtilleryBulletType extends BasicBulletType { } @Override - public void update(Bullet b) { + public void update(Bullet b){ super.update(b); - if(b.timer.get(0, 3 + b.fslope()*2f)){ + if(b.timer.get(0, 3 + b.fslope() * 2f)){ Effects.effect(trailEffect, backColor, b.x, b.y, b.fslope() * 4f); } } @Override - public void draw(Bullet b) { + public void draw(Bullet b){ float baseScale = 0.7f; - float scale = (baseScale + b.fslope()*(1f-baseScale)); + float scale = (baseScale + b.fslope() * (1f - baseScale)); float height = bulletHeight * ((1f - bulletShrink) + bulletShrink * b.fout()); diff --git a/core/src/io/anuke/mindustry/entities/bullet/BasicBulletType.java b/core/src/io/anuke/mindustry/entities/bullet/BasicBulletType.java index 3822ea7f52..d5d1455eb0 100644 --- a/core/src/io/anuke/mindustry/entities/bullet/BasicBulletType.java +++ b/core/src/io/anuke/mindustry/entities/bullet/BasicBulletType.java @@ -12,8 +12,10 @@ import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.util.Angles; import io.anuke.ucore.util.Mathf; -/**A BulletType for most ammo-based bullets shot from turrets and units.*/ -public class BasicBulletType extends BulletType { +/** + * A BulletType for most ammo-based bullets shot from turrets and units. + */ +public class BasicBulletType extends BulletType{ public Color backColor = Palette.bulletYellowBack, frontColor = Palette.bulletYellow; public float bulletWidth = 5f, bulletHeight = 7f; public float bulletShrink = 0.5f; @@ -23,7 +25,9 @@ public class BasicBulletType extends BulletType { public float fragVelocityMin = 0.2f, fragVelocityMax = 1f; public BulletType fragBullet = null; - /**Use a negative value to disable splash damage.*/ + /** + * Use a negative value to disable splash damage. + */ public float splashDamageRadius = -1f; public float splashDamage = 6f; @@ -39,19 +43,19 @@ public class BasicBulletType extends BulletType { public float hitShake = 0f; - public BasicBulletType(float speed, float damage, String bulletSprite) { + public BasicBulletType(float speed, float damage, String bulletSprite){ super(speed, damage); this.bulletSprite = bulletSprite; } @Override - public void load() { + public void load(){ backRegion = Draw.region(bulletSprite + "-back"); frontRegion = Draw.region(bulletSprite); } @Override - public void draw(Bullet b) { + public void draw(Bullet b){ float height = bulletHeight * ((1f - bulletShrink) + bulletShrink * b.fout()); Draw.color(backColor); @@ -62,7 +66,7 @@ public class BasicBulletType extends BulletType { } @Override - public void update(Bullet b) { + public void update(Bullet b){ super.update(b); if(homingPower > 0.0001f){ @@ -74,20 +78,20 @@ public class BasicBulletType extends BulletType { } @Override - public void hit(Bullet b, float x, float y) { + public void hit(Bullet b, float x, float y){ super.hit(b, x, y); Effects.shake(hitShake, hitShake, b); - if(fragBullet != null) { - for (int i = 0; i < fragBullets; i++) { + if(fragBullet != null){ + for(int i = 0; i < fragBullets; i++){ float len = Mathf.random(1f, 7f); float a = Mathf.random(360f); Bullet.create(fragBullet, b, x + Angles.trnsx(a, len), y + Angles.trnsy(a, len), a, Mathf.random(fragVelocityMin, fragVelocityMax)); } } - if(Mathf.chance(incendChance)) { + if(Mathf.chance(incendChance)){ Damage.createIncend(x, y, incendSpread, incendAmount); } @@ -97,7 +101,7 @@ public class BasicBulletType extends BulletType { } @Override - public void despawned(Bullet b) { + public void despawned(Bullet b){ if(fragBullet != null || splashDamageRadius > 0){ hit(b); } diff --git a/core/src/io/anuke/mindustry/entities/bullet/BombBulletType.java b/core/src/io/anuke/mindustry/entities/bullet/BombBulletType.java index 5682477e49..cdca80de79 100644 --- a/core/src/io/anuke/mindustry/entities/bullet/BombBulletType.java +++ b/core/src/io/anuke/mindustry/entities/bullet/BombBulletType.java @@ -1,8 +1,8 @@ package io.anuke.mindustry.entities.bullet; -public class BombBulletType extends BasicBulletType { +public class BombBulletType extends BasicBulletType{ - public BombBulletType(float damage, float radius, String sprite) { + public BombBulletType(float damage, float radius, String sprite){ super(0.7f, 0, sprite); splashDamageRadius = radius; splashDamage = damage; diff --git a/core/src/io/anuke/mindustry/entities/bullet/Bullet.java b/core/src/io/anuke/mindustry/entities/bullet/Bullet.java index 7f218d4524..0e9322928f 100644 --- a/core/src/io/anuke/mindustry/entities/bullet/Bullet.java +++ b/core/src/io/anuke/mindustry/entities/bullet/Bullet.java @@ -27,198 +27,199 @@ import static io.anuke.mindustry.Vars.bulletGroup; import static io.anuke.mindustry.Vars.world; public class Bullet extends BulletEntity implements TeamTrait, SyncTrait{ - private static Vector2 vector = new Vector2(); + private static Vector2 vector = new Vector2(); + public Timer timer = new Timer(3); + private Team team; + private Object data; + private boolean supressCollision; - private Team team; - private Object data; - private boolean supressCollision; + /** + * Internal use only! + */ + public Bullet(){ + } - public Timer timer = new Timer(3); + public static void create(BulletType type, TeamTrait owner, float x, float y, float angle){ + create(type, owner, owner.getTeam(), x, y, angle); + } - public static void create (BulletType type, TeamTrait owner, float x, float y, float angle){ - create(type, owner, owner.getTeam(), x, y, angle); - } + public static void create(BulletType type, Entity owner, Team team, float x, float y, float angle){ + create(type, owner, team, x, y, angle, 1f); + } - public static void create (BulletType type, Entity owner, Team team, float x, float y, float angle){ - create(type, owner, team, x, y, angle, 1f); - } + public static void create(BulletType type, Entity owner, Team team, float x, float y, float angle, float velocityScl){ + create(type, owner, team, x, y, angle, velocityScl, null); + } - public static void create (BulletType type, Entity owner, Team team, float x, float y, float angle, float velocityScl){ - create(type, owner, team, x, y, angle, velocityScl, null); - } + public static void create(BulletType type, Entity owner, Team team, float x, float y, float angle, float velocityScl, Object data){ + Bullet bullet = Pooling.obtain(Bullet.class); + bullet.type = type; + bullet.owner = owner; + bullet.data = data; - public static void create (BulletType type, Entity owner, Team team, float x, float y, float angle, float velocityScl, Object data){ - Bullet bullet = Pooling.obtain(Bullet.class); - bullet.type = type; - bullet.owner = owner; - bullet.data = data; + bullet.velocity.set(0, type.speed).setAngle(angle).scl(velocityScl); + if(type.keepVelocity){ + bullet.velocity.add(owner instanceof VelocityTrait ? ((VelocityTrait) owner).getVelocity() : Vector2.Zero); + } + bullet.hitbox.setSize(type.hitsize); - bullet.velocity.set(0, type.speed).setAngle(angle).scl(velocityScl); - if(type.keepVelocity){ - bullet.velocity.add(owner instanceof VelocityTrait ? ((VelocityTrait)owner).getVelocity() : Vector2.Zero); - } - bullet.hitbox.setSize(type.hitsize); + bullet.team = team; + bullet.type = type; - bullet.team = team; - bullet.type = type; + //translate bullets backwards, purely for visual reasons + float backDelta = Timers.delta(); - //translate bullets backwards, purely for visual reasons - float backDelta = Timers.delta(); + bullet.lastPosition().set(x - bullet.velocity.x * backDelta, y - bullet.velocity.y * backDelta, bullet.angle()); + bullet.setLastUpdated(TimeUtils.millis()); + bullet.setUpdateSpacing((long) ((Timers.delta() / 60f) * 1000)); + bullet.set(x - bullet.velocity.x * backDelta, y - bullet.velocity.y * backDelta); - bullet.lastPosition().set(x-bullet.velocity.x * backDelta, y-bullet.velocity.y * backDelta, bullet.angle()); - bullet.setLastUpdated(TimeUtils.millis()); - bullet.setUpdateSpacing((long)((Timers.delta() / 60f) * 1000)); - bullet.set(x-bullet.velocity.x * backDelta, y-bullet.velocity.y * backDelta); + bullet.add(); + } - bullet.add(); - } + public static void create(BulletType type, Bullet parent, float x, float y, float angle){ + create(type, parent.owner, parent.team, x, y, angle); + } - public static void create(BulletType type, Bullet parent, float x, float y, float angle){ - create(type, parent.owner, parent.team, x, y, angle); - } + public static void create(BulletType type, Bullet parent, float x, float y, float angle, float velocityScl){ + create(type, parent.owner, parent.team, x, y, angle, velocityScl); + } - public static void create(BulletType type, Bullet parent, float x, float y, float angle, float velocityScl){ - create(type, parent.owner, parent.team, x, y, angle, velocityScl); - } + @Remote(called = Loc.server, in = In.entities) + public static void createBullet(BulletType type, float x, float y, float angle){ + create(type, null, Team.none, x, y, angle); + } - @Remote(called = Loc.server, in = In.entities) - public static void createBullet(BulletType type, float x, float y, float angle){ - create(type, null, Team.none, x, y, angle); - } + public boolean collidesTiles(){ + return type.collidesTiles; + } - /**Internal use only!*/ - public Bullet(){} + public void supressCollision(){ + supressCollision = true; + } - public boolean collidesTiles(){ - return type.collidesTiles; - } + public void resetOwner(Entity entity, Team team){ + this.owner = entity; + this.team = team; + } - public void supressCollision(){ - supressCollision = true; - } + public void scaleTime(float add){ + time += add; + } - public void resetOwner(Entity entity, Team team){ - this.owner = entity; - this.team = team; - } + public Object getData(){ + return data; + } - public void scaleTime(float add){ - time += add; - } + @Override + public float getDamage(){ + if(owner instanceof Unit){ + return super.getDamage() * ((Unit) owner).getDamageMultipler(); + } - public Object getData() { - return data; - } + return super.getDamage(); + } - @Override - public float getDamage(){ - if(owner instanceof Unit){ - return super.getDamage() * ((Unit) owner).getDamageMultipler(); - } + @Override + public boolean isSyncing(){ + return type.syncable; + } - return super.getDamage(); - } + @Override + public void write(DataOutput data) throws IOException{ + data.writeFloat(x); + data.writeFloat(y); + data.writeFloat(velocity.x); + data.writeFloat(velocity.y); + data.writeByte(team.ordinal()); + data.writeByte(type.id); + } - @Override - public boolean isSyncing(){ - return type.syncable; - } + @Override + public void read(DataInput data, long time) throws IOException{ + x = data.readFloat(); + y = data.readFloat(); + velocity.x = data.readFloat(); + velocity.y = data.readFloat(); + team = Team.all[data.readByte()]; + type = BulletType.getByID(data.readByte()); + } - @Override - public void write(DataOutput data) throws IOException { - data.writeFloat(x); - data.writeFloat(y); - data.writeFloat(velocity.x); - data.writeFloat(velocity.y); - data.writeByte(team.ordinal()); - data.writeByte(type.id); - } + @Override + public Team getTeam(){ + return team; + } - @Override - public void read(DataInput data, long time) throws IOException{ - x = data.readFloat(); - y = data.readFloat(); - velocity.x = data.readFloat(); - velocity.y = data.readFloat(); - team = Team.all[data.readByte()]; - type = BulletType.getByID(data.readByte()); - } + @Override + public void draw(){ + type.draw(this); + } - @Override - public Team getTeam() { - return team; - } + @Override + public float drawSize(){ + return 8; + } - @Override - public void draw(){ - type.draw(this); - } + @Override + public boolean collides(SolidTrait other){ + return type.collides && super.collides(other); + } - @Override - public float drawSize(){ - return 8; - } + @Override + public void collision(SolidTrait other, float x, float y){ + super.collision(other, x, y); - @Override - public boolean collides(SolidTrait other){ - return type.collides && super.collides(other); - } + if(other instanceof Unit){ + Unit unit = (Unit) other; + unit.getVelocity().add(vector.set(other.getX(), other.getY()).sub(x, y).setLength(type.knockback / unit.getMass())); + unit.applyEffect(type.status, type.statusIntensity); + } + } - @Override - public void collision(SolidTrait other, float x, float y){ - super.collision(other, x, y); + @Override + public void update(){ + super.update(); - if(other instanceof Unit){ - Unit unit = (Unit)other; - unit.getVelocity().add(vector.set(other.getX(), other.getY()).sub(x, y).setLength(type.knockback / unit.getMass())); - unit.applyEffect(type.status, type.statusIntensity); - } - } + if(type.hitTiles && collidesTiles() && !supressCollision){ + world.raycastEach(world.toTile(lastPosition().x), world.toTile(lastPosition().y), world.toTile(x), world.toTile(y), (x, y) -> { - @Override - public void update(){ - super.update(); + Tile tile = world.tile(x, y); + if(tile == null) return false; + tile = tile.target(); - if (type.hitTiles && collidesTiles() && !supressCollision) { - world.raycastEach(world.toTile(lastPosition().x), world.toTile(lastPosition().y), world.toTile(x), world.toTile(y), (x, y) -> { + if(tile.entity != null && tile.entity.collide(this) && !tile.entity.isDead() && tile.entity.tile.getTeam() != team){ + tile.entity.collision(this); - Tile tile = world.tile(x, y); - if (tile == null) return false; - tile = tile.target(); + if(!supressCollision){ + type.hit(this); + remove(); + } - if (tile.entity != null && tile.entity.collide(this) && !tile.entity.isDead() && tile.entity.tile.getTeam() != team) { - tile.entity.collision(this); + return true; + } - if(!supressCollision){ - type.hit(this); - remove(); - } + return false; + }); + } - return true; - } + supressCollision = false; + } - return false; - }); - } + @Override + public void reset(){ + super.reset(); + timer.clear(); + team = null; + data = null; + } - supressCollision = false; - } + @Override + public void removed(){ + Pooling.free(this); + } - @Override - public void reset() { - super.reset(); - timer.clear(); - team = null; - data = null; - } - - @Override - public void removed() { - Pooling.free(this); - } - - @Override - public EntityGroup targetGroup() { - return bulletGroup; - } + @Override + public EntityGroup targetGroup(){ + return bulletGroup; + } } diff --git a/core/src/io/anuke/mindustry/entities/bullet/BulletType.java b/core/src/io/anuke/mindustry/entities/bullet/BulletType.java index e402ab00a1..d4d750035d 100644 --- a/core/src/io/anuke/mindustry/entities/bullet/BulletType.java +++ b/core/src/io/anuke/mindustry/entities/bullet/BulletType.java @@ -9,65 +9,83 @@ import io.anuke.ucore.core.Effects; import io.anuke.ucore.entities.impl.BaseBulletType; public abstract class BulletType extends BaseBulletType implements Content{ - private static int lastid = 0; - private static Array types = new Array<>(); + private static int lastid = 0; + private static Array types = new Array<>(); - public final int id; - /**Knockback in velocity.*/ - public float knockback; - /**Whether this bullet hits tiles.*/ - public boolean hitTiles = true; - /**Status effect applied on hit.*/ - public StatusEffect status = StatusEffects.none; - /**Intensity of applied status effect in terms of duration.*/ - public float statusIntensity = 0.5f; - /**What fraction of armor is pierced, 0-1*/ - public float armorPierce = 0f; - /**Whether to sync this bullet to clients.*/ - public boolean syncable; - /**Whether this bullet type collides with tiles.*/ - public boolean collidesTiles = true; - /**Whether this bullet types collides with anything at all.*/ - public boolean collides = true; - /**Whether velocity is inherited from the shooter.*/ - public boolean keepVelocity = true; + public final int id; + /** + * Knockback in velocity. + */ + public float knockback; + /** + * Whether this bullet hits tiles. + */ + public boolean hitTiles = true; + /** + * Status effect applied on hit. + */ + public StatusEffect status = StatusEffects.none; + /** + * Intensity of applied status effect in terms of duration. + */ + public float statusIntensity = 0.5f; + /** + * What fraction of armor is pierced, 0-1 + */ + public float armorPierce = 0f; + /** + * Whether to sync this bullet to clients. + */ + public boolean syncable; + /** + * Whether this bullet type collides with tiles. + */ + public boolean collidesTiles = true; + /** + * Whether this bullet types collides with anything at all. + */ + public boolean collides = true; + /** + * Whether velocity is inherited from the shooter. + */ + public boolean keepVelocity = true; - public BulletType(float speed, float damage){ - this.id = lastid ++; - this.speed = speed; - this.damage = damage; - lifetime = 40f; - hiteffect = BulletFx.hitBulletSmall; - despawneffect = BulletFx.despawn; + public BulletType(float speed, float damage){ + this.id = lastid++; + this.speed = speed; + this.damage = damage; + lifetime = 40f; + hiteffect = BulletFx.hitBulletSmall; + despawneffect = BulletFx.despawn; - types.add(this); - } - - @Override - public void hit(Bullet b, float hitx, float hity){ - Effects.effect(hiteffect, hitx, hity, b.angle()); - } + types.add(this); + } - @Override - public void despawned(Bullet b){ - Effects.effect(despawneffect, b.x, b.y, b.angle()); - } + public static BulletType getByID(int id){ + return types.get(id); + } - @Override - public String getContentTypeName() { - return "bullettype"; - } + public static Array all(){ + return types; + } - @Override - public Array getAll() { - return types; - } + @Override + public void hit(Bullet b, float hitx, float hity){ + Effects.effect(hiteffect, hitx, hity, b.angle()); + } - public static BulletType getByID(int id){ - return types.get(id); - } + @Override + public void despawned(Bullet b){ + Effects.effect(despawneffect, b.x, b.y, b.angle()); + } - public static Array all(){ - return types; - } + @Override + public String getContentTypeName(){ + return "bullettype"; + } + + @Override + public Array getAll(){ + return types; + } } diff --git a/core/src/io/anuke/mindustry/entities/bullet/LiquidBulletType.java b/core/src/io/anuke/mindustry/entities/bullet/LiquidBulletType.java index df36debba3..3b034962d8 100644 --- a/core/src/io/anuke/mindustry/entities/bullet/LiquidBulletType.java +++ b/core/src/io/anuke/mindustry/entities/bullet/LiquidBulletType.java @@ -16,10 +16,10 @@ import io.anuke.ucore.util.Mathf; import static io.anuke.mindustry.Vars.tilesize; import static io.anuke.mindustry.Vars.world; -public class LiquidBulletType extends BulletType { +public class LiquidBulletType extends BulletType{ Liquid liquid; - public LiquidBulletType(Liquid liquid) { + public LiquidBulletType(Liquid liquid){ super(2.5f, 0); this.liquid = liquid; @@ -31,14 +31,14 @@ public class LiquidBulletType extends BulletType { } @Override - public void draw(Bullet b) { + public void draw(Bullet b){ Draw.color(liquid.color, Color.WHITE, b.fout() / 100f + Mathf.randomSeedRange(b.id, 0.1f)); - Fill.circle(b.x, b.y, 0.5f + b.fout()*2.5f); + Fill.circle(b.x, b.y, 0.5f + b.fout() * 2.5f); } @Override - public void hit(Bullet b, float hitx, float hity) { + public void hit(Bullet b, float hitx, float hity){ Effects.effect(hiteffect, liquid.color, hitx, hity); Puddle.deposit(world.tileWorld(hitx, hity), liquid, 5f); @@ -46,7 +46,7 @@ public class LiquidBulletType extends BulletType { float intensity = 400f; Fire.extinguish(world.tileWorld(hitx, hity), intensity); for(GridPoint2 p : Geometry.d4){ - Fire.extinguish(world.tileWorld(hitx + p.x*tilesize, hity + p.y*tilesize), intensity); + Fire.extinguish(world.tileWorld(hitx + p.x * tilesize, hity + p.y * tilesize), intensity); } } } diff --git a/core/src/io/anuke/mindustry/entities/bullet/MissileBulletType.java b/core/src/io/anuke/mindustry/entities/bullet/MissileBulletType.java index 20747c7fa4..6179d2e6a3 100644 --- a/core/src/io/anuke/mindustry/entities/bullet/MissileBulletType.java +++ b/core/src/io/anuke/mindustry/entities/bullet/MissileBulletType.java @@ -4,9 +4,9 @@ import io.anuke.mindustry.content.fx.BulletFx; import io.anuke.mindustry.graphics.Palette; import io.anuke.ucore.core.Effects; -public class MissileBulletType extends BasicBulletType { +public class MissileBulletType extends BasicBulletType{ - public MissileBulletType(float speed, float damage, String bulletSprite) { + public MissileBulletType(float speed, float damage, String bulletSprite){ super(speed, damage, bulletSprite); backColor = Palette.missileYellowBack; frontColor = Palette.missileYellow; @@ -14,7 +14,7 @@ public class MissileBulletType extends BasicBulletType { } @Override - public void update(Bullet b) { + public void update(Bullet b){ super.update(b); if(b.timer.get(0, 4f)){ diff --git a/core/src/io/anuke/mindustry/entities/effect/Decal.java b/core/src/io/anuke/mindustry/entities/effect/Decal.java index 762cc480e6..64c9facba6 100644 --- a/core/src/io/anuke/mindustry/entities/effect/Decal.java +++ b/core/src/io/anuke/mindustry/entities/effect/Decal.java @@ -10,24 +10,26 @@ import io.anuke.ucore.util.Mathf; import static io.anuke.mindustry.Vars.groundEffectGroup; -/**Class for creating block rubble on the ground.*/ -public abstract class Decal extends TimedEntity implements BelowLiquidTrait, DrawTrait { +/** + * Class for creating block rubble on the ground. + */ +public abstract class Decal extends TimedEntity implements BelowLiquidTrait, DrawTrait{ private static final Color color = Color.valueOf("52504e"); @Override - public float lifetime() { + public float lifetime(){ return 8200f; } @Override public void draw(){ - Draw.color(color.r, color.g, color.b, 1f-Mathf.curve(fin(), 0.98f)); + Draw.color(color.r, color.g, color.b, 1f - Mathf.curve(fin(), 0.98f)); drawDecal(); Draw.color(); } @Override - public EntityGroup targetGroup() { + public EntityGroup targetGroup(){ return groundEffectGroup; } diff --git a/core/src/io/anuke/mindustry/entities/effect/Fire.java b/core/src/io/anuke/mindustry/entities/effect/Fire.java index bd2c0f0333..e933e5dfbf 100644 --- a/core/src/io/anuke/mindustry/entities/effect/Fire.java +++ b/core/src/io/anuke/mindustry/entities/effect/Fire.java @@ -31,7 +31,7 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.*; -public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable { +public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable{ private static final IntMap map = new IntMap<>(); private static final float baseLifetime = 1000f; @@ -41,7 +41,15 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable private float baseFlammability = -1, puddleFlammability; private float lifetime; - /**Start a fire on the tile. If there already is a file there, refreshes its lifetime.*/ + /** + * Deserialization use only! + */ + public Fire(){ + } + + /** + * Start a fire on the tile. If there already is a file there, refreshes its lifetime. + */ public static void create(Tile tile){ if(Net.client() || tile == null) return; //not clientside. @@ -60,24 +68,28 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable } } - /**Attempts to extinguish a fire by shortening its life. If there is no fire here, does nothing.*/ - public static void extinguish(Tile tile, float intensity) { - if (tile != null && map.containsKey(tile.packedPosition())) { + /** + * Attempts to extinguish a fire by shortening its life. If there is no fire here, does nothing. + */ + public static void extinguish(Tile tile, float intensity){ + if(tile != null && map.containsKey(tile.packedPosition())){ map.get(tile.packedPosition()).time += intensity * Timers.delta(); } } - /**Deserialization use only!*/ - public Fire(){} + @Remote(called = Loc.server, in = In.entities) + public static void onFireRemoved(int fireid){ + fireGroup.removeByID(fireid); + } @Override - public float lifetime() { + public float lifetime(){ return lifetime; } @Override - public void update() { - if(Mathf.chance(0.1 * Timers.delta())) { + public void update(){ + if(Mathf.chance(0.1 * Timers.delta())){ Effects.effect(EnvironmentFx.fire, x + Mathf.range(4f), y + Mathf.range(4f)); } @@ -103,19 +115,19 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable float flammability = baseFlammability + puddleFlammability; if(!damage && flammability <= 0){ - time += Timers.delta()*8; + time += Timers.delta() * 8; } - if (baseFlammability < 0 || block != tile.block()){ + if(baseFlammability < 0 || block != tile.block()){ baseFlammability = tile.block().getFlammability(tile); block = tile.block(); } - if(damage) { + if(damage){ lifetime += Mathf.clamp(flammability / 8f, 0f, 0.6f) * Timers.delta(); } - if (flammability > 1f && Mathf.chance(0.03 * Timers.delta() * Mathf.clamp(flammability/5f, 0.3f, 2f))) { + if(flammability > 1f && Mathf.chance(0.03 * Timers.delta() * Mathf.clamp(flammability / 5f, 0.3f, 2f))){ GridPoint2 p = Mathf.select(Geometry.d4); Tile other = world.tile(tile.x + p.x, tile.y + p.y); create(other); @@ -128,7 +140,7 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable if(Mathf.chance(0.1 * Timers.delta())){ Puddle p = Puddle.getPuddle(tile); if(p != null){ - puddleFlammability = p.getFlammability()/3f; + puddleFlammability = p.getFlammability() / 3f; }else{ puddleFlammability = 0; } @@ -141,14 +153,14 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable } @Override - public void writeSave(DataOutput stream) throws IOException { + public void writeSave(DataOutput stream) throws IOException{ stream.writeInt(tile.packedPosition()); stream.writeFloat(lifetime); stream.writeFloat(time); } @Override - public void readSave(DataInput stream) throws IOException { + public void readSave(DataInput stream) throws IOException{ this.loadedPosition = stream.readInt(); this.lifetime = stream.readFloat(); this.time = stream.readFloat(); @@ -156,19 +168,19 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable } @Override - public void write(DataOutput data) throws IOException { + public void write(DataOutput data) throws IOException{ data.writeFloat(x); data.writeFloat(y); } @Override - public void read(DataInput data, long time) throws IOException { + public void read(DataInput data, long time) throws IOException{ x = data.readFloat(); y = data.readFloat(); } @Override - public void reset() { + public void reset(){ loadedPosition = -1; tile = null; baseFlammability = -1; @@ -176,7 +188,7 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable } @Override - public void added() { + public void added(){ if(loadedPosition != -1){ map.put(loadedPosition, this); tile = world.tile(loadedPosition); @@ -185,7 +197,7 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable } @Override - public void removed() { + public void removed(){ if(tile != null){ map.remove(tile.packedPosition()); } @@ -193,12 +205,7 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable } @Override - public EntityGroup targetGroup() { + public EntityGroup targetGroup(){ return fireGroup; } - - @Remote(called = Loc.server, in = In.entities) - public static void onFireRemoved(int fireid){ - fireGroup.removeByID(fireid); - } } diff --git a/core/src/io/anuke/mindustry/entities/effect/GroundEffectEntity.java b/core/src/io/anuke/mindustry/entities/effect/GroundEffectEntity.java index 99187ee60a..889c04a7ce 100644 --- a/core/src/io/anuke/mindustry/entities/effect/GroundEffectEntity.java +++ b/core/src/io/anuke/mindustry/entities/effect/GroundEffectEntity.java @@ -9,27 +9,29 @@ import io.anuke.ucore.entities.impl.EffectEntity; import io.anuke.ucore.function.EffectRenderer; import io.anuke.ucore.util.Mathf; -/**A ground effect contains an effect that is rendered on the ground layer as opposed to the top layer.*/ -public class GroundEffectEntity extends EffectEntity { +/** + * A ground effect contains an effect that is rendered on the ground layer as opposed to the top layer. + */ +public class GroundEffectEntity extends EffectEntity{ private boolean once; @Override public void update(){ - GroundEffect effect = (GroundEffect)this.effect; + GroundEffect effect = (GroundEffect) this.effect; - if(effect.isStatic) { + if(effect.isStatic){ time += Timers.delta(); time = Mathf.clamp(time, 0, effect.staticLife); - if (!once && time >= lifetime()) { + if(!once && time >= lifetime()){ once = true; time = 0f; Tile tile = Vars.world.tileWorld(x, y); if(tile != null && tile.floor().isLiquid){ remove(); } - } else if (once && time >= effect.staticLife) { + }else if(once && time >= effect.staticLife){ remove(); } }else{ @@ -39,7 +41,7 @@ public class GroundEffectEntity extends EffectEntity { @Override public void draw(){ - GroundEffect effect = (GroundEffect)this.effect; + GroundEffect effect = (GroundEffect) this.effect; if(once && effect.isStatic) Effects.renderEffect(id, effect, color, lifetime(), rotation, x, y, data); @@ -48,32 +50,38 @@ public class GroundEffectEntity extends EffectEntity { } @Override - public void reset() { + public void reset(){ super.reset(); once = false; } - /**An effect that is rendered on the ground layer as opposed to the top layer.*/ + /** + * An effect that is rendered on the ground layer as opposed to the top layer. + */ public static class GroundEffect extends Effect{ - /**How long this effect stays on the ground when static.*/ + /** + * How long this effect stays on the ground when static. + */ public final float staticLife; - /**If true, this effect will stop and lie on the ground for a specific duration, - * after its initial lifetime is over.*/ + /** + * If true, this effect will stop and lie on the ground for a specific duration, + * after its initial lifetime is over. + */ public final boolean isStatic; - public GroundEffect(float life, float staticLife, EffectRenderer draw) { + public GroundEffect(float life, float staticLife, EffectRenderer draw){ super(life, draw); this.staticLife = staticLife; this.isStatic = true; } - public GroundEffect(boolean isStatic, float life, EffectRenderer draw) { + public GroundEffect(boolean isStatic, float life, EffectRenderer draw){ super(life, draw); this.staticLife = 0f; this.isStatic = isStatic; } - public GroundEffect(float life, EffectRenderer draw) { + public GroundEffect(float life, EffectRenderer draw){ super(life, draw); this.staticLife = 0f; this.isStatic = false; diff --git a/core/src/io/anuke/mindustry/entities/effect/ItemDrop.java b/core/src/io/anuke/mindustry/entities/effect/ItemDrop.java index 2e4063ef8b..284b66420d 100644 --- a/core/src/io/anuke/mindustry/entities/effect/ItemDrop.java +++ b/core/src/io/anuke/mindustry/entities/effect/ItemDrop.java @@ -35,7 +35,7 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.*; -public class ItemDrop extends SolidEntity implements SaveTrait, SyncTrait, DrawTrait, VelocityTrait, TimeTrait, TargetTrait, Poolable { +public class ItemDrop extends SolidEntity implements SaveTrait, SyncTrait, DrawTrait, VelocityTrait, TimeTrait, TargetTrait, Poolable{ private static final float sinkLifetime = 80f; private Interpolator interpolator = new Interpolator(); @@ -46,6 +46,14 @@ public class ItemDrop extends SolidEntity implements SaveTrait, SyncTrait, DrawT private float time; private float sinktime; + /** + * Internal use only! + */ + public ItemDrop(){ + hitbox.setSize(5f); + hitboxTile.setSize(5f); + } + public static ItemDrop create(Item item, int amount, float x, float y, float angle){ ItemDrop drop = new ItemDrop(); drop.item = item; @@ -73,13 +81,7 @@ public class ItemDrop extends SolidEntity implements SaveTrait, SyncTrait, DrawT } } - /**Internal use only!*/ - public ItemDrop(){ - hitbox.setSize(5f); - hitboxTile.setSize(5f); - } - - public Item getItem() { + public Item getItem(){ return item; } @@ -88,66 +90,66 @@ public class ItemDrop extends SolidEntity implements SaveTrait, SyncTrait, DrawT } @Override - public boolean isDead() { + public boolean isDead(){ return !isAdded(); } @Override - public Team getTeam() { + public Team getTeam(){ return Team.none; } @Override - public float lifetime() { - return 60*60; + public float lifetime(){ + return 60 * 60; } @Override - public void time(float time) { + public void time(float time){ this.time = time; } @Override - public float time() { + public float time(){ return time; } @Override - public Vector2 getVelocity() { + public Vector2 getVelocity(){ return velocity; } @Override - public boolean collides(SolidTrait other) { + public boolean collides(SolidTrait other){ return other instanceof Player && time > 20f; } @Override - public void collision(SolidTrait other, float x, float y) { - Unit player = (Unit)other; + public void collision(SolidTrait other, float x, float y){ + Unit player = (Unit) other; if(player.inventory.canAcceptItem(item, 1)){ int used = Math.min(amount, player.inventory.capacity() - player.inventory.getItem().amount); player.inventory.addItem(item, used); amount -= used; - if(amount <= 0) { + if(amount <= 0){ CallEntity.onPickup(getID()); } } } @Override - public void draw() { - float size = itemSize * (1f - sinktime/sinkLifetime) * (1f-Mathf.curve(fin(), 0.98f)); + public void draw(){ + float size = itemSize * (1f - sinktime / sinkLifetime) * (1f - Mathf.curve(fin(), 0.98f)); Tile tile = world.tileWorld(x, y); - Draw.color(Color.WHITE, tile == null || !tile.floor().isLiquid ? Color.WHITE : tile.floor().liquidColor, sinktime/sinkLifetime); + Draw.color(Color.WHITE, tile == null || !tile.floor().isLiquid ? Color.WHITE : tile.floor().liquidColor, sinktime / sinkLifetime); Draw.rect(item.region, x, y, size, size); int stored = Mathf.clamp(amount / 6, 1, 8); - for(int i = 0; i < stored; i ++) { + for(int i = 0; i < stored; i++){ float px = stored == 1 ? 0 : Mathf.randomSeedRange(i + 1, 4f); float py = stored == 1 ? 0 : Mathf.randomSeedRange(i + 2, 4f); Draw.rect(item.region, x + px, y + py, size, size); @@ -157,8 +159,8 @@ public class ItemDrop extends SolidEntity implements SaveTrait, SyncTrait, DrawT } @Override - public void update() { - if(Net.client()) { + public void update(){ + if(Net.client()){ interpolate(); }else{ updateVelocity(0.2f); @@ -190,28 +192,28 @@ public class ItemDrop extends SolidEntity implements SaveTrait, SyncTrait, DrawT } @Override - public void reset() { + public void reset(){ time = 0f; interpolator.reset(); } @Override - public Interpolator getInterpolator() { + public Interpolator getInterpolator(){ return interpolator; } @Override - public float drawSize() { + public float drawSize(){ return 10; } @Override - public EntityGroup targetGroup() { + public EntityGroup targetGroup(){ return itemGroup; } @Override - public void writeSave(DataOutput data) throws IOException { + public void writeSave(DataOutput data) throws IOException{ data.writeFloat(x); data.writeFloat(y); data.writeByte(item.id); @@ -219,7 +221,7 @@ public class ItemDrop extends SolidEntity implements SaveTrait, SyncTrait, DrawT } @Override - public void readSave(DataInput data) throws IOException { + public void readSave(DataInput data) throws IOException{ x = data.readFloat(); y = data.readFloat(); item = Item.getByID(data.readByte()); diff --git a/core/src/io/anuke/mindustry/entities/effect/ItemTransfer.java b/core/src/io/anuke/mindustry/entities/effect/ItemTransfer.java index 2e119b99ea..b27b8abbd5 100644 --- a/core/src/io/anuke/mindustry/entities/effect/ItemTransfer.java +++ b/core/src/io/anuke/mindustry/entities/effect/ItemTransfer.java @@ -32,17 +32,22 @@ public class ItemTransfer extends TimedEntity implements DrawTrait{ private PosTrait to; private Runnable done; + public ItemTransfer(){ + } + @Remote(in = In.entities, called = Loc.server, unreliable = true) public static void transferAmmo(Item item, float x, float y, Unit to){ if(to == null) return; to.addAmmo(item); - create(item, x, y, to, () -> {}); + create(item, x, y, to, () -> { + }); } @Remote(in = In.entities, called = Loc.server, unreliable = true) public static void transferItemEffect(Item item, float x, float y, Unit to){ if(to == null) return; - create(item, x, y, to, () -> {}); + create(item, x, y, to, () -> { + }); } @Remote(in = In.entities, called = Loc.server, unreliable = true) @@ -54,8 +59,9 @@ public class ItemTransfer extends TimedEntity implements DrawTrait{ @Remote(in = In.entities, called = Loc.server) public static void transferItemTo(Item item, int amount, float x, float y, Tile tile){ if(tile == null) return; - for (int i = 0; i < Mathf.clamp(amount/3, 1, 8); i++) { - Timers.run(i*3, () -> create(item, x, y, tile, () -> {})); + for(int i = 0; i < Mathf.clamp(amount / 3, 1, 8); i++){ + Timers.run(i * 3, () -> create(item, x, y, tile, () -> { + })); } tile.entity.items.add(item, amount); } @@ -70,15 +76,13 @@ public class ItemTransfer extends TimedEntity implements DrawTrait{ tr.add(); } - public ItemTransfer(){} - @Override - public float lifetime() { + public float lifetime(){ return 60; } @Override - public void reset() { + public void reset(){ super.reset(); item = null; to = null; @@ -89,7 +93,7 @@ public class ItemTransfer extends TimedEntity implements DrawTrait{ } @Override - public void removed() { + public void removed(){ if(done != null){ threads.run(done); } @@ -97,7 +101,7 @@ public class ItemTransfer extends TimedEntity implements DrawTrait{ } @Override - public void update() { + public void update(){ if(to == null){ remove(); return; @@ -110,24 +114,24 @@ public class ItemTransfer extends TimedEntity implements DrawTrait{ } @Override - public void draw() { - float length = fslope()*6f; + public void draw(){ + float length = fslope() * 6f; float angle = current.set(x, y).sub(from).angle(); Draw.color(Palette.accent); - Lines.stroke(fslope()*2f); + Lines.stroke(fslope() * 2f); - Lines.circle(x, y, fslope()*2f); + Lines.circle(x, y, fslope() * 2f); Lines.lineAngleCenter(x, y, angle, length); - Lines.lineAngle(x, y, angle, fout()*6f); + Lines.lineAngle(x, y, angle, fout() * 6f); Draw.color(item.color); - Fill.circle(x, y, fslope()*1.5f); + Fill.circle(x, y, fslope() * 1.5f); Draw.reset(); } @Override - public EntityGroup targetGroup() { + public EntityGroup targetGroup(){ return effectGroup; } } diff --git a/core/src/io/anuke/mindustry/entities/effect/Lightning.java b/core/src/io/anuke/mindustry/entities/effect/Lightning.java index 37cd52523c..2ab599b003 100644 --- a/core/src/io/anuke/mindustry/entities/effect/Lightning.java +++ b/core/src/io/anuke/mindustry/entities/effect/Lightning.java @@ -46,7 +46,15 @@ public class Lightning extends TimedEntity implements Poolable, DrawTrait, SyncT private Color color = Palette.lancerLaser; private SeedRandom random = new SeedRandom(); - /**Create a lighting branch at a location. Use Team.none to damage everyone.*/ + /** + * For pooling use only. Do not call directly! + */ + public Lightning(){ + } + + /** + * Create a lighting branch at a location. Use Team.none to damage everyone. + */ public static void create(Team team, Effect effect, Color color, float damage, float x, float y, float targetAngle, int length){ CallEntity.createLighting(lastSeed++, team, effect, color, damage, x, y, targetAngle, length); } @@ -69,7 +77,7 @@ public class Lightning extends TimedEntity implements Poolable, DrawTrait, SyncT Units.getNearbyEnemies(team, rect, entities::add); - for(int i = 0; i < length; i ++){ + for(int i = 0; i < length; i++){ l.lines.add(new Vector2(x, y)); float fx = x, fy = y; @@ -82,15 +90,15 @@ public class Lightning extends TimedEntity implements Poolable, DrawTrait, SyncT Units.getNearbyEnemies(team, rect, entity -> { float dst = entity.distanceTo(x2, y2); - if(dst < attractRange) { + if(dst < attractRange){ angle = Mathf.slerp(angle, Angles.angle(x2, y2, entity.x, entity.y), (attractRange - dst) / attractRange / 4f); } entity.getHitbox(hitrect); - hitrect.x -= range/2f; - hitrect.y -= range/2f; - hitrect.width += range/2f; - hitrect.height += range/2f; + hitrect.x -= range / 2f; + hitrect.y -= range / 2f; + hitrect.width += range / 2f; + hitrect.height += range / 2f; if(hitrect.contains(x2, y2) || hitrect.contains(fx, fy)){ float result = damage; @@ -104,7 +112,7 @@ public class Lightning extends TimedEntity implements Poolable, DrawTrait, SyncT }); if(l.random.chance(0.1f)){ - createLighting(l.random.nextInt(), team, effect, color, damage, x2, y2, angle + l.random.range(100f), length/3); + createLighting(l.random.nextInt(), team, effect, color, damage, x2, y2, angle + l.random.range(100f), length / 3); } x = x2; @@ -115,47 +123,44 @@ public class Lightning extends TimedEntity implements Poolable, DrawTrait, SyncT l.add(); } - /**For pooling use only. Do not call directly!*/ - public Lightning(){} - @Override - public boolean isSyncing() { + public boolean isSyncing(){ return false; } @Override - public void write(DataOutput data) throws IOException { + public void write(DataOutput data) throws IOException{ } @Override - public void read(DataInput data, long time) throws IOException { + public void read(DataInput data, long time) throws IOException{ } @Override - public float lifetime() { + public float lifetime(){ return 10; } @Override - public void reset() { + public void reset(){ color = Palette.lancerLaser; lines.clear(); } @Override - public void removed() { + public void removed(){ Pooling.free(this); } @Override - public void draw() { + public void draw(){ float lx = x, ly = y; Draw.color(color, Color.WHITE, fin()); - for(int i = 0; i < lines.size; i ++){ + for(int i = 0; i < lines.size; i++){ Vector2 v = lines.get(i); - Lines.stroke(fout() * 3f + 1f-(float)i/lines.size); + Lines.stroke(fout() * 3f + 1f - (float) i / lines.size); Lines.line(lx, ly, v.x, v.y); lx = v.x; ly = v.y; @@ -164,7 +169,7 @@ public class Lightning extends TimedEntity implements Poolable, DrawTrait, SyncT } @Override - public EntityGroup targetGroup() { + public EntityGroup targetGroup(){ return bulletGroup; } } diff --git a/core/src/io/anuke/mindustry/entities/effect/Puddle.java b/core/src/io/anuke/mindustry/entities/effect/Puddle.java index 0aa7032348..1fa0cd8a0b 100644 --- a/core/src/io/anuke/mindustry/entities/effect/Puddle.java +++ b/core/src/io/anuke/mindustry/entities/effect/Puddle.java @@ -40,7 +40,7 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.puddleGroup; import static io.anuke.mindustry.Vars.world; -public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait, SyncTrait { +public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait, SyncTrait{ private static final IntMap map = new IntMap<>(); private static final float maxLiquid = 70f; private static final int maxGeneration = 2; @@ -58,17 +58,29 @@ public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait private float accepting; private byte generation; - /**Deposists a puddle between tile and source.*/ + /** + * Deserialization use only! + */ + public Puddle(){ + } + + /** + * Deposists a puddle between tile and source. + */ public static void deposit(Tile tile, Tile source, Liquid liquid, float amount){ deposit(tile, source, liquid, amount, 0); } - /**Deposists a puddle at a tile.*/ + /** + * Deposists a puddle at a tile. + */ public static void deposit(Tile tile, Liquid liquid, float amount){ deposit(tile, tile, liquid, amount, 0); } - /**Returns the puddle on the specified tile. May return null.*/ + /** + * Returns the puddle on the specified tile. May return null. + */ public static Puddle getPuddle(Tile tile){ return map.get(tile.packedPosition()); } @@ -76,11 +88,11 @@ public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait private static void deposit(Tile tile, Tile source, Liquid liquid, float amount, int generation){ if(tile.floor().isLiquid && !canStayOn(liquid, tile.floor().liquidDrop)){ reactPuddle(tile.floor().liquidDrop, liquid, amount, tile, - (tile.worldx() + source.worldx())/2f, (tile.worldy() + source.worldy())/2f); + (tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f); if(generation == 0 && Timers.get(tile, "ripple", 50)){ Effects.effect(BlockFx.ripple, tile.floor().liquidDrop.color, - (tile.worldx() + source.worldx())/2f, (tile.worldy() + source.worldy())/2f); + (tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f); } return; } @@ -93,28 +105,32 @@ public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait puddle.tile = tile; puddle.liquid = liquid; puddle.amount = amount; - puddle.generation = (byte)generation; - puddle.set((tile.worldx() + source.worldx())/2f, (tile.worldy() + source.worldy())/2f); + puddle.generation = (byte) generation; + puddle.set((tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f); puddle.add(); map.put(tile.packedPosition(), puddle); }else if(p.liquid == liquid){ p.accepting = Math.max(amount, p.accepting); - if(generation == 0 && Timers.get(p, "ripple2", 50) && p.amount >= maxLiquid/2f){ - Effects.effect(BlockFx.ripple, p.liquid.color, (tile.worldx() + source.worldx())/2f, (tile.worldy() + source.worldy())/2f); + if(generation == 0 && Timers.get(p, "ripple2", 50) && p.amount >= maxLiquid / 2f){ + Effects.effect(BlockFx.ripple, p.liquid.color, (tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f); } }else{ p.amount -= reactPuddle(p.liquid, liquid, amount, p.tile, p.x, p.y); } } - /**Returns whether the first liquid can 'stay' on the second one. - * Currently, the only place where this can happen is oil on water.*/ + /** + * Returns whether the first liquid can 'stay' on the second one. + * Currently, the only place where this can happen is oil on water. + */ private static boolean canStayOn(Liquid liquid, Liquid other){ return liquid == Liquids.oil && other == Liquids.water; } - /**Reacts two liquids together at a location.*/ + /** + * Reacts two liquids together at a location. + */ private static float reactPuddle(Liquid dest, Liquid liquid, float amount, Tile tile, float x, float y){ if((dest.flammability > 0.3f && liquid.temperature > 0.7f) || (liquid.flammability > 0.3f && dest.temperature > 0.7f)){ //flammable liquid + hot liquid @@ -126,25 +142,27 @@ public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait if(Mathf.chance(0.5f * amount)){ Effects.effect(EnvironmentFx.steam, x, y); } - return - 0.1f * amount; + return -0.1f * amount; }else if(liquid.temperature > 0.7f && dest.temperature < 0.55f){ //hot liquid poured onto cold puddle if(Mathf.chance(0.8f * amount)){ Effects.effect(EnvironmentFx.steam, x, y); } - return - 0.4f * amount; + return -0.4f * amount; } return 0f; } - /**Deserialization use only!*/ - public Puddle(){} + @Remote(called = Loc.server, in = In.entities) + public static void onPuddleRemoved(int puddleid){ + puddleGroup.removeByID(puddleid); + } public float getFlammability(){ return liquid.flammability * amount; } @Override - public void update() { + public void update(){ //no updating happens clientside if(Net.client()){ @@ -158,11 +176,11 @@ public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait amount += accepting; accepting = 0f; - if (amount >= maxLiquid / 1.5f && generation < maxGeneration) { + if(amount >= maxLiquid / 1.5f && generation < maxGeneration){ float deposited = Math.min((amount - maxLiquid / 1.5f) / 4f, 0.3f) * Timers.delta(); - for (GridPoint2 point : Geometry.d4) { + for(GridPoint2 point : Geometry.d4){ Tile other = world.tile(tile.x + point.x, tile.y + point.y); - if (other.block() == Blocks.air && other.cliffs == 0) { + if(other.block() == Blocks.air && other.cliffs == 0){ deposit(other, tile, liquid, deposited, generation + 1); amount -= deposited / 2f; //tweak to speed up/slow down puddle propagation } @@ -171,14 +189,14 @@ public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait amount = Mathf.clamp(amount, 0, maxLiquid); - if (amount <= 0f) { + if(amount <= 0f){ CallEntity.onPuddleRemoved(getID()); } } //effects-only code - if(amount >= maxLiquid/2f && updateTime <= 0f){ - Units.getNearby(rect.setSize(Mathf.clamp(amount/(maxLiquid/1.5f))*10f).setCenter(x, y), unit -> { + if(amount >= maxLiquid / 2f && updateTime <= 0f){ + Units.getNearby(rect.setSize(Mathf.clamp(amount / (maxLiquid / 1.5f)) * 10f).setCenter(x, y), unit -> { if(unit.isFlying()) return; unit.getHitbox(rect2); @@ -186,7 +204,7 @@ public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait unit.applyEffect(liquid.effect, 0.5f); - if(unit.getVelocity().len() > 0.1) { + if(unit.getVelocity().len() > 0.1){ Effects.effect(BlockFx.ripple, liquid.color, unit.x, unit.y); } }); @@ -202,30 +220,30 @@ public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait } @Override - public void draw() { + public void draw(){ seeds = id; boolean onLiquid = tile.floor().isLiquid; - float f = Mathf.clamp(amount/(maxLiquid/1.5f)); + float f = Mathf.clamp(amount / (maxLiquid / 1.5f)); float smag = onLiquid ? 0.8f : 0f; float sscl = 20f; Draw.color(Hue.shift(tmp.set(liquid.color), 2, -0.05f)); - Fill.circle(x + Mathf.sin(Timers.time() + seeds*532, sscl, smag), y + Mathf.sin(Timers.time() + seeds*53, sscl, smag), f * 8f); + Fill.circle(x + Mathf.sin(Timers.time() + seeds * 532, sscl, smag), y + Mathf.sin(Timers.time() + seeds * 53, sscl, smag), f * 8f); Angles.randLenVectors(id, 3, f * 6f, (ex, ey) -> { - Fill.circle(x + ex + Mathf.sin(Timers.time() + seeds*532, sscl, smag), - y + ey + Mathf.sin(Timers.time() + seeds*53, sscl, smag), f * 5f); - seeds ++; + Fill.circle(x + ex + Mathf.sin(Timers.time() + seeds * 532, sscl, smag), + y + ey + Mathf.sin(Timers.time() + seeds * 53, sscl, smag), f * 5f); + seeds++; }); Draw.color(); } @Override - public float drawSize() { + public float drawSize(){ return 20; } @Override - public void writeSave(DataOutput stream) throws IOException { + public void writeSave(DataOutput stream) throws IOException{ stream.writeInt(tile.packedPosition()); stream.writeFloat(x); stream.writeFloat(y); @@ -235,7 +253,7 @@ public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait } @Override - public void readSave(DataInput stream) throws IOException { + public void readSave(DataInput stream) throws IOException{ this.loadedPosition = stream.readInt(); this.x = stream.readFloat(); this.y = stream.readFloat(); @@ -246,7 +264,7 @@ public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait } @Override - public void reset() { + public void reset(){ loadedPosition = -1; tile = null; liquid = null; @@ -256,7 +274,7 @@ public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait } @Override - public void added() { + public void added(){ if(loadedPosition != -1){ map.put(loadedPosition, this); tile = world.tile(loadedPosition); @@ -264,38 +282,33 @@ public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait } @Override - public void removed() { + public void removed(){ map.remove(tile.packedPosition()); reset(); } @Override - public void write(DataOutput data) throws IOException { + public void write(DataOutput data) throws IOException{ data.writeFloat(x); data.writeFloat(y); data.writeByte(liquid.id); - data.writeShort((short)(amount * 4)); + data.writeShort((short) (amount * 4)); data.writeInt(tile.packedPosition()); } @Override - public void read(DataInput data, long time) throws IOException { + public void read(DataInput data, long time) throws IOException{ x = data.readFloat(); y = data.readFloat(); liquid = Liquid.getByID(data.readByte()); - targetAmount = data.readShort()/4f; + targetAmount = data.readShort() / 4f; tile = world.tile(data.readInt()); map.put(tile.packedPosition(), this); } @Override - public EntityGroup targetGroup() { + public EntityGroup targetGroup(){ return puddleGroup; } - - @Remote(called = Loc.server, in = In.entities) - public static void onPuddleRemoved(int puddleid){ - puddleGroup.removeByID(puddleid); - } } diff --git a/core/src/io/anuke/mindustry/entities/effect/RubbleDecal.java b/core/src/io/anuke/mindustry/entities/effect/RubbleDecal.java index 217dc9e62a..8fb5dedf98 100644 --- a/core/src/io/anuke/mindustry/entities/effect/RubbleDecal.java +++ b/core/src/io/anuke/mindustry/entities/effect/RubbleDecal.java @@ -3,10 +3,12 @@ package io.anuke.mindustry.entities.effect; import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.util.Mathf; -public class RubbleDecal extends Decal { +public class RubbleDecal extends Decal{ private int size; - /**Creates a rubble effect at a position. Provide a block size to use.*/ + /** + * Creates a rubble effect at a position. Provide a block size to use. + */ public static void create(float x, float y, int size){ RubbleDecal decal = new RubbleDecal(); decal.size = size; diff --git a/core/src/io/anuke/mindustry/entities/effect/ScorchDecal.java b/core/src/io/anuke/mindustry/entities/effect/ScorchDecal.java index 1ef31e49fc..7bfc3e369e 100644 --- a/core/src/io/anuke/mindustry/entities/effect/ScorchDecal.java +++ b/core/src/io/anuke/mindustry/entities/effect/ScorchDecal.java @@ -8,21 +8,21 @@ import io.anuke.ucore.util.Mathf; import static io.anuke.mindustry.Vars.world; -public class ScorchDecal extends Decal { +public class ScorchDecal extends Decal{ private static final int scorches = 5; private static final TextureRegion[] regions = new TextureRegion[scorches]; public static void create(float x, float y){ if(regions[0] == null){ - for (int i = 0; i < regions.length; i++) { - regions[i] = Draw.region("scorch" + (i+1)); + for(int i = 0; i < regions.length; i++){ + regions[i] = Draw.region("scorch" + (i + 1)); } } Tile tile = world.tileWorld(x, y); if(tile == null || tile.floor().liquidDrop != null) return; - + ScorchDecal decal = new ScorchDecal(); decal.set(x, y); decal.add(); @@ -31,10 +31,10 @@ public class ScorchDecal extends Decal { @Override public void drawDecal(){ - for (int i = 0; i < 5; i++) { - TextureRegion region = regions[Mathf.randomSeed(id - i, 0, scorches-1)]; + for(int i = 0; i < 5; i++){ + TextureRegion region = regions[Mathf.randomSeed(id - i, 0, scorches - 1)]; float rotation = Mathf.randomSeed(id + i, 0, 360); - float space = 1.5f + Mathf.randomSeed(id + i + 1, 0, 20)/10f; + float space = 1.5f + Mathf.randomSeed(id + i + 1, 0, 20) / 10f; Draw.grect(region, x + Angles.trnsx(rotation, space), y + Angles.trnsy(rotation, space), rotation - 90); } } diff --git a/core/src/io/anuke/mindustry/entities/effect/Shield.java b/core/src/io/anuke/mindustry/entities/effect/Shield.java index 31f7175048..da10da3fde 100644 --- a/core/src/io/anuke/mindustry/entities/effect/Shield.java +++ b/core/src/io/anuke/mindustry/entities/effect/Shield.java @@ -13,44 +13,43 @@ import io.anuke.ucore.util.Mathf; import static io.anuke.mindustry.Vars.shieldGroup; //todo re-implement -public class Shield extends BaseEntity implements DrawTrait { - public boolean active; - public boolean hitPlayers = false; - public float radius = 0f; - - private float uptime = 0f; - private final Tile tile; - - public Shield(Tile tile){ - this.tile = tile; - this.x = tile.worldx(); - this.y = tile.worldy(); - } - - public float drawSize(){ - return 150; - } - - @Override - public void update(){ - float alpha = 0.1f; - Interpolation interp = Interpolation.fade; - - if(active){ - uptime = interp.apply(uptime, 1f, alpha * Timers.delta()); - }else{ - uptime = interp.apply(uptime, 0f, alpha * Timers.delta()); - if(uptime <= 0.05f) - remove(); - } - uptime = Mathf.clamp(uptime); - - if(!(tile.block() instanceof ShieldBlock)){ - remove(); - return; - } - - ShieldBlock block = (ShieldBlock)tile.block(); +public class Shield extends BaseEntity implements DrawTrait{ + private final Tile tile; + public boolean active; + public boolean hitPlayers = false; + public float radius = 0f; + private float uptime = 0f; + + public Shield(Tile tile){ + this.tile = tile; + this.x = tile.worldx(); + this.y = tile.worldy(); + } + + public float drawSize(){ + return 150; + } + + @Override + public void update(){ + float alpha = 0.1f; + Interpolation interp = Interpolation.fade; + + if(active){ + uptime = interp.apply(uptime, 1f, alpha * Timers.delta()); + }else{ + uptime = interp.apply(uptime, 0f, alpha * Timers.delta()); + if(uptime <= 0.05f) + remove(); + } + uptime = Mathf.clamp(uptime); + + if(!(tile.block() instanceof ShieldBlock)){ + remove(); + return; + } + + ShieldBlock block = (ShieldBlock) tile.block(); /* Entities.getNearby(bulletGroup, x, y, block.shieldRadius * 2*uptime + 10, entity->{ @@ -64,39 +63,39 @@ public class Shield extends BaseEntity implements DrawTrait { } } });*/ - } - - @Override - public void draw(){ - if(!(tile.block() instanceof ShieldBlock) || radius <= 1f){ - return; - } + } - Fill.circle(x, y, drawRadius()); - } - - float drawRadius(){ - return (radius + Mathf.sin(Timers.time(), 25f, 1f)); - } - - public void removeDelay(){ - active = false; - } + @Override + public void draw(){ + if(!(tile.block() instanceof ShieldBlock) || radius <= 1f){ + return; + } - @Override - public EntityGroup targetGroup() { - return shieldGroup; - } + Fill.circle(x, y, drawRadius()); + } + + float drawRadius(){ + return (radius + Mathf.sin(Timers.time(), 25f, 1f)); + } + + public void removeDelay(){ + active = false; + } + + @Override + public EntityGroup targetGroup(){ + return shieldGroup; + } + + @Override + public void added(){ + active = true; + } + + @Override + public void removed(){ + active = false; + uptime = 0f; + } - @Override - public void added(){ - active = true; - } - - @Override - public void removed(){ - active = false; - uptime = 0f; - } - } diff --git a/core/src/io/anuke/mindustry/entities/traits/BelowLiquidTrait.java b/core/src/io/anuke/mindustry/entities/traits/BelowLiquidTrait.java index 37ee664fcb..c4abd8abf0 100644 --- a/core/src/io/anuke/mindustry/entities/traits/BelowLiquidTrait.java +++ b/core/src/io/anuke/mindustry/entities/traits/BelowLiquidTrait.java @@ -1,5 +1,7 @@ package io.anuke.mindustry.entities.traits; -/**A flag interface for marking an effect as appearing below liquids.*/ -public interface BelowLiquidTrait { +/** + * A flag interface for marking an effect as appearing below liquids. + */ +public interface BelowLiquidTrait{ } diff --git a/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java b/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java index 6a7f4d8cae..5b67677802 100644 --- a/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java +++ b/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java @@ -36,34 +36,48 @@ import java.util.Arrays; import static io.anuke.mindustry.Vars.tilesize; import static io.anuke.mindustry.Vars.world; -/**Interface for units that build, break or mine things.*/ +/** + * Interface for units that build, break or mine things. + */ public interface BuilderTrait extends Entity{ //these are not instance variables! Translator[] tmptr = {new Translator(), new Translator(), new Translator(), new Translator()}; float placeDistance = 140f; float mineDistance = 70f; - /**Returns the queue for storing build requests.*/ + /** + * Returns the queue for storing build requests. + */ Queue getPlaceQueue(); - /**Returns the tile this builder is currently mining.*/ + /** + * Returns the tile this builder is currently mining. + */ Tile getMineTile(); - /**Sets the tile this builder is currently mining.*/ + /** + * Sets the tile this builder is currently mining. + */ void setMineTile(Tile tile); - /**Returns the minining speed of this miner. 1 = standard, 0.5 = half speed, 2 = double speed, etc.*/ + /** + * Returns the minining speed of this miner. 1 = standard, 0.5 = half speed, 2 = double speed, etc. + */ float getMinePower(); - /**Build power, can be any float. 1 = builds recipes in normal time, 0 = doesn't build at all.*/ + /** + * Build power, can be any float. 1 = builds recipes in normal time, 0 = doesn't build at all. + */ float getBuildPower(Tile tile); - /**Whether this type of builder can begin creating new blocks.*/ + /** + * Whether this type of builder can begin creating new blocks. + */ default boolean canCreateBlocks(){ return true; } - default void writeBuilding(DataOutput output) throws IOException { + default void writeBuilding(DataOutput output) throws IOException{ BuildRequest request = getCurrentRequest(); if(request != null){ @@ -83,17 +97,17 @@ public interface BuilderTrait extends Entity{ } default void readBuilding(DataInput input, boolean applyChanges) throws IOException{ - synchronized (getPlaceQueue()) { + synchronized(getPlaceQueue()){ if(applyChanges) getPlaceQueue().clear(); byte type = input.readByte(); - if (type != -1) { + if(type != -1){ int position = input.readInt(); BuildRequest request; - if (type == 1) { //remove + if(type == 1){ //remove request = new BuildRequest(position % world.width(), position / world.width()); - } else { //place + }else{ //place byte recipe = input.readByte(); byte rotation = input.readByte(); request = new BuildRequest(position % world.width(), position / world.width(), rotation, Recipe.getByID(recipe)); @@ -106,17 +120,21 @@ public interface BuilderTrait extends Entity{ } } - /**Return whether this builder's place queue contains items.*/ + /** + * Return whether this builder's place queue contains items. + */ default boolean isBuilding(){ return getPlaceQueue().size != 0; } - /**If a place request matching this signature is present, it is removed. - * Otherwise, a new place request is added to the queue.*/ + /** + * If a place request matching this signature is present, it is removed. + * Otherwise, a new place request is added to the queue. + */ default void replaceBuilding(int x, int y, int rotation, Recipe recipe){ - synchronized (getPlaceQueue()) { - for (BuildRequest request : getPlaceQueue()) { - if (request.x == x && request.y == y) { + synchronized(getPlaceQueue()){ + for(BuildRequest request : getPlaceQueue()){ + if(request.x == x && request.y == y){ clearBuilding(); addBuildRequest(request); return; @@ -127,16 +145,20 @@ public interface BuilderTrait extends Entity{ addBuildRequest(new BuildRequest(x, y, rotation, recipe)); } - /**Clears the placement queue.*/ + /** + * Clears the placement queue. + */ default void clearBuilding(){ getPlaceQueue().clear(); } - /**Add another build requests to the tail of the queue, if it doesn't exist there yet.*/ + /** + * Add another build requests to the tail of the queue, if it doesn't exist there yet. + */ default void addBuildRequest(BuildRequest place){ - synchronized (getPlaceQueue()) { - for (BuildRequest request : getPlaceQueue()) { - if (request.x == place.x && request.y == place.y) { + synchronized(getPlaceQueue()){ + for(BuildRequest request : getPlaceQueue()){ + if(request.x == place.x && request.y == place.y){ return; } } @@ -144,16 +166,20 @@ public interface BuilderTrait extends Entity{ } } - /**Return the build requests currently active, or the one at the top of the queue. - * May return null.*/ + /** + * Return the build requests currently active, or the one at the top of the queue. + * May return null. + */ default BuildRequest getCurrentRequest(){ - synchronized (getPlaceQueue()) { + synchronized(getPlaceQueue()){ return getPlaceQueue().size == 0 ? null : getPlaceQueue().first(); } } - /**Update building mechanism for this unit. - * This includes mining.*/ + /** + * Update building mechanism for this unit. + * This includes mining. + */ default void updateBuilding(Unit unit){ BuildRequest current = getCurrentRequest(); @@ -176,8 +202,8 @@ public interface BuilderTrait extends Entity{ Tile tile = world.tile(current.x, current.y); - if (!(tile.block() instanceof BuildBlock)) { - if(canCreateBlocks() && !current.remove && Build.validPlace(unit.getTeam(), current.x, current.y, current.recipe.result, current.rotation)) { + if(!(tile.block() instanceof BuildBlock)){ + if(canCreateBlocks() && !current.remove && Build.validPlace(unit.getTeam(), current.x, current.y, current.recipe.result, current.rotation)){ Build.beginPlace(unit.getTeam(), current.x, current.y, current.recipe, current.rotation); }else if(canCreateBlocks() && current.remove && Build.validBreak(unit.getTeam(), current.x, current.y)){ Build.beginBreak(unit.getTeam(), current.x, current.y); @@ -203,7 +229,9 @@ public interface BuilderTrait extends Entity{ current.progress = entity.progress(); } - /**Do not call directly.*/ + /** + * Do not call directly. + */ default void updateMining(Unit unit){ Tile tile = getMineTile(); @@ -216,26 +244,28 @@ public interface BuilderTrait extends Entity{ if(unit.inventory.canAcceptItem(item) && Mathf.chance(Timers.delta() * (0.06 - item.hardness * 0.01) * getMinePower())){ CallEntity.transferItemToUnit(item, - tile.worldx() + Mathf.range(tilesize/2f), - tile.worldy() + Mathf.range(tilesize/2f), + tile.worldx() + Mathf.range(tilesize / 2f), + tile.worldy() + Mathf.range(tilesize / 2f), unit); } if(Mathf.chance(0.06 * Timers.delta())){ Effects.effect(BlockFx.pulverizeSmall, - tile.worldx() + Mathf.range(tilesize/2f), - tile.worldy() + Mathf.range(tilesize/2f), 0f, item.color); + tile.worldx() + Mathf.range(tilesize / 2f), + tile.worldy() + Mathf.range(tilesize / 2f), 0f, item.color); } } } - /**Draw placement effects for an entity. This includes mining*/ + /** + * Draw placement effects for an entity. This includes mining + */ default void drawBuilding(Unit unit){ BuildRequest request; - synchronized (getPlaceQueue()) { - if (!isBuilding()) { - if (getMineTile() != null) { + synchronized(getPlaceQueue()){ + if(!isBuilding()){ + if(getMineTile() != null){ drawMining(unit); } return; @@ -255,7 +285,7 @@ public interface BuilderTrait extends Entity{ float px = unit.x + Angles.trnsx(unit.rotation, focusLen); float py = unit.y + Angles.trnsy(unit.rotation, focusLen); - float sz = Vars.tilesize*tile.block().size/2f; + float sz = Vars.tilesize * tile.block().size / 2f; float ang = unit.angleTo(tile); tmptr[0].set(tile.drawx() - sz, tile.drawy() - sz); @@ -286,14 +316,16 @@ public interface BuilderTrait extends Entity{ Draw.color(); } - /**Internal use only.*/ + /** + * Internal use only. + */ default void drawMining(Unit unit){ Tile tile = getMineTile(); if(tile == null) return; float focusLen = 4f + Mathf.absin(Timers.time(), 1.1f, 0.5f); - float swingScl = 12f, swingMag = tilesize/8f; + float swingScl = 12f, swingMag = tilesize / 8f; float flashScl = 0.3f; float px = unit.x + Angles.trnsx(unit.rotation, focusLen); @@ -302,10 +334,10 @@ public interface BuilderTrait extends Entity{ float ex = tile.worldx() + Mathf.sin(Timers.time() + 48, swingScl, swingMag); float ey = tile.worldy() + Mathf.sin(Timers.time() + 48, swingScl + 2f, swingMag); - Draw.color(Color.LIGHT_GRAY, Color.WHITE, 1f-flashScl + Mathf.absin(Timers.time(), 0.5f, flashScl)); + Draw.color(Color.LIGHT_GRAY, Color.WHITE, 1f - flashScl + Mathf.absin(Timers.time(), 0.5f, flashScl)); Shapes.laser("minelaser", "minelaser-end", px, py, ex, ey); - if(unit instanceof Player && ((Player) unit).isLocal) { + if(unit instanceof Player && ((Player) unit).isLocal){ Draw.color(Palette.accent); Lines.poly(tile.worldx(), tile.worldy(), 4, tilesize / 2f * Mathf.sqrt2, Timers.time()); } @@ -313,16 +345,20 @@ public interface BuilderTrait extends Entity{ Draw.color(); } - /**Class for storing build requests. Can be either a place or remove request.*/ - class BuildRequest { + /** + * Class for storing build requests. Can be either a place or remove request. + */ + class BuildRequest{ public final int x, y, rotation; public final Recipe recipe; public final boolean remove; public float progress; - /**This creates a build request.*/ - public BuildRequest(int x, int y, int rotation, Recipe recipe) { + /** + * This creates a build request. + */ + public BuildRequest(int x, int y, int rotation, Recipe recipe){ this.x = x; this.y = y; this.rotation = rotation; @@ -330,8 +366,10 @@ public interface BuilderTrait extends Entity{ this.remove = false; } - /**This creates a remove request.*/ - public BuildRequest(int x, int y) { + /** + * This creates a remove request. + */ + public BuildRequest(int x, int y){ this.x = x; this.y = y; this.rotation = -1; diff --git a/core/src/io/anuke/mindustry/entities/traits/CarriableTrait.java b/core/src/io/anuke/mindustry/entities/traits/CarriableTrait.java index c21bd5ccf5..296b782154 100644 --- a/core/src/io/anuke/mindustry/entities/traits/CarriableTrait.java +++ b/core/src/io/anuke/mindustry/entities/traits/CarriableTrait.java @@ -8,6 +8,7 @@ public interface CarriableTrait extends TeamTrait, TargetTrait, SolidTrait{ return getCarrier() != null; } - void setCarrier(CarryTrait carrier); CarryTrait getCarrier(); + + void setCarrier(CarryTrait carrier); } diff --git a/core/src/io/anuke/mindustry/entities/traits/CarryTrait.java b/core/src/io/anuke/mindustry/entities/traits/CarryTrait.java index a9150df1bd..63c10a4578 100644 --- a/core/src/io/anuke/mindustry/entities/traits/CarryTrait.java +++ b/core/src/io/anuke/mindustry/entities/traits/CarryTrait.java @@ -10,28 +10,6 @@ import io.anuke.ucore.core.Effects; import io.anuke.ucore.entities.trait.SolidTrait; public interface CarryTrait extends TeamTrait, SolidTrait, TargetTrait{ - /**Returns the thing this carrier is carrying.*/ - CarriableTrait getCarry(); - /**Sets the carrying unit. Internal use only! Use {@link #carry(CarriableTrait)} to set state.*/ - void setCarry(CarriableTrait unit); - /**Returns maximum mass this carrier can carry.*/ - float getCarryWeight(); - - /**Drops the unit that is being carried, if applicable.*/ - default void dropCarry(){ - carry(null); - } - - default void dropCarryLocal(){ - setCarryOf(null, this, null); - } - - /**Do not override unless absolutely necessary. - * Carries a unit. To drop a unit, call with {@code null}.*/ - default void carry(CarriableTrait unit){ - CallEntity.setCarryOf(this instanceof Player ? (Player)this : null, this, unit); - } - @Remote(called = Loc.both, targets = Loc.both, forward = true, in = In.entities) static void dropSelf(Player player){ if(player.getCarrier() != null){ @@ -62,4 +40,38 @@ public interface CarryTrait extends TeamTrait, SolidTrait, TargetTrait{ Effects.effect(UnitFx.unitPickup, trait); } } + + /** + * Returns the thing this carrier is carrying. + */ + CarriableTrait getCarry(); + + /** + * Sets the carrying unit. Internal use only! Use {@link #carry(CarriableTrait)} to set state. + */ + void setCarry(CarriableTrait unit); + + /** + * Returns maximum mass this carrier can carry. + */ + float getCarryWeight(); + + /** + * Drops the unit that is being carried, if applicable. + */ + default void dropCarry(){ + carry(null); + } + + default void dropCarryLocal(){ + setCarryOf(null, this, null); + } + + /** + * Do not override unless absolutely necessary. + * Carries a unit. To drop a unit, call with {@code null}. + */ + default void carry(CarriableTrait unit){ + CallEntity.setCarryOf(this instanceof Player ? (Player) this : null, this, unit); + } } diff --git a/core/src/io/anuke/mindustry/entities/traits/InventoryTrait.java b/core/src/io/anuke/mindustry/entities/traits/InventoryTrait.java index 506ca6bd6a..c84eaea6b4 100644 --- a/core/src/io/anuke/mindustry/entities/traits/InventoryTrait.java +++ b/core/src/io/anuke/mindustry/entities/traits/InventoryTrait.java @@ -2,6 +2,6 @@ package io.anuke.mindustry.entities.traits; import io.anuke.mindustry.entities.UnitInventory; -public interface InventoryTrait { +public interface InventoryTrait{ UnitInventory getInventory(); } diff --git a/core/src/io/anuke/mindustry/entities/traits/RepairTrait.java b/core/src/io/anuke/mindustry/entities/traits/RepairTrait.java index 52e9a1c86c..4712730e4c 100644 --- a/core/src/io/anuke/mindustry/entities/traits/RepairTrait.java +++ b/core/src/io/anuke/mindustry/entities/traits/RepairTrait.java @@ -3,7 +3,7 @@ package io.anuke.mindustry.entities.traits; import io.anuke.ucore.entities.trait.HealthTrait; //TODO implement -public interface RepairTrait extends TeamTrait { +public interface RepairTrait extends TeamTrait{ HealthTrait getRepairing(); diff --git a/core/src/io/anuke/mindustry/entities/traits/SaveTrait.java b/core/src/io/anuke/mindustry/entities/traits/SaveTrait.java index 94590db2fc..4e7009771e 100644 --- a/core/src/io/anuke/mindustry/entities/traits/SaveTrait.java +++ b/core/src/io/anuke/mindustry/entities/traits/SaveTrait.java @@ -2,6 +2,8 @@ package io.anuke.mindustry.entities.traits; import io.anuke.ucore.entities.trait.Entity; -/**Marks an entity as serializable.*/ +/** + * Marks an entity as serializable. + */ public interface SaveTrait extends Entity, TypeTrait, Saveable{ } diff --git a/core/src/io/anuke/mindustry/entities/traits/Saveable.java b/core/src/io/anuke/mindustry/entities/traits/Saveable.java index fee2a3123b..6f3950bcb0 100644 --- a/core/src/io/anuke/mindustry/entities/traits/Saveable.java +++ b/core/src/io/anuke/mindustry/entities/traits/Saveable.java @@ -4,7 +4,8 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; -public interface Saveable { +public interface Saveable{ void writeSave(DataOutput stream) throws IOException; + void readSave(DataInput stream) throws IOException; } diff --git a/core/src/io/anuke/mindustry/entities/traits/ShooterTrait.java b/core/src/io/anuke/mindustry/entities/traits/ShooterTrait.java index c51eae473d..12df85dda1 100644 --- a/core/src/io/anuke/mindustry/entities/traits/ShooterTrait.java +++ b/core/src/io/anuke/mindustry/entities/traits/ShooterTrait.java @@ -7,6 +7,8 @@ import io.anuke.ucore.util.Timer; public interface ShooterTrait extends VelocityTrait, TeamTrait, InventoryTrait{ Timer getTimer(); + int getShootTimer(boolean left); + Weapon getWeapon(); } diff --git a/core/src/io/anuke/mindustry/entities/traits/SpawnerTrait.java b/core/src/io/anuke/mindustry/entities/traits/SpawnerTrait.java index 1fd4a9daf0..55c1763b1f 100644 --- a/core/src/io/anuke/mindustry/entities/traits/SpawnerTrait.java +++ b/core/src/io/anuke/mindustry/entities/traits/SpawnerTrait.java @@ -3,8 +3,10 @@ package io.anuke.mindustry.entities.traits; import io.anuke.mindustry.entities.Unit; import io.anuke.mindustry.world.Tile; -public interface SpawnerTrait { +public interface SpawnerTrait{ Tile getTile(); + void updateSpawning(Unit unit); + float getSpawnProgress(); } diff --git a/core/src/io/anuke/mindustry/entities/traits/SyncTrait.java b/core/src/io/anuke/mindustry/entities/traits/SyncTrait.java index 471b3226c4..56bd43f948 100644 --- a/core/src/io/anuke/mindustry/entities/traits/SyncTrait.java +++ b/core/src/io/anuke/mindustry/entities/traits/SyncTrait.java @@ -10,18 +10,22 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.threads; -public interface SyncTrait extends Entity, TypeTrait { +public interface SyncTrait extends Entity, TypeTrait{ - /**Whether smoothing of entities is enabled when using multithreading; not yet implemented.*/ + /** + * Whether smoothing of entities is enabled when using multithreading; not yet implemented. + */ static boolean isSmoothing(){ return threads.isEnabled() && threads.getTPS() <= Gdx.graphics.getFramesPerSecond() / 2f; } - /**Sets the position of this entity and updated the interpolator.*/ + /** + * Sets the position of this entity and updated the interpolator. + */ default void setNet(float x, float y){ set(x, y); - if(getInterpolator() != null) { + if(getInterpolator() != null){ getInterpolator().target.set(x, y); getInterpolator().last.set(x, y); getInterpolator().pos.set(0, 0); @@ -30,9 +34,12 @@ public interface SyncTrait extends Entity, TypeTrait { } } - /**Interpolate entity position only. Override if you need to interpolate rotations or other values.*/ + /** + * Interpolate entity position only. Override if you need to interpolate rotations or other values. + */ default void interpolate(){ - if(getInterpolator() == null) throw new RuntimeException("This entity must have an interpolator to interpolate()!"); + if(getInterpolator() == null) + throw new RuntimeException("This entity must have an interpolator to interpolate()!"); getInterpolator().update(); @@ -40,17 +47,22 @@ public interface SyncTrait extends Entity, TypeTrait { setY(getInterpolator().pos.y); } - /**Return the interpolator used for smoothing the position. Optional.*/ + /** + * Return the interpolator used for smoothing the position. Optional. + */ default Interpolator getInterpolator(){ return null; } - /**Whether syncing is enabled for this entity; true by default.*/ + /** + * Whether syncing is enabled for this entity; true by default. + */ default boolean isSyncing(){ return true; } //Read and write sync data, usually position void write(DataOutput data) throws IOException; + void read(DataInput data, long time) throws IOException; } diff --git a/core/src/io/anuke/mindustry/entities/traits/TargetTrait.java b/core/src/io/anuke/mindustry/entities/traits/TargetTrait.java index 488f853feb..f63cca760f 100644 --- a/core/src/io/anuke/mindustry/entities/traits/TargetTrait.java +++ b/core/src/io/anuke/mindustry/entities/traits/TargetTrait.java @@ -1,16 +1,21 @@ package io.anuke.mindustry.entities.traits; import io.anuke.mindustry.game.Team; -import io.anuke.ucore.entities.trait.VelocityTrait; import io.anuke.ucore.entities.trait.PosTrait; +import io.anuke.ucore.entities.trait.VelocityTrait; -/**Base interface for targetable entities.*/ -public interface TargetTrait extends PosTrait, VelocityTrait { +/** + * Base interface for targetable entities. + */ +public interface TargetTrait extends PosTrait, VelocityTrait{ boolean isDead(); + Team getTeam(); - /**Whether this entity is a valid target.*/ + /** + * Whether this entity is a valid target. + */ default boolean isValid(){ return !isDead(); } diff --git a/core/src/io/anuke/mindustry/entities/traits/TeamTrait.java b/core/src/io/anuke/mindustry/entities/traits/TeamTrait.java index a02c3e902c..3c7cbf7dd3 100644 --- a/core/src/io/anuke/mindustry/entities/traits/TeamTrait.java +++ b/core/src/io/anuke/mindustry/entities/traits/TeamTrait.java @@ -3,6 +3,6 @@ package io.anuke.mindustry.entities.traits; import io.anuke.mindustry.game.Team; import io.anuke.ucore.entities.trait.Entity; -public interface TeamTrait extends Entity { +public interface TeamTrait extends Entity{ Team getTeam(); } diff --git a/core/src/io/anuke/mindustry/entities/traits/TypeTrait.java b/core/src/io/anuke/mindustry/entities/traits/TypeTrait.java index 076c2d05b3..9bde9509fc 100644 --- a/core/src/io/anuke/mindustry/entities/traits/TypeTrait.java +++ b/core/src/io/anuke/mindustry/entities/traits/TypeTrait.java @@ -4,12 +4,14 @@ import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.ObjectIntMap; import io.anuke.ucore.function.Supplier; -public interface TypeTrait { +public interface TypeTrait{ int[] lastRegisteredID = {0}; Array> registeredTypes = new Array<>(); ObjectIntMap> typeToID = new ObjectIntMap<>(); - /**Register and return a type ID. The supplier should return a fresh instace of that type.*/ + /** + * Register and return a type ID. The supplier should return a fresh instace of that type. + */ static void registerType(Class type, Supplier supplier){ if(typeToID.get(type, -1) != -1){ throw new RuntimeException("Type is already registered: '" + type + "'!"); @@ -18,10 +20,12 @@ public interface TypeTrait { registeredTypes.add(supplier); int result = lastRegisteredID[0]; typeToID.put(type, result); - lastRegisteredID[0] ++; + lastRegisteredID[0]++; } - /**Registers a syncable type by ID.*/ + /** + * Registers a syncable type by ID. + */ static Supplier getTypeByID(int id){ if(id == -1){ throw new IllegalArgumentException("Attempt to retrieve invalid entity type ID! Did you forget to set it in ContentLoader.registerTypes()?"); @@ -29,11 +33,14 @@ public interface TypeTrait { return registeredTypes.get(id); } - /**Returns the type ID of this entity used for intstantiation. Should be < BYTE_MAX. - * Do not override!*/ + /** + * Returns the type ID of this entity used for intstantiation. Should be < BYTE_MAX. + * Do not override! + */ default int getTypeID(){ int id = typeToID.get(getClass(), -1); - if(id == -1) throw new RuntimeException("Class of type '" + getClass() + "' is not registered! Did you forget to register it in ContentLoader#registerTypes()?"); + if(id == -1) + throw new RuntimeException("Class of type '" + getClass() + "' is not registered! Did you forget to register it in ContentLoader#registerTypes()?"); return id; } } diff --git a/core/src/io/anuke/mindustry/entities/units/BaseUnit.java b/core/src/io/anuke/mindustry/entities/units/BaseUnit.java index c76d49cbeb..51a2e11c9a 100644 --- a/core/src/io/anuke/mindustry/entities/units/BaseUnit.java +++ b/core/src/io/anuke/mindustry/entities/units/BaseUnit.java @@ -30,7 +30,10 @@ import io.anuke.ucore.core.Effects; import io.anuke.ucore.core.Timers; import io.anuke.ucore.entities.EntityGroup; import io.anuke.ucore.graphics.Draw; -import io.anuke.ucore.util.*; +import io.anuke.ucore.util.Angles; +import io.anuke.ucore.util.Geometry; +import io.anuke.ucore.util.Mathf; +import io.anuke.ucore.util.Timer; import java.io.DataInput; import java.io.DataOutput; @@ -39,382 +42,392 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.*; public abstract class BaseUnit extends Unit implements ShooterTrait{ - protected static int timerIndex = 0; + protected static int timerIndex = 0; - protected static final int timerTarget = timerIndex++; + protected static final int timerTarget = timerIndex++; - protected static final int timerShootLeft = timerIndex++; - protected static final int timerShootRight = timerIndex++; + protected static final int timerShootLeft = timerIndex++; + protected static final int timerShootRight = timerIndex++; - protected UnitType type; - protected Timer timer = new Timer(5); - protected StateMachine state = new StateMachine(); - protected TargetTrait target; + protected UnitType type; + protected Timer timer = new Timer(5); + protected StateMachine state = new StateMachine(); + protected TargetTrait target; - protected boolean isWave; - protected Squad squad; - protected int spawner; + protected boolean isWave; + protected Squad squad; + protected int spawner; - /**Initialize the type and team of this unit. Only call once!*/ - public void init(UnitType type, Team team){ - if(this.type != null) throw new RuntimeException("This unit is already initialized!"); + /** + * internal constructor used for deserialization, DO NOT USE + */ + public BaseUnit(){ + } - this.type = type; - this.team = team; - } + @Remote(called = Loc.server, in = In.entities) + public static void onUnitDeath(BaseUnit unit){ + if(unit == null) return; - public void setSpawner(Tile tile) { - this.spawner = tile.packedPosition(); - } + if(Net.server() || !Net.active()){ + UnitDrops.dropItems(unit); + } - public UnitType getType() { - return type; - } + float explosiveness = 2f + (unit.inventory.hasItem() ? unit.inventory.getItem().item.explosiveness * unit.inventory.getItem().amount : 0f); + float flammability = (unit.inventory.hasItem() ? unit.inventory.getItem().item.flammability * unit.inventory.getItem().amount : 0f); + Damage.dynamicExplosion(unit.x, unit.y, flammability, explosiveness, 0f, unit.getSize() / 2f, Palette.darkFlame); - public Tile getSpawner(){ - return world.tile(spawner); - } + unit.onSuperDeath(); - /**internal constructor used for deserialization, DO NOT USE*/ - public BaseUnit(){} + ScorchDecal.create(unit.x, unit.y); + Effects.effect(ExplosionFx.explosion, unit); + Effects.shake(2f, 2f, unit); - /**Sets this to a 'wave' unit, which means it has slightly different AI and will not run out of ammo.*/ - public void setWave(){ - isWave = true; - } + //must run afterwards so the unit's group is not null + threads.runDelay(unit::remove); + } - public void setSquad(Squad squad) { - this.squad = squad; - squad.units ++; - } + /** + * Initialize the type and team of this unit. Only call once! + */ + public void init(UnitType type, Team team){ + if(this.type != null) throw new RuntimeException("This unit is already initialized!"); - public void rotate(float angle){ - rotation = Mathf.slerpDelta(rotation, angle, type.rotatespeed); - } + this.type = type; + this.team = team; + } - public boolean targetHasFlag(BlockFlag flag){ - return target instanceof TileEntity && - ((TileEntity)target).tile.block().flags.contains(flag); - } + public UnitType getType(){ + return type; + } - public void updateRespawning(){ - if(spawner == -1) return; + public Tile getSpawner(){ + return world.tile(spawner); + } - Tile tile = world.tile(spawner); - if(tile != null && tile.entity != null){ - if(tile.entity instanceof SpawnerTrait){ - ((SpawnerTrait) tile.entity).updateSpawning(this); + public void setSpawner(Tile tile){ + this.spawner = tile.packedPosition(); + } + + /** + * Sets this to a 'wave' unit, which means it has slightly different AI and will not run out of ammo. + */ + public void setWave(){ + isWave = true; + } + + public void setSquad(Squad squad){ + this.squad = squad; + squad.units++; + } + + public void rotate(float angle){ + rotation = Mathf.slerpDelta(rotation, angle, type.rotatespeed); + } + + public boolean targetHasFlag(BlockFlag flag){ + return target instanceof TileEntity && + ((TileEntity) target).tile.block().flags.contains(flag); + } + + public void updateRespawning(){ + if(spawner == -1) return; + + Tile tile = world.tile(spawner); + if(tile != null && tile.entity != null){ + if(tile.entity instanceof SpawnerTrait){ + ((SpawnerTrait) tile.entity).updateSpawning(this); } - }else{ - spawner = -1; - } - } - - public void setState(UnitState state){ - this.state.set(state); - } - - public void retarget(Runnable run){ - if(timer.get(timerTarget, 20)){ - run.run(); - } - } - - /**Only runs when the unit has a target.*/ - public void behavior(){ - - } - - public void updateTargeting(){ - if(target == null || (target instanceof Unit && (target.isDead() || target.getTeam() == team)) - || (target instanceof TileEntity && ((TileEntity) target).tile.entity == null)){ - target = null; - } - } - public void targetClosestAllyFlag(BlockFlag flag){ - Tile target = Geometry.findClosest(x, y, world.indexer().getAllied(team, flag)); - if (target != null) this.target = target.entity; - } - - public void targetClosestEnemyFlag(BlockFlag flag){ - Tile target = Geometry.findClosest(x, y, world.indexer().getEnemy(team, flag)); - if (target != null) this.target = target.entity; - } - - public void targetClosest(){ - target = Units.getClosestTarget(team, x, y, inventory.getAmmoRange()); - } - - public TileEntity getClosestEnemyCore(){ - if(Vars.state.teams.has(team)){ - ObjectSet datas = Vars.state.teams.enemyDataOf(team); - - for(TeamData data : datas){ - Tile tile = Geometry.findClosest(x, y, data.cores); - if(tile != null){ - return tile.entity; - } - } - } - return null; - } - - public UnitState getStartState(){ - return null; - } - - protected void drawItems(){ - float backTrns = 4f, itemSize = 5f; - if(inventory.hasItem()){ - ItemStack stack = inventory.getItem(); - int stored = Mathf.clamp(stack.amount / 6, 1, 8); - - for(int i = 0; i < stored; i ++) { - float angT = i == 0 ? 0 : Mathf.randomSeedRange(i + 2, 60f); - float lenT = i == 0 ? 0 : Mathf.randomSeedRange(i + 3, 1f) - 1f; - Draw.rect(stack.item.region, - x + Angles.trnsx(rotation + 180f + angT, backTrns + lenT), - y + Angles.trnsy(rotation + 180f + angT, backTrns + lenT), - itemSize, itemSize, rotation); - } - } - } - - @Override - public boolean isValid() { - return super.isValid() && isAdded(); - } - - @Override - public Timer getTimer() { - return timer; - } - - @Override - public int getShootTimer(boolean left) { - return left ? timerShootLeft : timerShootRight; - } - - @Override - public Weapon getWeapon() { - return type.weapon; - } - - @Override - public TextureRegion getIconRegion() { - return type.iconRegion; - } - - @Override - public int getItemCapacity() { - return type.itemCapacity; - } - - @Override - public int getAmmoCapacity() { - return type.ammoCapacity; - } - - @Override - public boolean isInfiniteAmmo() { - return isWave; - } - - @Override - public void interpolate() { - super.interpolate(); - - if(interpolator.values.length > 0){ - rotation = interpolator.values[0]; - } - } - - @Override - public float maxHealth() { - return type.health; - } - - @Override - public float getArmor() { - return type.armor; - } - - @Override - public boolean acceptsAmmo(Item item) { - return getWeapon().getAmmoType(item) != null && inventory.canAcceptAmmo(getWeapon().getAmmoType(item)); - } - - @Override - public void addAmmo(Item item) { - inventory.addAmmo(getWeapon().getAmmoType(item)); - } - - @Override - public float getSize() { - return 8; - } - - @Override - public float getMass() { - return type.mass; - } - - @Override - public boolean isFlying() { - return type.isFlying; - } - - @Override - public void update(){ - if(hitTime > 0){ - hitTime -= Timers.delta(); - } - - if(hitTime < 0) hitTime = 0; - - if(isDead()){ - updateRespawning(); - return; - } - - if(Net.client()){ - interpolate(); - status.update(this); - return; - } - - if(!Net.client()){ - avoidOthers(8f); - } - - if(squad != null){ - squad.update(); - } - - updateTargeting(); - - state.update(); - updateVelocityStatus(type.drag, type.maxVelocity); - - if(target != null) behavior(); - - if(!isWave) { - x = Mathf.clamp(x, 0, world.width() * tilesize); - y = Mathf.clamp(y, 0, world.height() * tilesize); - } - } - - @Override - public void draw(){ - - } - - @Override - public void drawUnder(){ - - } - - @Override - public void drawOver(){ - - } - - @Override - public void removed() { - Tile tile = world.tile(spawner); - - if(tile != null && tile.entity instanceof UnitFactoryEntity){ - UnitFactoryEntity factory = (UnitFactoryEntity)tile.entity; - factory.hasSpawned = false; - } - - spawner = -1; - } - - @Override - public float drawSize(){ - return 14; - } - - @Override - public void onDeath(){ - CallEntity.onUnitDeath(this); - } - - @Override - public void added(){ - hitbox.setSize(type.hitsize); - hitboxTile.setSize(type.hitsizeTile); - state.set(getStartState()); - - health(maxHealth()); - } - - @Override - public EntityGroup targetGroup() { - return unitGroups[team.ordinal()]; - } - - @Override - public void writeSave(DataOutput stream) throws IOException { - super.writeSave(stream); - stream.writeByte(type.id); - stream.writeBoolean(isWave); - stream.writeInt(spawner); - } - - @Override - public void readSave(DataInput stream) throws IOException { - super.readSave(stream); - byte type = stream.readByte(); - this.isWave = stream.readBoolean(); - this.spawner = stream.readInt(); - - this.type = UnitType.getByID(type); - add(); - } - - @Override - public void write(DataOutput data) throws IOException{ - super.writeSave(data); - data.writeByte(type.id); - data.writeInt(spawner); - } - - @Override - public void read(DataInput data, long time) throws IOException{ - float lastx = x, lasty = y, lastrot = rotation; - super.readSave(data); - this.type = UnitType.getByID(data.readByte()); - this.spawner = data.readInt(); - - interpolator.read(lastx, lasty, x, y, time, rotation); - rotation = lastrot; - } - - public void onSuperDeath(){ - super.onDeath(); - } - - @Remote(called = Loc.server, in = In.entities) - public static void onUnitDeath(BaseUnit unit){ - if(unit == null) return; - - if(Net.server() || !Net.active()){ - UnitDrops.dropItems(unit); - } - - float explosiveness = 2f + (unit.inventory.hasItem() ? unit.inventory.getItem().item.explosiveness * unit.inventory.getItem().amount : 0f); - float flammability = (unit.inventory.hasItem() ? unit.inventory.getItem().item.flammability * unit.inventory.getItem().amount : 0f); - Damage.dynamicExplosion(unit.x, unit.y, flammability, explosiveness, 0f, unit.getSize()/2f, Palette.darkFlame); - - unit.onSuperDeath(); - - ScorchDecal.create(unit.x, unit.y); - Effects.effect(ExplosionFx.explosion, unit); - Effects.shake(2f, 2f, unit); - - //must run afterwards so the unit's group is not null - threads.runDelay(unit::remove); - } + }else{ + spawner = -1; + } + } + + public void setState(UnitState state){ + this.state.set(state); + } + + public void retarget(Runnable run){ + if(timer.get(timerTarget, 20)){ + run.run(); + } + } + + /** + * Only runs when the unit has a target. + */ + public void behavior(){ + + } + + public void updateTargeting(){ + if(target == null || (target instanceof Unit && (target.isDead() || target.getTeam() == team)) + || (target instanceof TileEntity && ((TileEntity) target).tile.entity == null)){ + target = null; + } + } + + public void targetClosestAllyFlag(BlockFlag flag){ + Tile target = Geometry.findClosest(x, y, world.indexer().getAllied(team, flag)); + if(target != null) this.target = target.entity; + } + + public void targetClosestEnemyFlag(BlockFlag flag){ + Tile target = Geometry.findClosest(x, y, world.indexer().getEnemy(team, flag)); + if(target != null) this.target = target.entity; + } + + public void targetClosest(){ + target = Units.getClosestTarget(team, x, y, inventory.getAmmoRange()); + } + + public TileEntity getClosestEnemyCore(){ + if(Vars.state.teams.has(team)){ + ObjectSet datas = Vars.state.teams.enemyDataOf(team); + + for(TeamData data : datas){ + Tile tile = Geometry.findClosest(x, y, data.cores); + if(tile != null){ + return tile.entity; + } + } + } + return null; + } + + public UnitState getStartState(){ + return null; + } + + protected void drawItems(){ + float backTrns = 4f, itemSize = 5f; + if(inventory.hasItem()){ + ItemStack stack = inventory.getItem(); + int stored = Mathf.clamp(stack.amount / 6, 1, 8); + + for(int i = 0; i < stored; i++){ + float angT = i == 0 ? 0 : Mathf.randomSeedRange(i + 2, 60f); + float lenT = i == 0 ? 0 : Mathf.randomSeedRange(i + 3, 1f) - 1f; + Draw.rect(stack.item.region, + x + Angles.trnsx(rotation + 180f + angT, backTrns + lenT), + y + Angles.trnsy(rotation + 180f + angT, backTrns + lenT), + itemSize, itemSize, rotation); + } + } + } + + @Override + public boolean isValid(){ + return super.isValid() && isAdded(); + } + + @Override + public Timer getTimer(){ + return timer; + } + + @Override + public int getShootTimer(boolean left){ + return left ? timerShootLeft : timerShootRight; + } + + @Override + public Weapon getWeapon(){ + return type.weapon; + } + + @Override + public TextureRegion getIconRegion(){ + return type.iconRegion; + } + + @Override + public int getItemCapacity(){ + return type.itemCapacity; + } + + @Override + public int getAmmoCapacity(){ + return type.ammoCapacity; + } + + @Override + public boolean isInfiniteAmmo(){ + return isWave; + } + + @Override + public void interpolate(){ + super.interpolate(); + + if(interpolator.values.length > 0){ + rotation = interpolator.values[0]; + } + } + + @Override + public float maxHealth(){ + return type.health; + } + + @Override + public float getArmor(){ + return type.armor; + } + + @Override + public boolean acceptsAmmo(Item item){ + return getWeapon().getAmmoType(item) != null && inventory.canAcceptAmmo(getWeapon().getAmmoType(item)); + } + + @Override + public void addAmmo(Item item){ + inventory.addAmmo(getWeapon().getAmmoType(item)); + } + + @Override + public float getSize(){ + return 8; + } + + @Override + public float getMass(){ + return type.mass; + } + + @Override + public boolean isFlying(){ + return type.isFlying; + } + + @Override + public void update(){ + if(hitTime > 0){ + hitTime -= Timers.delta(); + } + + if(hitTime < 0) hitTime = 0; + + if(isDead()){ + updateRespawning(); + return; + } + + if(Net.client()){ + interpolate(); + status.update(this); + return; + } + + if(!Net.client()){ + avoidOthers(8f); + } + + if(squad != null){ + squad.update(); + } + + updateTargeting(); + + state.update(); + updateVelocityStatus(type.drag, type.maxVelocity); + + if(target != null) behavior(); + + if(!isWave){ + x = Mathf.clamp(x, 0, world.width() * tilesize); + y = Mathf.clamp(y, 0, world.height() * tilesize); + } + } + + @Override + public void draw(){ + + } + + @Override + public void drawUnder(){ + + } + + @Override + public void drawOver(){ + + } + + @Override + public void removed(){ + Tile tile = world.tile(spawner); + + if(tile != null && tile.entity instanceof UnitFactoryEntity){ + UnitFactoryEntity factory = (UnitFactoryEntity) tile.entity; + factory.hasSpawned = false; + } + + spawner = -1; + } + + @Override + public float drawSize(){ + return 14; + } + + @Override + public void onDeath(){ + CallEntity.onUnitDeath(this); + } + + @Override + public void added(){ + hitbox.setSize(type.hitsize); + hitboxTile.setSize(type.hitsizeTile); + state.set(getStartState()); + + health(maxHealth()); + } + + @Override + public EntityGroup targetGroup(){ + return unitGroups[team.ordinal()]; + } + + @Override + public void writeSave(DataOutput stream) throws IOException{ + super.writeSave(stream); + stream.writeByte(type.id); + stream.writeBoolean(isWave); + stream.writeInt(spawner); + } + + @Override + public void readSave(DataInput stream) throws IOException{ + super.readSave(stream); + byte type = stream.readByte(); + this.isWave = stream.readBoolean(); + this.spawner = stream.readInt(); + + this.type = UnitType.getByID(type); + add(); + } + + @Override + public void write(DataOutput data) throws IOException{ + super.writeSave(data); + data.writeByte(type.id); + data.writeInt(spawner); + } + + @Override + public void read(DataInput data, long time) throws IOException{ + float lastx = x, lasty = y, lastrot = rotation; + super.readSave(data); + this.type = UnitType.getByID(data.readByte()); + this.spawner = data.readInt(); + + interpolator.read(lastx, lasty, x, y, time, rotation); + rotation = lastrot; + } + + public void onSuperDeath(){ + super.onDeath(); + } } diff --git a/core/src/io/anuke/mindustry/entities/units/FlyingUnit.java b/core/src/io/anuke/mindustry/entities/units/FlyingUnit.java index b4353ca108..cdcdbf4717 100644 --- a/core/src/io/anuke/mindustry/entities/units/FlyingUnit.java +++ b/core/src/io/anuke/mindustry/entities/units/FlyingUnit.java @@ -23,7 +23,96 @@ import static io.anuke.mindustry.Vars.world; public abstract class FlyingUnit extends BaseUnit implements CarryTrait{ protected static Translator vec = new Translator(); protected static float wobblyness = 0.6f; + public final UnitState + resupply = new UnitState(){ + public void entered(){ + target = null; + } + + public void update(){ + if(inventory.totalAmmo() + 10 >= inventory.ammoCapacity()){ + state.set(attack); + }else if(!targetHasFlag(BlockFlag.resupplyPoint)){ + retarget(() -> targetClosestAllyFlag(BlockFlag.resupplyPoint)); + }else{ + circle(20f); + } + } + }, + idle = new UnitState(){ + public void update(){ + retarget(() -> { + targetClosest(); + targetClosestEnemyFlag(BlockFlag.target); + + if(target != null){ + setState(attack); + } + }); + + target = getClosestCore(); + if(target != null){ + circle(50f); + } + velocity.scl(0.8f); + } + }, + attack = new UnitState(){ + public void entered(){ + target = null; + } + + public void update(){ + if(Units.invalidateTarget(target, team, x, y)){ + target = null; + } + + if(!inventory.hasAmmo()){ + state.set(resupply); + }else if(target == null){ + retarget(() -> { + targetClosest(); + targetClosestEnemyFlag(BlockFlag.target); + targetClosestEnemyFlag(BlockFlag.producer); + + if(target == null){ + setState(idle); + } + }); + }else{ + attack(150f); + + if((Mathf.angNear(angleTo(target), rotation, 15f) || !inventory.getAmmo().bullet.keepVelocity) //bombers don't care about rotation + && distanceTo(target) < inventory.getAmmo().getRange()){ + AmmoType ammo = inventory.getAmmo(); + inventory.useAmmo(); + + Vector2 to = Predict.intercept(FlyingUnit.this, target, ammo.bullet.speed); + + getWeapon().update(FlyingUnit.this, to.x, to.y); + } + } + } + }, + retreat = new UnitState(){ + public void entered(){ + target = null; + } + + public void update(){ + if(health >= maxHealth()){ + state.set(attack); + }else if(!targetHasFlag(BlockFlag.repair)){ + retarget(() -> { + Tile target = Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.repair)); + if(target != null) FlyingUnit.this.target = target.entity; + }); + }else{ + circle(20f); + } + } + }; protected Trail trail = new Trail(8); protected CarriableTrait carrying; @@ -34,26 +123,26 @@ public abstract class FlyingUnit extends BaseUnit implements CarryTrait{ @Override public void drawShadow(){ - Draw.rect(type.region, x + elevation*elevationScale, y - elevation*elevationScale, rotation - 90); + Draw.rect(type.region, x + elevation * elevationScale, y - elevation * elevationScale, rotation - 90); } @Override - public CarriableTrait getCarry() { + public CarriableTrait getCarry(){ return carrying; } @Override - public void setCarry(CarriableTrait unit) { + public void setCarry(CarriableTrait unit){ this.carrying = unit; } @Override - public float getCarryWeight() { + public float getCarryWeight(){ return type.carryWeight; } @Override - public void update() { + public void update(){ super.update(); updateRotation(); @@ -64,7 +153,7 @@ public abstract class FlyingUnit extends BaseUnit implements CarryTrait{ } @Override - public void draw() { + public void draw(){ Draw.alpha(hitTime / hitDuration); Draw.rect(type.name, x, y, rotation - 90); @@ -75,12 +164,12 @@ public abstract class FlyingUnit extends BaseUnit implements CarryTrait{ } @Override - public void drawOver() { + public void drawOver(){ trail.draw(Palette.lightTrail, 5f); } @Override - public void behavior() { + public void behavior(){ if(health <= health * type.retreatPercent && !isWave && Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.repair)) != null){ setState(retreat); @@ -98,7 +187,7 @@ public abstract class FlyingUnit extends BaseUnit implements CarryTrait{ } @Override - public float drawSize() { + public float drawSize(){ return 60; } @@ -108,7 +197,7 @@ public abstract class FlyingUnit extends BaseUnit implements CarryTrait{ x += Mathf.sin(Timers.time() + id * 999, 25f, 0.07f); y += Mathf.cos(Timers.time() + id * 999, 25f, 0.07f); - if (velocity.len() <= 0.2f) { + if(velocity.len() <= 0.2f){ rotation += Mathf.sin(Timers.time() + id * 99, 10f, 8f); } } @@ -127,7 +216,7 @@ public abstract class FlyingUnit extends BaseUnit implements CarryTrait{ vec.set(target.getX() - x, target.getY() - y); if(vec.len() < circleLength){ - vec.rotate((circleLength-vec.len())/circleLength * 180f); + vec.rotate((circleLength - vec.len()) / circleLength * 180f); } vec.setLength(speed * Timers.delta()); @@ -140,7 +229,7 @@ public abstract class FlyingUnit extends BaseUnit implements CarryTrait{ vec.set(target.getX() - x, target.getY() - y); - float length = Mathf.clamp((distanceTo(target) - circleLength)/100f, -1f, 1f); + float length = Mathf.clamp((distanceTo(target) - circleLength) / 100f, -1f, 1f); vec.setLength(type.speed * Timers.delta() * length); if(length < 0) vec.rotate(180f); @@ -157,102 +246,11 @@ public abstract class FlyingUnit extends BaseUnit implements CarryTrait{ if(diff > 100f && vec.len() < circleLength){ vec.setAngle(velocity.angle()); }else{ - vec.setAngle(Mathf.slerpDelta(velocity.angle(), vec.angle(), 0.44f)); + vec.setAngle(Mathf.slerpDelta(velocity.angle(), vec.angle(), 0.44f)); } - vec.setLength(type.speed*Timers.delta()); + vec.setLength(type.speed * Timers.delta()); velocity.add(vec); } - - public final UnitState - - resupply = new UnitState(){ - public void entered() { - target = null; - } - - public void update() { - if(inventory.totalAmmo() + 10 >= inventory.ammoCapacity()){ - state.set(attack); - }else if(!targetHasFlag(BlockFlag.resupplyPoint)){ - retarget(() -> targetClosestAllyFlag(BlockFlag.resupplyPoint)); - }else{ - circle(20f); - } - } - }, - idle = new UnitState() { - public void update() { - retarget(() -> { - targetClosest(); - targetClosestEnemyFlag(BlockFlag.target); - - if(target != null){ - setState(attack); - } - }); - - target = getClosestCore(); - if(target != null){ - circle(50f); - } - velocity.scl(0.8f); - } - }, - attack = new UnitState(){ - public void entered() { - target = null; - } - - public void update() { - if(Units.invalidateTarget(target, team, x, y)){ - target = null; - } - - if(!inventory.hasAmmo()) { - state.set(resupply); - }else if (target == null){ - retarget(() -> { - targetClosest(); - targetClosestEnemyFlag(BlockFlag.target); - targetClosestEnemyFlag(BlockFlag.producer); - - if(target == null){ - setState(idle); - } - }); - }else{ - attack(150f); - - if ((Mathf.angNear(angleTo(target), rotation, 15f) || !inventory.getAmmo().bullet.keepVelocity) //bombers don't care about rotation - && distanceTo(target) < inventory.getAmmo().getRange()) { - AmmoType ammo = inventory.getAmmo(); - inventory.useAmmo(); - - Vector2 to = Predict.intercept(FlyingUnit.this, target, ammo.bullet.speed); - - getWeapon().update(FlyingUnit.this, to.x, to.y); - } - } - } - }, - retreat = new UnitState() { - public void entered() { - target = null; - } - - public void update() { - if(health >= maxHealth()){ - state.set(attack); - }else if(!targetHasFlag(BlockFlag.repair)){ - retarget(() -> { - Tile target = Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.repair)); - if (target != null) FlyingUnit.this.target = target.entity; - }); - }else{ - circle(20f); - } - } - }; } diff --git a/core/src/io/anuke/mindustry/entities/units/GroundUnit.java b/core/src/io/anuke/mindustry/entities/units/GroundUnit.java index 0ce352809f..5dba19a82d 100644 --- a/core/src/io/anuke/mindustry/entities/units/GroundUnit.java +++ b/core/src/io/anuke/mindustry/entities/units/GroundUnit.java @@ -26,24 +26,101 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.world; -public abstract class GroundUnit extends BaseUnit { +public abstract class GroundUnit extends BaseUnit{ protected static Translator vec = new Translator(); protected float walkTime; protected float baseRotation; + public final UnitState + + resupply = new UnitState(){ + public void entered(){ + target = null; + } + + public void update(){ + Tile tile = Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.resupplyPoint)); + + if(tile != null && distanceTo(tile) > 40){ + moveAwayFromCore(); + } + + //TODO move toward resupply point + if(isWave || inventory.totalAmmo() + 10 >= inventory.ammoCapacity()){ + state.set(attack); + } + } + }, + attack = new UnitState(){ + public void entered(){ + target = null; + } + + public void update(){ + TileEntity core = getClosestEnemyCore(); + float dst = core == null ? 0 : distanceTo(core); + + if(core != null && inventory.hasAmmo() && dst < inventory.getAmmo().getRange() / 1.1f){ + target = core; + }else{ + retarget(() -> targetClosest()); + } + + if(!inventory.hasAmmo()){ + state.set(resupply); + }else if(target != null){ + if(core != null){ + if(dst > inventory.getAmmo().getRange() * 0.5f){ + moveToCore(); + } + + }else{ + moveToCore(); + } + + if(distanceTo(target) < inventory.getAmmo().getRange()){ + rotate(angleTo(target)); + + if(Mathf.angNear(angleTo(target), rotation, 13f)){ + AmmoType ammo = inventory.getAmmo(); + + Vector2 to = Predict.intercept(GroundUnit.this, target, ammo.bullet.speed); + + getWeapon().update(GroundUnit.this, to.x, to.y); + } + } + + }else{ + moveToCore(); + } + } + }, + retreat = new UnitState(){ + public void entered(){ + target = null; + } + + public void update(){ + if(health >= health){ + state.set(attack); + } + + moveAwayFromCore(); + } + }; protected Weapon weapon; @Override - public void init(UnitType type, Team team) { + public void init(UnitType type, Team team){ super.init(type, team); this.weapon = type.weapon; } @Override - public void interpolate() { + public void interpolate(){ super.interpolate(); - if(interpolator.values.length > 1) { + if(interpolator.values.length > 1){ baseRotation = interpolator.values[1]; } } @@ -57,12 +134,12 @@ public abstract class GroundUnit extends BaseUnit { } @Override - public UnitState getStartState() { + public UnitState getStartState(){ return resupply; } @Override - public void update() { + public void update(){ super.update(); if(!velocity.isZero(0.0001f) && (target == null || !inventory.hasAmmo() || (inventory.hasAmmo() && distanceTo(target) > inventory.getAmmoRange()))){ @@ -71,12 +148,16 @@ public abstract class GroundUnit extends BaseUnit { } @Override - public Weapon getWeapon() { + public Weapon getWeapon(){ return weapon; } + public void setWeapon(Weapon weapon){ + this.weapon = weapon; + } + @Override - public void draw() { + public void draw(){ Draw.alpha(hitTime / hitDuration); float walktime = walkTime; @@ -89,25 +170,25 @@ public abstract class GroundUnit extends BaseUnit { Draw.tint(Color.WHITE, floor.liquidColor, 0.5f); } - for (int i : Mathf.signs) { + for(int i : Mathf.signs){ Draw.rect(type.legRegion, x + Angles.trnsx(baseRotation, ft * i), y + Angles.trnsy(baseRotation, ft * i), 12f * i, 12f - Mathf.clamp(ft * i, 0, 2), baseRotation - 90); } - if(floor.isLiquid) { + if(floor.isLiquid){ Draw.tint(Color.WHITE, floor.liquidColor, drownTime * 0.4f); - }else { + }else{ Draw.tint(Color.WHITE); } - Draw.rect(type.baseRegion, x, y, baseRotation- 90); + Draw.rect(type.baseRegion, x, y, baseRotation - 90); - Draw.rect(type.region, x, y, rotation -90); + Draw.rect(type.region, x, y, rotation - 90); - for (int i : Mathf.signs) { - float tra = rotation - 90, trY = - weapon.getRecoil(this, i > 0) + type.weaponOffsetY; + for(int i : Mathf.signs){ + float tra = rotation - 90, trY = -weapon.getRecoil(this, i > 0) + type.weaponOffsetY; float w = i > 0 ? -12 : 12; Draw.rect(weapon.equipRegion, x + Angles.trnsx(tra, type.weaponOffsetX * i, trY), @@ -120,35 +201,35 @@ public abstract class GroundUnit extends BaseUnit { } @Override - public void behavior() { + public void behavior(){ if(health <= health * type.retreatPercent && !isWave){ setState(retreat); } } @Override - public void updateTargeting() { + public void updateTargeting(){ super.updateTargeting(); - if(Units.invalidateTarget(target, team, x, y, Float.MAX_VALUE)){ + if(Units.invalidateTarget(target, team, x, y, Float.MAX_VALUE)){ target = null; } } @Override - public void write(DataOutput data) throws IOException { + public void write(DataOutput data) throws IOException{ super.write(data); data.writeByte(weapon.id); } @Override - public void read(DataInput data, long time) throws IOException { + public void read(DataInput data, long time) throws IOException{ super.read(data, time); weapon = Upgrade.getByID(data.readByte()); } @Override - public void writeSave(DataOutput stream) throws IOException { + public void writeSave(DataOutput stream) throws IOException{ stream.writeByte(weapon.id); super.writeSave(stream); } @@ -159,10 +240,6 @@ public abstract class GroundUnit extends BaseUnit { super.readSave(stream); } - public void setWeapon(Weapon weapon){ - this.weapon = weapon; - } - protected void moveToCore(){ Tile tile = world.tileWorld(x, y); if(tile == null) return; @@ -189,82 +266,4 @@ public abstract class GroundUnit extends BaseUnit { walkTime += Timers.delta(); velocity.add(vec); } - - public final UnitState - - resupply = new UnitState(){ - public void entered() { - target = null; - } - - public void update() { - Tile tile = Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.resupplyPoint)); - - if (tile != null && distanceTo(tile) > 40) { - moveAwayFromCore(); - } - - //TODO move toward resupply point - if(isWave || inventory.totalAmmo() + 10 >= inventory.ammoCapacity()){ - state.set(attack); - } - } - }, - attack = new UnitState(){ - public void entered() { - target = null; - } - - public void update() { - TileEntity core = getClosestEnemyCore(); - float dst = core == null ? 0 :distanceTo(core); - - if(core != null && inventory.hasAmmo() && dst < inventory.getAmmo().getRange()/1.1f){ - target = core; - }else { - retarget(() -> targetClosest()); - } - - if(!inventory.hasAmmo()) { - state.set(resupply); - }else if(target != null){ - if(core != null){ - if(dst > inventory.getAmmo().getRange() * 0.5f){ - moveToCore(); - } - - }else{ - moveToCore(); - } - - if(distanceTo(target) < inventory.getAmmo().getRange()){ - rotate(angleTo(target)); - - if (Mathf.angNear(angleTo(target), rotation, 13f)) { - AmmoType ammo = inventory.getAmmo(); - - Vector2 to = Predict.intercept(GroundUnit.this, target, ammo.bullet.speed); - - getWeapon().update(GroundUnit.this, to.x, to.y); - } - } - - }else{ - moveToCore(); - } - } - }, - retreat = new UnitState() { - public void entered() { - target = null; - } - - public void update() { - if(health >= health){ - state.set(attack); - } - - moveAwayFromCore(); - } - }; } diff --git a/core/src/io/anuke/mindustry/entities/units/Squad.java b/core/src/io/anuke/mindustry/entities/units/Squad.java index 41ad5f9d58..1068de0a6b 100644 --- a/core/src/io/anuke/mindustry/entities/units/Squad.java +++ b/core/src/io/anuke/mindustry/entities/units/Squad.java @@ -5,9 +5,11 @@ import io.anuke.ucore.util.Translator; import static io.anuke.mindustry.Vars.threads; -/**Used to group entities together, for formations and such. - * Usually, squads are used by units spawned in the same wave.*/ -public class Squad { +/** + * Used to group entities together, for formations and such. + * Usually, squads are used by units spawned in the same wave. + */ +public class Squad{ public Vector2 direction = new Translator(); public int units; diff --git a/core/src/io/anuke/mindustry/entities/units/StateMachine.java b/core/src/io/anuke/mindustry/entities/units/StateMachine.java index 082f7d114f..a676716693 100644 --- a/core/src/io/anuke/mindustry/entities/units/StateMachine.java +++ b/core/src/io/anuke/mindustry/entities/units/StateMachine.java @@ -1,13 +1,13 @@ package io.anuke.mindustry.entities.units; -public class StateMachine { +public class StateMachine{ private UnitState state; public void update(){ if(state != null) state.update(); } - public void set( UnitState next){ + public void set(UnitState next){ if(next == state) return; if(state != null) state.exited(); this.state = next; diff --git a/core/src/io/anuke/mindustry/entities/units/UnitDrops.java b/core/src/io/anuke/mindustry/entities/units/UnitDrops.java index c931cd875c..2ba240de70 100644 --- a/core/src/io/anuke/mindustry/entities/units/UnitDrops.java +++ b/core/src/io/anuke/mindustry/entities/units/UnitDrops.java @@ -6,7 +6,7 @@ import io.anuke.mindustry.entities.effect.ItemDrop; import io.anuke.mindustry.type.Item; import io.anuke.ucore.util.Mathf; -public class UnitDrops { +public class UnitDrops{ private static final int maxItems = 200; private static Item[] dropTable; @@ -19,7 +19,7 @@ public class UnitDrops { dropTable = new Item[]{Items.tungsten, Items.lead, Items.carbide}; } - for (int i = 0; i < 3; i++) { + for(int i = 0; i < 3; i++){ for(Item item : dropTable){ if(Mathf.chance(0.03)){ int amount = Mathf.random(20, 40); diff --git a/core/src/io/anuke/mindustry/entities/units/UnitState.java b/core/src/io/anuke/mindustry/entities/units/UnitState.java index c11ce3aa8b..5c5f9c64a8 100644 --- a/core/src/io/anuke/mindustry/entities/units/UnitState.java +++ b/core/src/io/anuke/mindustry/entities/units/UnitState.java @@ -1,7 +1,12 @@ package io.anuke.mindustry.entities.units; -public interface UnitState { - default void entered(){} - default void exited(){} - default void update(){} +public interface UnitState{ + default void entered(){ + } + + default void exited(){ + } + + default void update(){ + } } diff --git a/core/src/io/anuke/mindustry/entities/units/UnitType.java b/core/src/io/anuke/mindustry/entities/units/UnitType.java index d03396120b..848ef6d372 100644 --- a/core/src/io/anuke/mindustry/entities/units/UnitType.java +++ b/core/src/io/anuke/mindustry/entities/units/UnitType.java @@ -18,12 +18,9 @@ import io.anuke.ucore.util.Bundles; public class UnitType implements UnlockableContent{ private static byte lastid = 0; private static Array types = new Array<>(); - - protected final Supplier constructor; - public final String name; public final byte id; - + protected final Supplier constructor; public float health = 60; public float hitsize = 5f; public float hitsizeTile = 4f; @@ -57,44 +54,52 @@ public class UnitType implements UnlockableContent{ TypeTrait.registerType(type, mainConstructor); } + public static UnitType getByID(byte id){ + return types.get(id); + } + + public static Array all(){ + return types; + } + @Override - public void displayInfo(Table table) { + public void displayInfo(Table table){ ContentDisplay.displayUnit(table, this); } @Override - public String localizedName() { + public String localizedName(){ return Bundles.get("unit." + name + ".name"); } @Override - public TextureRegion getContentIcon() { + public TextureRegion getContentIcon(){ return iconRegion; } @Override - public void load() { + public void load(){ iconRegion = Draw.region("unit-icon-" + name); region = Draw.region(name); - if(!isFlying) { + if(!isFlying){ legRegion = Draw.region(name + "-leg"); baseRegion = Draw.region(name + "-base"); } } @Override - public String getContentTypeName() { + public String getContentTypeName(){ return "unit-type"; } @Override - public String getContentName() { + public String getContentName(){ return name; } @Override - public Array getAll() { + public Array getAll(){ return types; } @@ -103,12 +108,4 @@ public class UnitType implements UnlockableContent{ unit.init(this, team); return unit; } - - public static UnitType getByID(byte id){ - return types.get(id); - } - - public static Array all(){ - return types; - } } diff --git a/core/src/io/anuke/mindustry/entities/units/types/Drone.java b/core/src/io/anuke/mindustry/entities/units/types/Drone.java index ac004b943f..3cf689b327 100644 --- a/core/src/io/anuke/mindustry/entities/units/types/Drone.java +++ b/core/src/io/anuke/mindustry/entities/units/types/Drone.java @@ -39,7 +39,7 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.*; -public class Drone extends FlyingUnit implements BuilderTrait { +public class Drone extends FlyingUnit implements BuilderTrait{ protected static ObjectSet toMine; protected static float discoverRange = 120f; protected static boolean initialized; @@ -47,195 +47,17 @@ public class Drone extends FlyingUnit implements BuilderTrait { protected Item targetItem; protected Tile mineTile; protected Queue placeQueue = new ThreadQueue<>(); - - /**Initialize placement event notifier system. - * Static initialization is to be avoided, thus, this is done lazily.*/ - private static void initEvents(){ - if(initialized) return; - - toMine = ObjectSet.with(Items.lead, Items.tungsten); - - Events.on(BlockBuildEvent.class, (team, tile) -> { - EntityGroup group = unitGroups[team.ordinal()]; - - if(!(tile.entity instanceof BuildEntity)) return; - BuildEntity entity = tile.entity(); - - for(BaseUnit unit : group.all()){ - if(unit instanceof Drone){ - ((Drone) unit).notifyPlaced(entity); - } - } - }); - - initialized = true; - } - - { - initEvents(); - } - - private void notifyPlaced(BuildEntity entity){ - float timeToBuild = entity.recipe.cost; - float dist = Math.min(entity.distanceTo(x, y) - placeDistance, 0); - - if(dist / type.maxVelocity < timeToBuild * 0.9f){ - //CallEntity.onDroneBeginBuild(this, entity.tile, entity.recipe); - target = entity; - setState(build); - } - } - - @Override - public float getBuildPower(Tile tile) { - return type.buildPower; - } - - @Override - public float getMinePower() { - return type.minePower; - } - - @Override - public Queue getPlaceQueue() { - return placeQueue; - } - - @Override - public Tile getMineTile() { - return mineTile; - } - - @Override - public void setMineTile(Tile tile) { - mineTile = tile; - } - - @Override - public void update() { - super.update(); - - x += Mathf.sin(Timers.time() + id * 999, 25f, 0.07f); - y += Mathf.cos(Timers.time() + id * 999, 25f, 0.07f); - - updateBuilding(this); - } - - @Override - protected void updateRotation() { - if(target != null && (state.is(repair) || state.is(mine))){ - rotation = Mathf.slerpDelta(rotation, angleTo(target), 0.3f); - }else{ - rotation = Mathf.slerpDelta(rotation, velocity.angle(), 0.3f); - } - - if(velocity.len() <= 0.2f && !(state.is(repair) && target != null)){ - rotation += Mathf.sin(Timers.time() + id * 99, 10f, 5f); - } - } - - @Override - public void behavior() { - if(health <= health * type.retreatPercent && - Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.repair)) != null){ - setState(retreat); - } - } - - @Override - public UnitState getStartState() { - return repair; - } - - @Override - public void drawOver() { - trail.draw(Palette.lightTrail, 3f); - - TargetTrait entity = target; - - if(entity instanceof TileEntity && state.is(repair)){ - float len = 5f; - Draw.color(Color.BLACK, Color.WHITE, 0.95f + Mathf.absin(Timers.time(), 0.8f, 0.05f)); - Shapes.laser("beam", "beam-end", - x + Angles.trnsx(rotation, len), - y + Angles.trnsy(rotation, len), - entity.getX(), entity.getY()); - Draw.color(); - } - - drawBuilding(this); - } - - @Override - public float drawSize() { - return isBuilding() ? placeDistance*2f : 30f; - } - - @Override - public float getAmmoFraction() { - return inventory.getItem().amount / (float)type.itemCapacity; - } - - protected void findItem(){ - TileEntity entity = getClosestCore(); - if(entity == null){ - return; - } - targetItem = Mathf.findMin(toMine, (a, b) -> -Integer.compare(entity.items.get(a), entity.items.get(b))); - } - - protected boolean findItemDrop(){ - TileEntity core = getClosestCore(); - - if(core == null) return false; - - //find nearby dropped items to pick up if applicable - ItemDrop drop = EntityPhysics.getClosest(itemGroup, x, y, 60f, - item -> core.tile.block().acceptStack(item.getItem(), item.getAmount(), core.tile, Drone.this) == item.getAmount() && - inventory.canAcceptItem(item.getItem(), 1)); - if(drop != null){ - setState(pickup); - target = drop; - return true; - } - return false; - } - - @Override - public boolean canCreateBlocks() { - return false; - } - - @Override - public void write(DataOutput data) throws IOException { - super.write(data); - data.writeInt(mineTile == null ? -1 : mineTile.packedPosition()); - writeBuilding(data); - } - - @Override - public void read(DataInput data, long time) throws IOException { - super.read(data, time); - int mined = data.readInt(); - - readBuilding(data); - - if(mined != -1){ - mineTile = world.tile(mined); - } - } - public final UnitState - build = new UnitState(){ - public void entered() { + build = new UnitState(){ + public void entered(){ if(!(target instanceof BuildEntity)){ target = null; } } - public void update() { - BuildEntity entity = (BuildEntity)target; + public void update(){ + BuildEntity entity = (BuildEntity) target; TileEntity core = getClosestCore(); if(entity == null){ @@ -268,17 +90,17 @@ public class Drone extends FlyingUnit implements BuilderTrait { }, repair = new UnitState(){ - public void entered() { + public void entered(){ target = null; } - public void update() { - if(target != null && (((TileEntity)target).health >= ((TileEntity)target).tile.block().health + public void update(){ + if(target != null && (((TileEntity) target).health >= ((TileEntity) target).tile.block().health || target.distanceTo(Drone.this) > discoverRange)){ target = null; } - if (target == null) { + if(target == null){ retarget(() -> { target = Units.findAllyTile(team, x, y, discoverRange, tile -> tile.entity != null && tile.entity.health + 0.0001f < tile.block().health); @@ -296,140 +118,319 @@ public class Drone extends FlyingUnit implements BuilderTrait { } } }, - mine = new UnitState() { - public void entered() { - target = null; - } - - public void update() { - TileEntity entity = getClosestCore(); - - if(entity == null) return; - - if(targetItem == null) { - findItem(); - } - - //core full - if(targetItem != null && entity.tile.block().acceptStack(targetItem, 1, entity.tile, Drone.this) == 0){ - setState(repair); - return; - } - - //if inventory is full, drop it off. - if(inventory.isFull()){ - setState(drop); - }else{ - if(targetItem != null && !inventory.canAcceptItem(targetItem)){ - setState(drop); - return; + mine = new UnitState(){ + public void entered(){ + target = null; } - retarget(() -> { - if(findItemDrop()){ - return; - } + public void update(){ + TileEntity entity = getClosestCore(); - if(getMineTile() == null){ + if(entity == null) return; + + if(targetItem == null){ findItem(); } - if(targetItem == null) return; - - target = world.indexer().findClosestOre(x, y, targetItem); - }); - - if(target instanceof Tile) { - moveTo(type.range/1.5f); - - if (distanceTo(target) < type.range && mineTile != target) { - setMineTile((Tile)target); + //core full + if(targetItem != null && entity.tile.block().acceptStack(targetItem, 1, entity.tile, Drone.this) == 0){ + setState(repair); + return; } - if(((Tile)target).block() != Blocks.air){ + //if inventory is full, drop it off. + if(inventory.isFull()){ setState(drop); + }else{ + if(targetItem != null && !inventory.canAcceptItem(targetItem)){ + setState(drop); + return; + } + + retarget(() -> { + if(findItemDrop()){ + return; + } + + if(getMineTile() == null){ + findItem(); + } + + if(targetItem == null) return; + + target = world.indexer().findClosestOre(x, y, targetItem); + }); + + if(target instanceof Tile){ + moveTo(type.range / 1.5f); + + if(distanceTo(target) < type.range && mineTile != target){ + setMineTile((Tile) target); + } + + if(((Tile) target).block() != Blocks.air){ + setState(drop); + } + } } } - } - } - public void exited() { - setMineTile(null); - } - }, - pickup = new UnitState() { - public void entered() { - target = null; - } - - public void update() { - ItemDrop item = (ItemDrop)target; - - if(inventory.isFull() || !inventory.canAcceptItem(item.getItem(), 1)){ - setState(drop); - return; - } - - if(distanceTo(item) < 4){ - item.collision(Drone.this, x, y); - } - - //item has been picked up - if(item.getAmount() == 0){ - if(!findItemDrop()){ - setState(drop); + public void exited(){ + setMineTile(null); } - } - - moveTo(0f); - } - }, - drop = new UnitState() { - public void entered() { - target = null; - } - - public void update() { - if(inventory.isEmpty()){ - setState(mine); - return; - } - - target = getClosestCore(); - - if(target == null) return; - - TileEntity tile = (TileEntity)target; - - if(distanceTo(target) < type.range){ - if(tile.tile.block().acceptStack(inventory.getItem().item, inventory.getItem().amount, tile.tile, Drone.this) == inventory.getItem().amount) { - CallEntity.transferItemTo(inventory.getItem().item, inventory.getItem().amount, x, y, tile.tile); - inventory.clearItem(); + }, + pickup = new UnitState(){ + public void entered(){ + target = null; } - setState(repair); - } + public void update(){ + ItemDrop item = (ItemDrop) target; - circle(type.range/1.8f); - } - }, - retreat = new UnitState() { - public void entered() { - target = null; - } + if(inventory.isFull() || !inventory.canAcceptItem(item.getItem(), 1)){ + setState(drop); + return; + } - public void update() { - if(health >= health){ - state.set(attack); - }else if(!targetHasFlag(BlockFlag.repair)){ - if(timer.get(timerTarget, 20)) { - Tile target = Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.repair)); - if (target != null) Drone.this.target = target.entity; + if(distanceTo(item) < 4){ + item.collision(Drone.this, x, y); + } + + //item has been picked up + if(item.getAmount() == 0){ + if(!findItemDrop()){ + setState(drop); + } + } + + moveTo(0f); + } + }, + drop = new UnitState(){ + public void entered(){ + target = null; + } + + public void update(){ + if(inventory.isEmpty()){ + setState(mine); + return; + } + + target = getClosestCore(); + + if(target == null) return; + + TileEntity tile = (TileEntity) target; + + if(distanceTo(target) < type.range){ + if(tile.tile.block().acceptStack(inventory.getItem().item, inventory.getItem().amount, tile.tile, Drone.this) == inventory.getItem().amount){ + CallEntity.transferItemTo(inventory.getItem().item, inventory.getItem().amount, x, y, tile.tile); + inventory.clearItem(); + } + + setState(repair); + } + + circle(type.range / 1.8f); + } + }, + retreat = new UnitState(){ + public void entered(){ + target = null; + } + + public void update(){ + if(health >= health){ + state.set(attack); + }else if(!targetHasFlag(BlockFlag.repair)){ + if(timer.get(timerTarget, 20)){ + Tile target = Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.repair)); + if(target != null) Drone.this.target = target.entity; + } + }else{ + circle(40f); + } + } + }; + + { + initEvents(); + } + + /** + * Initialize placement event notifier system. + * Static initialization is to be avoided, thus, this is done lazily. + */ + private static void initEvents(){ + if(initialized) return; + + toMine = ObjectSet.with(Items.lead, Items.tungsten); + + Events.on(BlockBuildEvent.class, (team, tile) -> { + EntityGroup group = unitGroups[team.ordinal()]; + + if(!(tile.entity instanceof BuildEntity)) return; + BuildEntity entity = tile.entity(); + + for(BaseUnit unit : group.all()){ + if(unit instanceof Drone){ + ((Drone) unit).notifyPlaced(entity); } - }else{ - circle(40f); } + }); + + initialized = true; + } + + private void notifyPlaced(BuildEntity entity){ + float timeToBuild = entity.recipe.cost; + float dist = Math.min(entity.distanceTo(x, y) - placeDistance, 0); + + if(dist / type.maxVelocity < timeToBuild * 0.9f){ + //CallEntity.onDroneBeginBuild(this, entity.tile, entity.recipe); + target = entity; + setState(build); } - }; + } + + @Override + public float getBuildPower(Tile tile){ + return type.buildPower; + } + + @Override + public float getMinePower(){ + return type.minePower; + } + + @Override + public Queue getPlaceQueue(){ + return placeQueue; + } + + @Override + public Tile getMineTile(){ + return mineTile; + } + + @Override + public void setMineTile(Tile tile){ + mineTile = tile; + } + + @Override + public void update(){ + super.update(); + + x += Mathf.sin(Timers.time() + id * 999, 25f, 0.07f); + y += Mathf.cos(Timers.time() + id * 999, 25f, 0.07f); + + updateBuilding(this); + } + + @Override + protected void updateRotation(){ + if(target != null && (state.is(repair) || state.is(mine))){ + rotation = Mathf.slerpDelta(rotation, angleTo(target), 0.3f); + }else{ + rotation = Mathf.slerpDelta(rotation, velocity.angle(), 0.3f); + } + + if(velocity.len() <= 0.2f && !(state.is(repair) && target != null)){ + rotation += Mathf.sin(Timers.time() + id * 99, 10f, 5f); + } + } + + @Override + public void behavior(){ + if(health <= health * type.retreatPercent && + Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.repair)) != null){ + setState(retreat); + } + } + + @Override + public UnitState getStartState(){ + return repair; + } + + @Override + public void drawOver(){ + trail.draw(Palette.lightTrail, 3f); + + TargetTrait entity = target; + + if(entity instanceof TileEntity && state.is(repair)){ + float len = 5f; + Draw.color(Color.BLACK, Color.WHITE, 0.95f + Mathf.absin(Timers.time(), 0.8f, 0.05f)); + Shapes.laser("beam", "beam-end", + x + Angles.trnsx(rotation, len), + y + Angles.trnsy(rotation, len), + entity.getX(), entity.getY()); + Draw.color(); + } + + drawBuilding(this); + } + + @Override + public float drawSize(){ + return isBuilding() ? placeDistance * 2f : 30f; + } + + @Override + public float getAmmoFraction(){ + return inventory.getItem().amount / (float) type.itemCapacity; + } + + protected void findItem(){ + TileEntity entity = getClosestCore(); + if(entity == null){ + return; + } + targetItem = Mathf.findMin(toMine, (a, b) -> -Integer.compare(entity.items.get(a), entity.items.get(b))); + } + + protected boolean findItemDrop(){ + TileEntity core = getClosestCore(); + + if(core == null) return false; + + //find nearby dropped items to pick up if applicable + ItemDrop drop = EntityPhysics.getClosest(itemGroup, x, y, 60f, + item -> core.tile.block().acceptStack(item.getItem(), item.getAmount(), core.tile, Drone.this) == item.getAmount() && + inventory.canAcceptItem(item.getItem(), 1)); + if(drop != null){ + setState(pickup); + target = drop; + return true; + } + return false; + } + + @Override + public boolean canCreateBlocks(){ + return false; + } + + @Override + public void write(DataOutput data) throws IOException{ + super.write(data); + data.writeInt(mineTile == null ? -1 : mineTile.packedPosition()); + writeBuilding(data); + } + + @Override + public void read(DataInput data, long time) throws IOException{ + super.read(data, time); + int mined = data.readInt(); + + readBuilding(data); + + if(mined != -1){ + mineTile = world.tile(mined); + } + } } diff --git a/core/src/io/anuke/mindustry/entities/units/types/Fabricator.java b/core/src/io/anuke/mindustry/entities/units/types/Fabricator.java index 89dcea9646..90119c8550 100644 --- a/core/src/io/anuke/mindustry/entities/units/types/Fabricator.java +++ b/core/src/io/anuke/mindustry/entities/units/types/Fabricator.java @@ -1,5 +1,5 @@ package io.anuke.mindustry.entities.units.types; -public class Fabricator extends Drone { +public class Fabricator extends Drone{ } diff --git a/core/src/io/anuke/mindustry/entities/units/types/Monsoon.java b/core/src/io/anuke/mindustry/entities/units/types/Monsoon.java index 09db7fc53a..b87e0d2f81 100644 --- a/core/src/io/anuke/mindustry/entities/units/types/Monsoon.java +++ b/core/src/io/anuke/mindustry/entities/units/types/Monsoon.java @@ -2,6 +2,6 @@ package io.anuke.mindustry.entities.units.types; import io.anuke.mindustry.entities.units.FlyingUnit; -public class Monsoon extends FlyingUnit { +public class Monsoon extends FlyingUnit{ } diff --git a/core/src/io/anuke/mindustry/entities/units/types/Scout.java b/core/src/io/anuke/mindustry/entities/units/types/Scout.java index 765dbfbb12..bdfdbe4f89 100644 --- a/core/src/io/anuke/mindustry/entities/units/types/Scout.java +++ b/core/src/io/anuke/mindustry/entities/units/types/Scout.java @@ -2,6 +2,6 @@ package io.anuke.mindustry.entities.units.types; import io.anuke.mindustry.entities.units.GroundUnit; -public class Scout extends GroundUnit { +public class Scout extends GroundUnit{ } diff --git a/core/src/io/anuke/mindustry/entities/units/types/Vtol.java b/core/src/io/anuke/mindustry/entities/units/types/Vtol.java index 370446b148..cf02014794 100644 --- a/core/src/io/anuke/mindustry/entities/units/types/Vtol.java +++ b/core/src/io/anuke/mindustry/entities/units/types/Vtol.java @@ -2,6 +2,6 @@ package io.anuke.mindustry.entities.units.types; import io.anuke.mindustry.entities.units.FlyingUnit; -public class Vtol extends FlyingUnit { +public class Vtol extends FlyingUnit{ } diff --git a/core/src/io/anuke/mindustry/game/Content.java b/core/src/io/anuke/mindustry/game/Content.java index 542f78f97c..accf8d0947 100644 --- a/core/src/io/anuke/mindustry/game/Content.java +++ b/core/src/io/anuke/mindustry/game/Content.java @@ -2,20 +2,32 @@ package io.anuke.mindustry.game; import com.badlogic.gdx.utils.Array; -/**Base interface for a content type that is loaded in {@link io.anuke.mindustry.core.ContentLoader}.*/ -public interface Content { +/** + * Base interface for a content type that is loaded in {@link io.anuke.mindustry.core.ContentLoader}. + */ +public interface Content{ - /**Returns the type name of this piece of content. - * This should return the same value for all instances of this content type.*/ + /** + * Returns the type name of this piece of content. + * This should return the same value for all instances of this content type. + */ String getContentTypeName(); - /**Returns a list of all instances of this content.*/ + /** + * Returns a list of all instances of this content. + */ Array getAll(); - /**Called after all content is created. Do not use to load regions or texture data!*/ - default void init(){} + /** + * Called after all content is created. Do not use to load regions or texture data! + */ + default void init(){ + } - /**Called after all content is created, only on non-headless versions. - * Use for loading regions or other image data.*/ - default void load(){} + /** + * Called after all content is created, only on non-headless versions. + * Use for loading regions or other image data. + */ + default void load(){ + } } diff --git a/core/src/io/anuke/mindustry/game/ContentDatabase.java b/core/src/io/anuke/mindustry/game/ContentDatabase.java index d01a7e97cc..ed658e111f 100644 --- a/core/src/io/anuke/mindustry/game/ContentDatabase.java +++ b/core/src/io/anuke/mindustry/game/ContentDatabase.java @@ -8,13 +8,19 @@ import io.anuke.mindustry.game.EventType.UnlockEvent; import io.anuke.ucore.core.Events; import io.anuke.ucore.core.Settings; -public class ContentDatabase { - /**Maps unlockable type names to a set of unlocked content.*/ +public class ContentDatabase{ + /** + * Maps unlockable type names to a set of unlocked content. + */ private ObjectMap> unlocked = new ObjectMap<>(); - /**Whether unlockables have changed since the last save.*/ + /** + * Whether unlockables have changed since the last save. + */ private boolean dirty; - /**Returns whether or not this piece of content is unlocked yet.*/ + /** + * Returns whether or not this piece of content is unlocked yet. + */ public boolean isUnlocked(UnlockableContent content){ if(!unlocked.containsKey(content.getContentTypeName())){ unlocked.put(content.getContentTypeName(), new ObjectSet<>()); @@ -25,10 +31,13 @@ public class ContentDatabase { return set.contains(content.getContentName()); } - /**Makes this piece of content 'unlocked', if possible. + /** + * Makes this piece of content 'unlocked', if possible. * If this piece of content is already unlocked or cannot be unlocked due to dependencies, nothing changes. * Results are not saved until you call {@link #save()}. - * @return whether or not this content was newly unlocked.*/ + * + * @return whether or not this content was newly unlocked. + */ public boolean unlockContent(UnlockableContent content){ if(!content.canBeUnlocked()) return false; @@ -48,12 +57,16 @@ public class ContentDatabase { return ret; } - /**Returns whether unlockables have changed since the last save.*/ + /** + * Returns whether unlockables have changed since the last save. + */ public boolean isDirty(){ return dirty; } - /**Clears all unlocked content.*/ + /** + * Clears all unlocked content. + */ public void reset(){ unlocked.clear(); dirty = true; diff --git a/core/src/io/anuke/mindustry/game/Difficulty.java b/core/src/io/anuke/mindustry/game/Difficulty.java index 4f7091a58f..7c86e5685e 100644 --- a/core/src/io/anuke/mindustry/game/Difficulty.java +++ b/core/src/io/anuke/mindustry/game/Difficulty.java @@ -2,7 +2,7 @@ package io.anuke.mindustry.game; import io.anuke.ucore.util.Bundles; -public enum Difficulty { +public enum Difficulty{ easy(4f, 2f, 1f), normal(2f, 1f, 1f), hard(1.5f, 0.5f, 0.75f), @@ -10,13 +10,19 @@ public enum Difficulty { //purge removed due to new wave system /*purge(0.25f, 0.01f, 0.25f)*/; - /**The scaling of how many waves it takes for one more enemy of a type to appear. + /** + * The scaling of how many waves it takes for one more enemy of a type to appear. * For example: with enemeyScaling = 2 and the default scaling being 2, it would take 4 waves for - * an enemy spawn to go from 1->2 enemies.*/ + * an enemy spawn to go from 1->2 enemies. + */ public final float enemyScaling; - /**Multiplier of the time between waves.*/ + /** + * Multiplier of the time between waves. + */ public final float timeScaling; - /**Scaling of max time between waves. Default time is 4 minutes.*/ + /** + * Scaling of max time between waves. Default time is 4 minutes. + */ public final float maxTimeScaling; private String value; @@ -28,7 +34,7 @@ public enum Difficulty { } @Override - public String toString() { + public String toString(){ if(value == null){ value = Bundles.get("setting.difficulty." + name()); } diff --git a/core/src/io/anuke/mindustry/game/EventType.java b/core/src/io/anuke/mindustry/game/EventType.java index 9aecf1a473..ec880f3be9 100644 --- a/core/src/io/anuke/mindustry/game/EventType.java +++ b/core/src/io/anuke/mindustry/game/EventType.java @@ -4,7 +4,7 @@ import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.world.Tile; import io.anuke.ucore.function.Event; -public class EventType { +public class EventType{ public interface PlayEvent extends Event{ void handle(); @@ -22,19 +22,25 @@ public class EventType { void handle(); } - /**This event is called from the logic thread. - * DO NOT INITIALIZE GRAPHICS HERE.*/ + /** + * This event is called from the logic thread. + * DO NOT INITIALIZE GRAPHICS HERE. + */ public interface WorldLoadEvent extends Event{ void handle(); } - /**Called after the WorldLoadEvent is, and all logic has been loaded. - * It is safe to intialize graphics here.*/ + /** + * Called after the WorldLoadEvent is, and all logic has been loaded. + * It is safe to intialize graphics here. + */ public interface WorldLoadGraphicsEvent extends Event{ void handle(); } - /**Called from the logic thread. Do not access graphics here!*/ + /** + * Called from the logic thread. Do not access graphics here! + */ public interface TileChangeEvent extends Event{ void handle(Tile tile); } diff --git a/core/src/io/anuke/mindustry/game/GameMode.java b/core/src/io/anuke/mindustry/game/GameMode.java index cbc7f7349d..fcf9e8c09a 100644 --- a/core/src/io/anuke/mindustry/game/GameMode.java +++ b/core/src/io/anuke/mindustry/game/GameMode.java @@ -3,8 +3,8 @@ package io.anuke.mindustry.game; import io.anuke.ucore.util.Bundles; public enum GameMode{ - waves, - //disabled for technical reasons + waves, + //disabled for technical reasons /*sandbox{ { infiniteResources = true; @@ -16,16 +16,16 @@ public enum GameMode{ disableWaveTimer = true; } }; - public boolean infiniteResources; - public boolean disableWaveTimer; + public boolean infiniteResources; + public boolean disableWaveTimer; - public String description(){ - return Bundles.get("mode."+name()+".description"); - } + public String description(){ + return Bundles.get("mode." + name() + ".description"); + } - @Override - public String toString(){ - return Bundles.get("mode."+name()+".name"); - } + @Override + public String toString(){ + return Bundles.get("mode." + name() + ".name"); + } } diff --git a/core/src/io/anuke/mindustry/game/SpawnGroup.java b/core/src/io/anuke/mindustry/game/SpawnGroup.java index 6c91faa1ac..4d8fe850be 100644 --- a/core/src/io/anuke/mindustry/game/SpawnGroup.java +++ b/core/src/io/anuke/mindustry/game/SpawnGroup.java @@ -8,84 +8,118 @@ import io.anuke.mindustry.type.ItemStack; import io.anuke.mindustry.type.StatusEffect; import io.anuke.mindustry.type.Weapon; -/**A spawn group defines spawn information for a specific type of unit, with optional extra information like +/** + * A spawn group defines spawn information for a specific type of unit, with optional extra information like * weapon equipped, ammo used, and status effects. - * Each spawn group can have multiple sub-groups spawned in different areas of the map.*/ -public class SpawnGroup { - /**The unit type spawned*/ - public final UnitType type; - /**When this spawn should end*/ - protected int end = Integer.MAX_VALUE; - /**When this spawn should start*/ - protected int begin; - /**The spacing, in waves, of spawns. For example, 2 = spawns every other wave*/ - protected int spacing = 1; - /**Maximum amount of units that spawn*/ - protected int max = 60; - /**How many waves need to pass before the amount of units spawned increases by 1*/ - protected float unitScaling = 9999f; - /**How many waves need to pass before the amount of instances of this group increases by 1*/ - protected float groupScaling = 9999f; - /**Amount of enemies spawned initially, with no scaling*/ - protected int unitAmount = 1; - /**Amount of enemies spawned initially, with no scaling*/ - protected int groupAmount = 1; - /**Weapon used by the spawned unit. Null to disable. Only applicable to ground units.*/ - protected Weapon weapon; - /**Status effect applied to the spawned unit. Null to disable.*/ - protected StatusEffect effect; - /**Items this unit spawns with. Null to disable.*/ - protected ItemStack items; - /**Ammo type this unit spawns with. Null to use the first available ammo.*/ - protected Item ammoItem; - - public SpawnGroup(UnitType type){ - this.type = type; - } + * Each spawn group can have multiple sub-groups spawned in different areas of the map. + */ +public class SpawnGroup{ + /** + * The unit type spawned + */ + public final UnitType type; + /** + * When this spawn should end + */ + protected int end = Integer.MAX_VALUE; + /** + * When this spawn should start + */ + protected int begin; + /** + * The spacing, in waves, of spawns. For example, 2 = spawns every other wave + */ + protected int spacing = 1; + /** + * Maximum amount of units that spawn + */ + protected int max = 60; + /** + * How many waves need to pass before the amount of units spawned increases by 1 + */ + protected float unitScaling = 9999f; + /** + * How many waves need to pass before the amount of instances of this group increases by 1 + */ + protected float groupScaling = 9999f; + /** + * Amount of enemies spawned initially, with no scaling + */ + protected int unitAmount = 1; + /** + * Amount of enemies spawned initially, with no scaling + */ + protected int groupAmount = 1; + /** + * Weapon used by the spawned unit. Null to disable. Only applicable to ground units. + */ + protected Weapon weapon; + /** + * Status effect applied to the spawned unit. Null to disable. + */ + protected StatusEffect effect; + /** + * Items this unit spawns with. Null to disable. + */ + protected ItemStack items; + /** + * Ammo type this unit spawns with. Null to use the first available ammo. + */ + protected Item ammoItem; - /**Returns the amount of units spawned on a specific wave.*/ - public int getUnitsSpawned(int wave){ - if(wave < begin || wave > end || (wave - begin) % spacing != 0){ - return 0; - } - float scaling = this.unitScaling; - - return Math.min(unitAmount-1 + Math.max((int)((wave / spacing) / scaling), 1), max); - } + public SpawnGroup(UnitType type){ + this.type = type; + } - /**Returns the amount of different unit groups at a specific wave.*/ - public int getGroupsSpawned(int wave){ - if(wave < begin || wave > end || (wave - begin) % spacing != 0){ - return 0; - } - float scaling = this.groupScaling; + /** + * Returns the amount of units spawned on a specific wave. + */ + public int getUnitsSpawned(int wave){ + if(wave < begin || wave > end || (wave - begin) % spacing != 0){ + return 0; + } + float scaling = this.unitScaling; - return Math.min(groupAmount-1 + Math.max((int)((wave / spacing) / groupScaling), 1), max); - } + return Math.min(unitAmount - 1 + Math.max((int) ((wave / spacing) / scaling), 1), max); + } - /**Creates a unit, and assigns correct values based on this group's data. - * This method does not add() the unit.*/ - public BaseUnit createUnit(Team team){ - BaseUnit unit = type.create(team); + /** + * Returns the amount of different unit groups at a specific wave. + */ + public int getGroupsSpawned(int wave){ + if(wave < begin || wave > end || (wave - begin) % spacing != 0){ + return 0; + } + float scaling = this.groupScaling; - if(unit instanceof GroundUnit && weapon != null){ - ((GroundUnit) unit).setWeapon(weapon); - } + return Math.min(groupAmount - 1 + Math.max((int) ((wave / spacing) / groupScaling), 1), max); + } - if(effect != null){ - unit.applyEffect(effect, 10000f); - } + /** + * Creates a unit, and assigns correct values based on this group's data. + * This method does not add() the unit. + */ + public BaseUnit createUnit(Team team){ + BaseUnit unit = type.create(team); - if(items != null){ - unit.inventory.addItem(items.item, items.amount); - } + if(unit instanceof GroundUnit && weapon != null){ + ((GroundUnit) unit).setWeapon(weapon); + } - if(ammoItem != null){ - unit.inventory.addAmmo(unit.getWeapon().getAmmoType(ammoItem)); - }else{ - unit.inventory.addAmmo(unit.getWeapon().getAmmoType(unit.getWeapon().getAcceptedItems().iterator().next())); - } + if(effect != null){ + unit.applyEffect(effect, 10000f); + } - return unit; - } + if(items != null){ + unit.inventory.addItem(items.item, items.amount); + } + + if(ammoItem != null){ + unit.inventory.addAmmo(unit.getWeapon().getAmmoType(ammoItem)); + }else{ + unit.inventory.addAmmo(unit.getWeapon().getAmmoType(unit.getWeapon().getAcceptedItems().iterator().next())); + } + + return unit; + } } diff --git a/core/src/io/anuke/mindustry/game/Team.java b/core/src/io/anuke/mindustry/game/Team.java index 0d97c39285..7fce5dc6fb 100644 --- a/core/src/io/anuke/mindustry/game/Team.java +++ b/core/src/io/anuke/mindustry/game/Team.java @@ -2,7 +2,7 @@ package io.anuke.mindustry.game; import com.badlogic.gdx.graphics.Color; -public enum Team { +public enum Team{ none(Color.DARK_GRAY), blue(Color.ROYAL), red(Color.valueOf("e84737")), @@ -10,11 +10,10 @@ public enum Team { purple(Color.valueOf("ba5bd9")), orange(Color.valueOf("e8c66a")); + public final static Team[] all = values(); public final Color color; public final int intColor; - public final static Team[] all = values(); - Team(Color color){ this.color = color; intColor = Color.rgba8888(color); diff --git a/core/src/io/anuke/mindustry/game/TeamInfo.java b/core/src/io/anuke/mindustry/game/TeamInfo.java index 33ea3b37c7..8559fde11d 100644 --- a/core/src/io/anuke/mindustry/game/TeamInfo.java +++ b/core/src/io/anuke/mindustry/game/TeamInfo.java @@ -6,8 +6,10 @@ import io.anuke.mindustry.world.Tile; import io.anuke.ucore.util.ThreadArray; import io.anuke.ucore.util.ThreadSet; -/**Class for various team-based utilities.*/ -public class TeamInfo { +/** + * Class for various team-based utilities. + */ +public class TeamInfo{ private ObjectMap map = new ObjectMap<>(); private ThreadSet allies = new ThreadSet<>(), enemies = new ThreadSet<>(); @@ -18,30 +20,37 @@ public class TeamInfo { private int allyBits = 0; private int enemyBits = 0; - /**Returns all teams on a side.*/ - public ObjectSet getTeams(boolean ally) { + /** + * Returns all teams on a side. + */ + public ObjectSet getTeams(boolean ally){ return ally ? allyData : enemyData; } - /**Returns all team data.*/ - public ObjectSet getTeams() { + /** + * Returns all team data. + */ + public ObjectSet getTeams(){ return allTeamData; } - /**Register a team. + /** + * Register a team. + * * @param team The team type enum. * @param ally Whether this team is an ally with the player or an enemy with the player. - * In PvP situations with dedicated servers, the sides can be arbitrary.*/ + * In PvP situations with dedicated servers, the sides can be arbitrary. + */ public void add(Team team, boolean ally){ if(has(team)) throw new RuntimeException("Can't define team information twice!"); TeamData data = new TeamData(team, ally); - if(ally) { + if(ally){ allies.add(team); allyData.add(data); allyBits |= (1 << team.ordinal()); - }else { + }else{ enemies.add(team); enemyData.add(data); enemyBits |= (1 << team.ordinal()); @@ -53,20 +62,26 @@ public class TeamInfo { map.put(team, data); } - /**Returns team data by type. Call {@link #has(Team)} first to make sure it's active!*/ + /** + * Returns team data by type. Call {@link #has(Team)} first to make sure it's active! + */ public TeamData get(Team team){ if(!has(team)) throw new RuntimeException("This team is not active! Check has() before calling get()."); return map.get(team); } - /**Returns whether the specified team is active, e.g. whether it is participating in the game.*/ + /** + * Returns whether the specified team is active, e.g. whether it is participating in the game. + */ public boolean has(Team team){ return map.containsKey(team); } - /**Returns a set of all teams that are enemies of this team. - * For teams not active, an empty set is returned.*/ - public ObjectSet enemiesOf(Team team) { + /** + * Returns a set of all teams that are enemies of this team. + * For teams not active, an empty set is returned. + */ + public ObjectSet enemiesOf(Team team){ boolean ally = allies.contains(team); boolean enemy = enemies.contains(team); @@ -76,9 +91,11 @@ public class TeamInfo { return ally ? enemies : allies; } - /**Returns a set of all teams that are allies of this team. - * For teams not active, an empty set is returned.*/ - public ObjectSet alliesOf(Team team) { + /** + * Returns a set of all teams that are allies of this team. + * For teams not active, an empty set is returned. + */ + public ObjectSet alliesOf(Team team){ boolean ally = allies.contains(team); boolean enemy = enemies.contains(team); @@ -88,9 +105,11 @@ public class TeamInfo { return !ally ? enemies : allies; } - /**Returns a set of all teams that are enemies of this team. - * For teams not active, an empty set is returned.*/ - public ObjectSet enemyDataOf(Team team) { + /** + * Returns a set of all teams that are enemies of this team. + * For teams not active, an empty set is returned. + */ + public ObjectSet enemyDataOf(Team team){ boolean ally = allies.contains(team); boolean enemy = enemies.contains(team); @@ -100,7 +119,9 @@ public class TeamInfo { return ally ? enemyData : allyData; } - /**Returns whether or not these two teams are enemies.*/ + /** + * Returns whether or not these two teams are enemies. + */ public boolean areEnemies(Team team, Team other){ if(team == other) return false; //fast fail to be more efficient boolean ally = (allyBits & (1 << team.ordinal())) != 0; @@ -108,12 +129,12 @@ public class TeamInfo { return (ally == enemy) || !ally; //if it's not in the game, target everything. } - public class TeamData { + public class TeamData{ public final ThreadArray cores = new ThreadArray<>(); public final Team team; public final boolean ally; - public TeamData(Team team, boolean ally) { + public TeamData(Team team, boolean ally){ this.team = team; this.ally = ally; } diff --git a/core/src/io/anuke/mindustry/game/UnlockableContent.java b/core/src/io/anuke/mindustry/game/UnlockableContent.java index b42ed1c645..6c04c7ad06 100644 --- a/core/src/io/anuke/mindustry/game/UnlockableContent.java +++ b/core/src/io/anuke/mindustry/game/UnlockableContent.java @@ -5,37 +5,54 @@ import io.anuke.ucore.scene.ui.layout.Table; import static io.anuke.mindustry.Vars.control; -/**Base interface for an unlockable content type.*/ +/** + * Base interface for an unlockable content type. + */ public interface UnlockableContent extends Content{ - /**Returns the unqiue name of this piece of content. + /** + * Returns the unqiue name of this piece of content. * The name only needs to be unique for all content of this type. * Do not use IDs for names! Make sure this string stays constant with each update unless removed. - * (e.g. having a recipe and a block, both with name "wall" is fine, as they are different types).*/ + * (e.g. having a recipe and a block, both with name "wall" is fine, as they are different types). + */ String getContentName(); - /**Returns the localized name of this content.*/ + /** + * Returns the localized name of this content. + */ String localizedName(); TextureRegion getContentIcon(); - /**This should show all necessary info about this content in the specified table.*/ + /** + * This should show all necessary info about this content in the specified table. + */ void displayInfo(Table table); - /**Called when this content is unlocked. Use this to unlock other related content.*/ - default void onUnlock(){} + /** + * Called when this content is unlocked. Use this to unlock other related content. + */ + default void onUnlock(){ + } - /**Whether this content is always hidden in the content info dialog.*/ + /** + * Whether this content is always hidden in the content info dialog. + */ default boolean isHidden(){ return false; } - /**Lists the content that must be unlocked in order for this specific content to become unlocked. May return null.*/ + /** + * Lists the content that must be unlocked in order for this specific content to become unlocked. May return null. + */ default UnlockableContent[] getDependencies(){ return null; } - /**Returns whether dependencies are satisfied for unlocking this content.*/ + /** + * Returns whether dependencies are satisfied for unlocking this content. + */ default boolean canBeUnlocked(){ UnlockableContent[] depend = getDependencies(); if(depend == null){ diff --git a/core/src/io/anuke/mindustry/game/WaveCreator.java b/core/src/io/anuke/mindustry/game/WaveCreator.java index c1d2adb8e2..a5e5e25340 100644 --- a/core/src/io/anuke/mindustry/game/WaveCreator.java +++ b/core/src/io/anuke/mindustry/game/WaveCreator.java @@ -8,183 +8,183 @@ import io.anuke.mindustry.content.Weapons; import io.anuke.mindustry.type.ItemStack; public class WaveCreator{ - - public static Array getSpawns(){ - return Array.with( - new SpawnGroup(UnitTypes.scout){{ - end = 8; - unitScaling = 2; - }}, - new SpawnGroup(UnitTypes.vtol){{ - begin = 12; - end = 14; - }}, + public static Array getSpawns(){ + return Array.with( + new SpawnGroup(UnitTypes.scout){{ + end = 8; + unitScaling = 2; + }}, - new SpawnGroup(UnitTypes.scout){{ - begin = 11; - unitScaling = 2; - spacing = 2; - max = 4; - }}, + new SpawnGroup(UnitTypes.vtol){{ + begin = 12; + end = 14; + }}, - new SpawnGroup(UnitTypes.titan){{ - begin = 9; - spacing = 3; - unitScaling = 2; + new SpawnGroup(UnitTypes.scout){{ + begin = 11; + unitScaling = 2; + spacing = 2; + max = 4; + }}, - end = 30; - }}, + new SpawnGroup(UnitTypes.titan){{ + begin = 9; + spacing = 3; + unitScaling = 2; - new SpawnGroup(UnitTypes.scout){{ - begin = 10; - unitScaling = 2; - unitAmount = 1; - spacing = 2; - ammoItem = Items.tungsten; - end = 30; - }}, + end = 30; + }}, - new SpawnGroup(UnitTypes.titan){{ - begin = 28; - spacing = 3; - unitScaling = 2; - weapon = Weapons.flamethrower; - end = 40; - }}, + new SpawnGroup(UnitTypes.scout){{ + begin = 10; + unitScaling = 2; + unitAmount = 1; + spacing = 2; + ammoItem = Items.tungsten; + end = 30; + }}, - new SpawnGroup(UnitTypes.titan){{ - begin = 45; - spacing = 3; - unitScaling = 2; - weapon = Weapons.flamethrower; - effect = StatusEffects.overdrive; - }}, + new SpawnGroup(UnitTypes.titan){{ + begin = 28; + spacing = 3; + unitScaling = 2; + weapon = Weapons.flamethrower; + end = 40; + }}, - new SpawnGroup(UnitTypes.titan){{ - begin = 120; - spacing = 2; - unitScaling = 3; - unitAmount = 5; - weapon = Weapons.flakgun; - effect = StatusEffects.overdrive; - }}, + new SpawnGroup(UnitTypes.titan){{ + begin = 45; + spacing = 3; + unitScaling = 2; + weapon = Weapons.flamethrower; + effect = StatusEffects.overdrive; + }}, - new SpawnGroup(UnitTypes.vtol){{ - begin = 16; - unitScaling = 2; - spacing = 2; + new SpawnGroup(UnitTypes.titan){{ + begin = 120; + spacing = 2; + unitScaling = 3; + unitAmount = 5; + weapon = Weapons.flakgun; + effect = StatusEffects.overdrive; + }}, - end = 39; - max = 7; - }}, + new SpawnGroup(UnitTypes.vtol){{ + begin = 16; + unitScaling = 2; + spacing = 2; - new SpawnGroup(UnitTypes.scout){{ - begin = 82; - spacing = 3; - unitAmount = 4; - groupAmount = 2; - unitScaling = 3; - effect = StatusEffects.overdrive; - ammoItem = Items.silicon; - }}, + end = 39; + max = 7; + }}, - new SpawnGroup(UnitTypes.scout){{ - begin = 41; - spacing = 5; - unitAmount = 1; - unitScaling = 3; - effect = StatusEffects.shielded; - ammoItem = Items.thorium; - max = 10; - }}, + new SpawnGroup(UnitTypes.scout){{ + begin = 82; + spacing = 3; + unitAmount = 4; + groupAmount = 2; + unitScaling = 3; + effect = StatusEffects.overdrive; + ammoItem = Items.silicon; + }}, - new SpawnGroup(UnitTypes.scout){{ - begin = 35; - spacing = 3; - unitAmount = 4; - groupAmount = 2; - effect = StatusEffects.overdrive; - items = new ItemStack(Items.blastCompound, 60); - end = 60; - }}, + new SpawnGroup(UnitTypes.scout){{ + begin = 41; + spacing = 5; + unitAmount = 1; + unitScaling = 3; + effect = StatusEffects.shielded; + ammoItem = Items.thorium; + max = 10; + }}, - new SpawnGroup(UnitTypes.scout){{ - begin = 42; - spacing = 3; - unitAmount = 4; - groupAmount = 2; - effect = StatusEffects.overdrive; - items = new ItemStack(Items.pyratite, 100); - end = 130; - }}, + new SpawnGroup(UnitTypes.scout){{ + begin = 35; + spacing = 3; + unitAmount = 4; + groupAmount = 2; + effect = StatusEffects.overdrive; + items = new ItemStack(Items.blastCompound, 60); + end = 60; + }}, - new SpawnGroup(UnitTypes.monsoon){{ - begin = 40; - ammoItem = Items.blastCompound; - unitAmount = 2; - spacing = 2; - unitScaling = 3; - max = 8; - }}, + new SpawnGroup(UnitTypes.scout){{ + begin = 42; + spacing = 3; + unitAmount = 4; + groupAmount = 2; + effect = StatusEffects.overdrive; + items = new ItemStack(Items.pyratite, 100); + end = 130; + }}, - new SpawnGroup(UnitTypes.vtol){{ - begin = 50; - unitAmount = 4; - unitScaling = 3; - spacing = 5; - groupAmount = 2; - effect = StatusEffects.overdrive; - max = 8; - }}, + new SpawnGroup(UnitTypes.monsoon){{ + begin = 40; + ammoItem = Items.blastCompound; + unitAmount = 2; + spacing = 2; + unitScaling = 3; + max = 8; + }}, - new SpawnGroup(UnitTypes.monsoon){{ - begin = 53; - ammoItem = Items.pyratite; - unitAmount = 2; - unitScaling = 3; - spacing = 4; - max = 8; - end = 74; - }}, + new SpawnGroup(UnitTypes.vtol){{ + begin = 50; + unitAmount = 4; + unitScaling = 3; + spacing = 5; + groupAmount = 2; + effect = StatusEffects.overdrive; + max = 8; + }}, - new SpawnGroup(UnitTypes.monsoon){{ - begin = 53; - ammoItem = Items.coal; - unitAmount = 2; - unitScaling = 3; - spacing = 4; - max = 8; - end = 74; - }} - ); - } + new SpawnGroup(UnitTypes.monsoon){{ + begin = 53; + ammoItem = Items.pyratite; + unitAmount = 2; + unitScaling = 3; + spacing = 4; + max = 8; + end = 74; + }}, - public static void testWaves(int from, int to){ - Array spawns = getSpawns(); - for(int i = from; i <= to; i ++){ - System.out.print(i+": "); - int total = 0; - for(SpawnGroup spawn : spawns){ - int a = spawn.getUnitsSpawned(i) * spawn.getGroupsSpawned(i); - total += a; - - if(a > 0){ - System.out.print(a+"x" + spawn.type.name); + new SpawnGroup(UnitTypes.monsoon){{ + begin = 53; + ammoItem = Items.coal; + unitAmount = 2; + unitScaling = 3; + spacing = 4; + max = 8; + end = 74; + }} + ); + } - if(spawn.weapon != null){ - System.out.print(":" + spawn.weapon.name); - } + public static void testWaves(int from, int to){ + Array spawns = getSpawns(); + for(int i = from; i <= to; i++){ + System.out.print(i + ": "); + int total = 0; + for(SpawnGroup spawn : spawns){ + int a = spawn.getUnitsSpawned(i) * spawn.getGroupsSpawned(i); + total += a; - if(spawn.ammoItem != null){ - System.out.print(":" + spawn.ammoItem.name); - } + if(a > 0){ + System.out.print(a + "x" + spawn.type.name); - System.out.print(" "); - } - } - System.out.print(" (" + total + ")"); - System.out.println(); - } - } + if(spawn.weapon != null){ + System.out.print(":" + spawn.weapon.name); + } + + if(spawn.ammoItem != null){ + System.out.print(":" + spawn.ammoItem.name); + } + + System.out.print(" "); + } + } + System.out.print(" (" + total + ")"); + System.out.println(); + } + } } diff --git a/core/src/io/anuke/mindustry/graphics/BlockRenderer.java b/core/src/io/anuke/mindustry/graphics/BlockRenderer.java index 7a898eb72f..0f6317a7e0 100644 --- a/core/src/io/anuke/mindustry/graphics/BlockRenderer.java +++ b/core/src/io/anuke/mindustry/graphics/BlockRenderer.java @@ -15,203 +15,207 @@ import static io.anuke.mindustry.Vars.*; import static io.anuke.ucore.core.Core.camera; public class BlockRenderer{ - private final static int initialRequests = 32*32; + private final static int initialRequests = 32 * 32; - private FloorRenderer floorRenderer; - - private Array requests = new Array<>(initialRequests); - private Layer lastLayer; - private int requestidx = 0; - private int iterateidx = 0; + private FloorRenderer floorRenderer; - public BlockRenderer(){ - floorRenderer = new FloorRenderer(); + private Array requests = new Array<>(initialRequests); + private Layer lastLayer; + private int requestidx = 0; + private int iterateidx = 0; - for(int i = 0; i < requests.size; i ++){ - requests.set(i, new BlockRequest()); - } - } - - private class BlockRequest implements Comparable{ - Tile tile; - Layer layer; - - @Override - public int compareTo(BlockRequest other){ - return layer.compareTo(other.layer); - } + public BlockRenderer(){ + floorRenderer = new FloorRenderer(); - @Override - public String toString(){ - return tile.block().name + ":" + layer.toString(); - } - } - - /**Process all blocks to draw, simultaneously drawing block shadows and static blocks.*/ - public void processBlocks(){ - requestidx = 0; - lastLayer = null; - - int rangex = (int) (camera.viewportWidth * camera.zoom / tilesize / 2)+2; - int rangey = (int) (camera.viewportHeight * camera.zoom / tilesize / 2)+2; + for(int i = 0; i < requests.size; i++){ + requests.set(i, new BlockRequest()); + } + } - int expandr = 4; - - Graphics.surface(renderer.effectSurface, true, false); + /** + * Process all blocks to draw, simultaneously drawing block shadows and static blocks. + */ + public void processBlocks(){ + requestidx = 0; + lastLayer = null; - int avgx = Mathf.scl(camera.position.x, tilesize); - int avgy = Mathf.scl(camera.position.y, tilesize); + int rangex = (int) (camera.viewportWidth * camera.zoom / tilesize / 2) + 2; + int rangey = (int) (camera.viewportHeight * camera.zoom / tilesize / 2) + 2; - int minx = Math.max(avgx - rangex - expandr, 0); - int miny = Math.max(avgy - rangey - expandr, 0); - int maxx = Math.min(world.width() - 1, avgx + rangex + expandr); - int maxy = Math.min(world.height() - 1, avgy+ rangey + expandr); + int expandr = 4; - for(int x = minx; x <= maxx; x++){ - for(int y = miny; y <= maxy; y++){ - boolean expanded = (Math.abs(x - avgx) > rangex || Math.abs(y - avgy) > rangey); + Graphics.surface(renderer.effectSurface, true, false); - synchronized (Tile.tileSetLock) { - Tile tile = world.rawTile(x, y); + int avgx = Mathf.scl(camera.position.x, tilesize); + int avgy = Mathf.scl(camera.position.y, tilesize); - if (tile != null) { - Block block = tile.block(); + int minx = Math.max(avgx - rangex - expandr, 0); + int miny = Math.max(avgy - rangey - expandr, 0); + int maxx = Math.min(world.width() - 1, avgx + rangex + expandr); + int maxy = Math.min(world.height() - 1, avgy + rangey + expandr); - if (!expanded && block != Blocks.air && world.isAccessible(x, y)) { - tile.block().drawShadow(tile); - } + for(int x = minx; x <= maxx; x++){ + for(int y = miny; y <= maxy; y++){ + boolean expanded = (Math.abs(x - avgx) > rangex || Math.abs(y - avgy) > rangey); - if (!(block instanceof StaticBlock)) { - if (block != Blocks.air) { - if (!expanded) { - addRequest(tile, Layer.block); - } + synchronized(Tile.tileSetLock){ + Tile tile = world.rawTile(x, y); - if (block.expanded || !expanded) { - if (block.layer != null && block.isLayer(tile)) { - addRequest(tile, block.layer); - } + if(tile != null){ + Block block = tile.block(); - if (block.layer2 != null && block.isLayer2(tile)) { - addRequest(tile, block.layer2); - } - } - } - } - } - } - } - } + if(!expanded && block != Blocks.air && world.isAccessible(x, y)){ + tile.block().drawShadow(tile); + } - //TODO this actually isn't necessary - Draw.color(0, 0, 0, 0.15f); - Graphics.flushSurface(); - Draw.color(); + if(!(block instanceof StaticBlock)){ + if(block != Blocks.air){ + if(!expanded){ + addRequest(tile, Layer.block); + } - Graphics.end(); - floorRenderer.beginDraw(); - floorRenderer.drawLayer(CacheLayer.walls); - floorRenderer.endDraw(); - Graphics.begin(); + if(block.expanded || !expanded){ + if(block.layer != null && block.isLayer(tile)){ + addRequest(tile, block.layer); + } - Sort.instance().sort(requests.items, 0, requestidx); - iterateidx = 0; - } + if(block.layer2 != null && block.isLayer2(tile)){ + addRequest(tile, block.layer2); + } + } + } + } + } + } + } + } - public int getRequests(){ - return requestidx; - } - - public void drawBlocks(Layer stopAt){ - - for(; iterateidx < requestidx; iterateidx ++){ + //TODO this actually isn't necessary + Draw.color(0, 0, 0, 0.15f); + Graphics.flushSurface(); + Draw.color(); - if(iterateidx < requests.size && requests.get(iterateidx).layer.ordinal() > stopAt.ordinal()){ - break; - } - - BlockRequest req = requests.get(iterateidx); + Graphics.end(); + floorRenderer.beginDraw(); + floorRenderer.drawLayer(CacheLayer.walls); + floorRenderer.endDraw(); + Graphics.begin(); - if(req.layer != lastLayer){ - if(lastLayer != null) layerEnds(lastLayer); - layerBegins(req.layer); - } + Sort.instance().sort(requests.items, 0, requestidx); + iterateidx = 0; + } - synchronized (Tile.tileSetLock) { - Block block = req.tile.block(); + public int getRequests(){ + return requestidx; + } - if (req.layer == Layer.block) { - block.draw(req.tile); - } else if (req.layer == block.layer) { - block.drawLayer(req.tile); - } else if (req.layer == block.layer2) { - block.drawLayer2(req.tile); - } - } + public void drawBlocks(Layer stopAt){ - lastLayer = req.layer; - } - } + for(; iterateidx < requestidx; iterateidx++){ - public void drawTeamBlocks(Layer layer, Team team){ - int index = this.iterateidx; + if(iterateidx < requests.size && requests.get(iterateidx).layer.ordinal() > stopAt.ordinal()){ + break; + } - for(; index < requestidx; index ++){ + BlockRequest req = requests.get(iterateidx); - if(index < requests.size && requests.get(index).layer.ordinal() > layer.ordinal()){ - break; - } + if(req.layer != lastLayer){ + if(lastLayer != null) layerEnds(lastLayer); + layerBegins(req.layer); + } - BlockRequest req = requests.get(index); - if(req.tile.getTeam() != team) continue; + synchronized(Tile.tileSetLock){ + Block block = req.tile.block(); - synchronized (Tile.tileSetLock) { - Block block = req.tile.block(); + if(req.layer == Layer.block){ + block.draw(req.tile); + }else if(req.layer == block.layer){ + block.drawLayer(req.tile); + }else if(req.layer == block.layer2){ + block.drawLayer2(req.tile); + } + } - if (req.layer == block.layer) { - block.drawLayer(req.tile); - } else if (req.layer == block.layer2) { - block.drawLayer2(req.tile); - } - } - } - } + lastLayer = req.layer; + } + } - public void skipLayer(Layer stopAt){ + public void drawTeamBlocks(Layer layer, Team team){ + int index = this.iterateidx; - for(; iterateidx < requestidx; iterateidx ++){ - if(iterateidx < requests.size && requests.get(iterateidx).layer.ordinal() > stopAt.ordinal()){ - break; - } - } - } + for(; index < requestidx; index++){ - public void beginFloor(){ - floorRenderer.beginDraw(); - } + if(index < requests.size && requests.get(index).layer.ordinal() > layer.ordinal()){ + break; + } - public void endFloor(){ - floorRenderer.endDraw(); - } + BlockRequest req = requests.get(index); + if(req.tile.getTeam() != team) continue; - public void drawFloor(){ - floorRenderer.drawFloor(); - } + synchronized(Tile.tileSetLock){ + Block block = req.tile.block(); - private void layerBegins(Layer layer){} + if(req.layer == block.layer){ + block.drawLayer(req.tile); + }else if(req.layer == block.layer2){ + block.drawLayer2(req.tile); + } + } + } + } - private void layerEnds(Layer layer){} + public void skipLayer(Layer stopAt){ - private void addRequest(Tile tile, Layer layer){ - if(requestidx >= requests.size){ - requests.add(new BlockRequest()); - } - BlockRequest r = requests.get(requestidx); - if(r == null){ - requests.set(requestidx, r = new BlockRequest()); - } - r.tile = tile; - r.layer = layer; - requestidx ++; - } + for(; iterateidx < requestidx; iterateidx++){ + if(iterateidx < requests.size && requests.get(iterateidx).layer.ordinal() > stopAt.ordinal()){ + break; + } + } + } + + public void beginFloor(){ + floorRenderer.beginDraw(); + } + + public void endFloor(){ + floorRenderer.endDraw(); + } + + public void drawFloor(){ + floorRenderer.drawFloor(); + } + + private void layerBegins(Layer layer){ + } + + private void layerEnds(Layer layer){ + } + + private void addRequest(Tile tile, Layer layer){ + if(requestidx >= requests.size){ + requests.add(new BlockRequest()); + } + BlockRequest r = requests.get(requestidx); + if(r == null){ + requests.set(requestidx, r = new BlockRequest()); + } + r.tile = tile; + r.layer = layer; + requestidx++; + } + + private class BlockRequest implements Comparable{ + Tile tile; + Layer layer; + + @Override + public int compareTo(BlockRequest other){ + return layer.compareTo(other.layer); + } + + @Override + public String toString(){ + return tile.block().name + ":" + layer.toString(); + } + } } diff --git a/core/src/io/anuke/mindustry/graphics/CacheLayer.java b/core/src/io/anuke/mindustry/graphics/CacheLayer.java index f1471c2a42..aecf703ae2 100644 --- a/core/src/io/anuke/mindustry/graphics/CacheLayer.java +++ b/core/src/io/anuke/mindustry/graphics/CacheLayer.java @@ -8,7 +8,7 @@ import io.anuke.ucore.graphics.Shader; import static io.anuke.mindustry.Vars.renderer; -public enum CacheLayer { +public enum CacheLayer{ water{ @Override public void begin(){ @@ -57,11 +57,11 @@ public enum CacheLayer { walls; public void begin(){ - + } public void end(){ - + } protected void beginShader(){ diff --git a/core/src/io/anuke/mindustry/graphics/FloorRenderer.java b/core/src/io/anuke/mindustry/graphics/FloorRenderer.java index a93bde5a6f..5ccefa23be 100644 --- a/core/src/io/anuke/mindustry/graphics/FloorRenderer.java +++ b/core/src/io/anuke/mindustry/graphics/FloorRenderer.java @@ -26,7 +26,7 @@ import java.util.Arrays; import static io.anuke.mindustry.Vars.tilesize; import static io.anuke.mindustry.Vars.world; -public class FloorRenderer { +public class FloorRenderer{ private final static int chunksize = 64; private Chunk[][] cache; @@ -38,6 +38,35 @@ public class FloorRenderer { Events.on(WorldLoadGraphicsEvent.class, this::clearTiles); } + static ShaderProgram createDefaultShader(){ + String vertexShader = "attribute vec4 " + ShaderProgram.POSITION_ATTRIBUTE + ";\n" // + + "attribute vec2 " + ShaderProgram.TEXCOORD_ATTRIBUTE + "0;\n" // + + "uniform mat4 u_projTrans;\n" // + + "varying vec2 v_texCoords;\n" // + + "\n" // + + "void main()\n" // + + "{\n" // + + " v_texCoords = " + ShaderProgram.TEXCOORD_ATTRIBUTE + "0;\n" // + + " gl_Position = u_projTrans * " + ShaderProgram.POSITION_ATTRIBUTE + ";\n" // + + "}\n"; + String fragmentShader = "#ifdef GL_ES\n" // + + "#define LOWP lowp\n" // + + "precision mediump float;\n" // + + "#else\n" // + + "#define LOWP \n" // + + "#endif\n" // + + "varying vec2 v_texCoords;\n" // + + "uniform sampler2D u_texture;\n" // + + "void main()\n"// + + "{\n" // + + " gl_FragColor = texture2D(u_texture, v_texCoords);\n" // + + "}"; + + ShaderProgram shader = new ShaderProgram(vertexShader, fragmentShader); + if(!shader.isCompiled()) throw new IllegalArgumentException("Error compiling shader: " + shader.getLog()); + return shader; + } + public void drawFloor(){ if(cache == null){ return; @@ -45,11 +74,11 @@ public class FloorRenderer { OrthographicCamera camera = Core.camera; - int crangex = (int)(camera.viewportWidth * camera.zoom / (chunksize * tilesize))+1; - int crangey = (int)(camera.viewportHeight * camera.zoom / (chunksize * tilesize))+1; + int crangex = (int) (camera.viewportWidth * camera.zoom / (chunksize * tilesize)) + 1; + int crangey = (int) (camera.viewportHeight * camera.zoom / (chunksize * tilesize)) + 1; - for(int x = -crangex; x <= crangex; x++) { - for (int y = -crangey; y <= crangey; y++) { + for(int x = -crangex; x <= crangex; x++){ + for(int y = -crangey; y <= crangey; y++){ int worldx = Mathf.scl(camera.position.x, chunksize * tilesize) + x; int worldy = Mathf.scl(camera.position.y, chunksize * tilesize) + y; @@ -71,13 +100,13 @@ public class FloorRenderer { int worldx = Mathf.scl(camera.position.x, chunksize * tilesize) + x; int worldy = Mathf.scl(camera.position.y, chunksize * tilesize) + y; - if (!Mathf.inBounds(worldx, worldy, cache)) + if(!Mathf.inBounds(worldx, worldy, cache)) continue; Chunk chunk = cache[worldx][worldy]; //loop through all layers, and add layer index if it exists - for(int i = 0; i < layers - 1; i ++){ + for(int i = 0; i < layers - 1; i++){ if(chunk.caches[i] != -1){ drawnLayerSet.add(i); } @@ -95,7 +124,7 @@ public class FloorRenderer { Graphics.end(); beginDraw(); - for(int i = 0; i < drawnLayers.size; i ++) { + for(int i = 0; i < drawnLayers.size; i++){ CacheLayer layer = CacheLayer.values()[drawnLayers.get(i)]; drawLayer(layer); @@ -131,13 +160,13 @@ public class FloorRenderer { OrthographicCamera camera = Core.camera; - int crangex = (int)(camera.viewportWidth * camera.zoom / (chunksize * tilesize))+1; - int crangey = (int)(camera.viewportHeight * camera.zoom / (chunksize * tilesize))+1; + int crangex = (int) (camera.viewportWidth * camera.zoom / (chunksize * tilesize)) + 1; + int crangey = (int) (camera.viewportHeight * camera.zoom / (chunksize * tilesize)) + 1; layer.begin(); - for (int x = -crangex; x <= crangex; x++) { - for (int y = -crangey; y <= crangey; y++) { + for(int x = -crangex; x <= crangex; x++){ + for(int y = -crangey; y <= crangey; y++){ int worldx = Mathf.scl(camera.position.x, chunksize * tilesize) + x; int worldy = Mathf.scl(camera.position.y, chunksize * tilesize) + y; @@ -166,12 +195,12 @@ public class FloorRenderer { ObjectSet used = new ObjectSet<>(); - for(int tilex = cx * chunksize; tilex < (cx + 1) * chunksize; tilex++) { - for (int tiley = cy * chunksize; tiley < (cy + 1) * chunksize; tiley++) { + for(int tilex = cx * chunksize; tilex < (cx + 1) * chunksize; tilex++){ + for(int tiley = cy * chunksize; tiley < (cy + 1) * chunksize; tiley++){ Tile tile = world.tile(tilex, tiley); - if (tile != null){ + if(tile != null){ used.add(tile.block().cacheLayer == CacheLayer.walls ? - CacheLayer.walls : tile.floor().cacheLayer); + CacheLayer.walls : tile.floor().cacheLayer); } } } @@ -180,7 +209,7 @@ public class FloorRenderer { cacheChunkLayer(cx, cy, chunk, layer); } - // Log.info("Time to cache a chunk: {0}", TimeUtils.timeSinceNanos(time) / 1000000f); + // Log.info("Time to cache a chunk: {0}", TimeUtils.timeSinceNanos(time) / 1000000f); } private void cacheChunkLayer(int cx, int cy, Chunk chunk, CacheLayer layer){ @@ -211,25 +240,21 @@ public class FloorRenderer { chunk.caches[layer.ordinal()] = cbatch.getLastCache(); } - private class Chunk{ - int[] caches = new int[CacheLayer.values().length]; - } - public void clearTiles(){ if(cbatch != null) cbatch.dispose(); Timers.mark(); - int chunksx = Mathf.ceil((float)world.width() / chunksize), chunksy = Mathf.ceil((float)world.height() / chunksize); + int chunksx = Mathf.ceil((float) world.width() / chunksize), chunksy = Mathf.ceil((float) world.height() / chunksize); cache = new Chunk[chunksx][chunksy]; - cbatch = new CacheBatch(world.width()*world.height()*4*4); + cbatch = new CacheBatch(world.width() * world.height() * 4 * 4); Log.info("Time to create: {0}", Timers.elapsed()); Timers.mark(); - for (int x = 0; x < chunksx; x++) { - for (int y = 0; y < chunksy; y++) { + for(int x = 0; x < chunksx; x++){ + for(int y = 0; y < chunksy; y++){ cache[x][y] = new Chunk(); Arrays.fill(cache[x][y].caches, -1); @@ -240,32 +265,7 @@ public class FloorRenderer { Log.info("Time to cache: {0}", Timers.elapsed()); } - static ShaderProgram createDefaultShader () { - String vertexShader = "attribute vec4 " + ShaderProgram.POSITION_ATTRIBUTE + ";\n" // - + "attribute vec2 " + ShaderProgram.TEXCOORD_ATTRIBUTE + "0;\n" // - + "uniform mat4 u_projTrans;\n" // - + "varying vec2 v_texCoords;\n" // - + "\n" // - + "void main()\n" // - + "{\n" // - + " v_texCoords = " + ShaderProgram.TEXCOORD_ATTRIBUTE + "0;\n" // - + " gl_Position = u_projTrans * " + ShaderProgram.POSITION_ATTRIBUTE + ";\n" // - + "}\n"; - String fragmentShader = "#ifdef GL_ES\n" // - + "#define LOWP lowp\n" // - + "precision mediump float;\n" // - + "#else\n" // - + "#define LOWP \n" // - + "#endif\n" // - + "varying vec2 v_texCoords;\n" // - + "uniform sampler2D u_texture;\n" // - + "void main()\n"// - + "{\n" // - + " gl_FragColor = texture2D(u_texture, v_texCoords);\n" // - + "}"; - - ShaderProgram shader = new ShaderProgram(vertexShader, fragmentShader); - if (!shader.isCompiled()) throw new IllegalArgumentException("Error compiling shader: " + shader.getLog()); - return shader; + private class Chunk{ + int[] caches = new int[CacheLayer.values().length]; } } diff --git a/core/src/io/anuke/mindustry/graphics/FogRenderer.java b/core/src/io/anuke/mindustry/graphics/FogRenderer.java index 2e8c2ba52b..6ebdfb8f90 100644 --- a/core/src/io/anuke/mindustry/graphics/FogRenderer.java +++ b/core/src/io/anuke/mindustry/graphics/FogRenderer.java @@ -24,7 +24,9 @@ import java.nio.ByteBuffer; import static io.anuke.mindustry.Vars.*; -/**Used for rendering fog of war. A framebuffer is used for this.*/ +/** + * Used for rendering fog of war. A framebuffer is used for this. + */ public class FogRenderer implements Disposable{ private TextureRegion region = new TextureRegion(); private FrameBuffer buffer; @@ -42,8 +44,8 @@ public class FogRenderer implements Disposable{ Graphics.clear(0, 0, 0, 1f); buffer.end(); - for (int x = 0; x < world.width(); x++) { - for (int y = 0; y < world.height(); y++) { + for(int x = 0; x < world.width(); x++){ + for(int y = 0; y < world.height(); y++){ Tile tile = world.tile(x, y); if(tile.getTeam() == players[0].getTeam() && tile.block().synthetic() && tile.block().viewRange > 0){ @@ -66,14 +68,14 @@ public class FogRenderer implements Disposable{ float vw = Core.camera.viewportWidth * Core.camera.zoom; float vh = Core.camera.viewportHeight * Core.camera.zoom; - float px = Core.camera.position.x -= vw/2f; - float py = Core.camera.position.y -= vh/2f; + float px = Core.camera.position.x -= vw / 2f; + float py = Core.camera.position.y -= vh / 2f; float u = px / tilesize / world.width(); float v = py / tilesize / world.height(); - float u2 = (px + vw)/ tilesize / world.width(); - float v2 = (py + vh)/ tilesize / world.height(); + float u2 = (px + vw) / tilesize / world.width(); + float v2 = (py + vh) / tilesize / world.height(); if(Core.batch instanceof ClipSpriteBatch){ ((ClipSpriteBatch) Core.batch).enableClip(false); @@ -137,7 +139,7 @@ public class FogRenderer implements Disposable{ } @Override - public void dispose() { + public void dispose(){ if(buffer != null) buffer.dispose(); } } diff --git a/core/src/io/anuke/mindustry/graphics/Layer.java b/core/src/io/anuke/mindustry/graphics/Layer.java index 3bf5949605..f04144a2cd 100644 --- a/core/src/io/anuke/mindustry/graphics/Layer.java +++ b/core/src/io/anuke/mindustry/graphics/Layer.java @@ -1,16 +1,28 @@ package io.anuke.mindustry.graphics; public enum Layer{ - /**Base block layer.*/ - block, - /**for placement*/ + /** + * Base block layer. + */ + block, + /** + * for placement + */ placement, - /**First overlay. Stuff like conveyor items.*/ - overlay, - /**"High" blocks, like turrets.*/ - turret, - /**Power lasers.*/ - power, - /**Extra lasers, like healing turrets.*/ - laser + /** + * First overlay. Stuff like conveyor items. + */ + overlay, + /** + * "High" blocks, like turrets. + */ + turret, + /** + * Power lasers. + */ + power, + /** + * Extra lasers, like healing turrets. + */ + laser } diff --git a/core/src/io/anuke/mindustry/graphics/MinimapRenderer.java b/core/src/io/anuke/mindustry/graphics/MinimapRenderer.java index 096c4455a7..2760dbdc78 100644 --- a/core/src/io/anuke/mindustry/graphics/MinimapRenderer.java +++ b/core/src/io/anuke/mindustry/graphics/MinimapRenderer.java @@ -54,7 +54,7 @@ public class MinimapRenderer implements Disposable{ public void zoomBy(int amount){ zoom += amount; - zoom = Mathf.clamp(zoom, 1, Math.min(world.width(), world.height())/baseSize/2); + zoom = Mathf.clamp(zoom, 1, Math.min(world.width(), world.height()) / baseSize / 2); } public void reset(){ @@ -71,10 +71,10 @@ public class MinimapRenderer implements Disposable{ int sz = baseSize * zoom; float dx = (Core.camera.position.x / tilesize); float dy = (Core.camera.position.y / tilesize); - dx = Mathf.clamp(dx, sz, world.width()-sz); - dy = Mathf.clamp(dy, sz, world.height()-sz); + dx = Mathf.clamp(dx, sz, world.width() - sz); + dy = Mathf.clamp(dy, sz, world.height() - sz); - synchronized (units){ + synchronized(units){ rect.set((dx - sz) * tilesize, (dy - sz) * tilesize, sz * 2 * tilesize, sz * 2 * tilesize); Graphics.flush(); @@ -93,24 +93,24 @@ public class MinimapRenderer implements Disposable{ } } - public TextureRegion getRegion() { + public TextureRegion getRegion(){ if(texture == null) return null; int sz = Mathf.clamp(baseSize * zoom, baseSize, Math.min(world.width(), world.height())); float dx = (Core.camera.position.x / tilesize); float dy = (Core.camera.position.y / tilesize); - dx = Mathf.clamp(dx, sz, world.width()-sz); - dy = Mathf.clamp(dy, sz, world.height()-sz); + dx = Mathf.clamp(dx, sz, world.width() - sz); + dy = Mathf.clamp(dy, sz, world.height() - sz); float invTexWidth = 1f / texture.getWidth(); float invTexHeight = 1f / texture.getHeight(); - float x = dx - sz, y = world.height()-dy - sz, width = sz*2, height = sz*2; + float x = dx - sz, y = world.height() - dy - sz, width = sz * 2, height = sz * 2; region.setRegion(x * invTexWidth, y * invTexHeight, (x + width) * invTexWidth, (y + height) * invTexHeight); return region; } public void updateAll(){ - for(int x = 0; x < world.width(); x ++){ - for(int y = 0; y < world.height(); y ++){ + for(int x = 0; x < world.width(); x++){ + for(int y = 0; y < world.height(); y++){ pixmap.drawPixel(x, pixmap.getHeight() - 1 - y, colorFor(world.tile(x, y))); } } @@ -128,10 +128,10 @@ public class MinimapRenderer implements Disposable{ int sz = baseSize * zoom; float dx = (Core.camera.position.x / tilesize); float dy = (Core.camera.position.y / tilesize); - dx = Mathf.clamp(dx, sz, world.width()-sz); - dy = Mathf.clamp(dy, sz, world.height()-sz); + dx = Mathf.clamp(dx, sz, world.width() - sz); + dy = Mathf.clamp(dy, sz, world.height() - sz); - synchronized (units) { + synchronized(units){ rect.set((dx - sz) * tilesize, (dy - sz) * tilesize, sz * 2 * tilesize, sz * 2 * tilesize); units.clear(); Units.getNearby(rect, units::add); @@ -142,7 +142,7 @@ public class MinimapRenderer implements Disposable{ int color = tile.breakable() ? tile.target().getTeam().intColor : ColorMapper.getBlockColor(tile.block()); if(color == 0) color = ColorMapper.getBlockColor(tile.floor()); if(tile.elevation > 0){ - float mul = 1.1f+tile.elevation/4f; + float mul = 1.1f + tile.elevation / 4f; tmpColor.set(color); tmpColor.mul(mul, mul, mul, 1f); color = Color.rgba8888(tmpColor); @@ -151,7 +151,7 @@ public class MinimapRenderer implements Disposable{ } @Override - public void dispose() { + public void dispose(){ pixmap.dispose(); texture.dispose(); texture = null; diff --git a/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java b/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java index dd23a56dd5..d207cb61b4 100644 --- a/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java +++ b/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java @@ -12,7 +12,6 @@ import io.anuke.mindustry.game.TeamInfo.TeamData; import io.anuke.mindustry.input.InputHandler; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.consumers.Consume; import io.anuke.mindustry.world.meta.BlockBar; import io.anuke.ucore.core.Graphics; import io.anuke.ucore.core.Settings; @@ -25,10 +24,10 @@ import io.anuke.ucore.util.Mathf; import static io.anuke.mindustry.Vars.*; -public class OverlayRenderer { +public class OverlayRenderer{ public void drawBottom(){ - for(Player player : players) { + for(Player player : players){ InputHandler input = control.input(player.playerIndex); if(!input.isDrawing() || player.isDead()) continue; @@ -44,7 +43,7 @@ public class OverlayRenderer { public void drawTop(){ - for(Player player : players) { + for(Player player : players){ if(player.isDead()) continue; //dead player don't draw InputHandler input = control.input(player.playerIndex); @@ -53,7 +52,7 @@ public class OverlayRenderer { if(input.frag.config.isShown()){ Tile tile = input.frag.config.getSelectedTile(); - synchronized (Tile.tileSetLock) { + synchronized(Tile.tileSetLock){ tile.block().drawConfigure(tile); } } @@ -63,14 +62,14 @@ public class OverlayRenderer { Draw.reset(); //draw selected block bars and info - if (input.recipe == null && !ui.hasMouse() && !input.frag.config.isShown()) { + if(input.recipe == null && !ui.hasMouse() && !input.frag.config.isShown()){ Vector2 vec = Graphics.world(input.getMouseX(), input.getMouseY()); Tile tile = world.tileWorld(vec.x, vec.y); - if (tile != null && tile.block() != Blocks.air) { + if(tile != null && tile.block() != Blocks.air){ Tile target = tile.target(); - if (showBlockDebug && target.entity != null) { + if(showBlockDebug && target.entity != null){ Draw.color(Color.RED); Lines.crect(target.drawx(), target.drawy(), target.block().size * tilesize, target.block().size * tilesize); Vector2 v = new Vector2(); @@ -79,7 +78,7 @@ public class OverlayRenderer { Draw.tscl(0.25f); Array arr = target.block().getDebugInfo(target); StringBuilder result = new StringBuilder(); - for (int i = 0; i < arr.size / 2; i++) { + for(int i = 0; i < arr.size / 2; i++){ result.append(arr.get(i * 2)); result.append(": "); result.append(arr.get(i * 2 + 1)); @@ -93,27 +92,27 @@ public class OverlayRenderer { Draw.reset(); } - synchronized (Tile.tileSetLock) { + synchronized(Tile.tileSetLock){ Block block = target.block(); TileEntity entity = target.entity; - if (entity != null) { + if(entity != null){ int[] values = {0, 0}; boolean[] doDraw = {false}; Callable drawbars = () -> { - for (BlockBar bar : block.bars.list()) { + for(BlockBar bar : block.bars.list()){ float offset = Mathf.sign(bar.top) * (block.size / 2f * tilesize + 2f + (bar.top ? values[0] : values[1])); float value = bar.value.get(target); - if (MathUtils.isEqual(value, -1f)) continue; + if(MathUtils.isEqual(value, -1f)) continue; if(doDraw[0]){ drawBar(bar.type.color, target.drawx(), target.drawy() + offset, value); } - if (bar.top) + if(bar.top) values[0]++; else values[1]++; @@ -123,11 +122,11 @@ public class OverlayRenderer { drawbars.run(); if(values[0] > 0){ - drawEncloser(target.drawx(), target.drawy() + block.size * tilesize/2f + 2f, values[0]); + drawEncloser(target.drawx(), target.drawy() + block.size * tilesize / 2f + 2f, values[0]); } if(values[1] > 0){ - drawEncloser(target.drawx(), target.drawy() - block.size * tilesize/2f - 2f - values[1], values[1]); + drawEncloser(target.drawx(), target.drawy() - block.size * tilesize / 2f - 2f - values[1], values[1]); } doDraw[0] = true; @@ -143,7 +142,7 @@ public class OverlayRenderer { } } - if (input.isDroppingItem()) { + if(input.isDroppingItem()){ Vector2 v = Graphics.world(input.getMouseX(), input.getMouseY()); float size = 8; Draw.rect(player.inventory.getItem().item.region, v.x, v.y, size, size); @@ -152,8 +151,8 @@ public class OverlayRenderer { Draw.reset(); Tile tile = world.tileWorld(v.x, v.y); - if (tile != null) tile = tile.target(); - if (tile != null && tile.block().acceptStack(player.inventory.getItem().item, player.inventory.getItem().amount, tile, player) > 0) { + if(tile != null) tile = tile.target(); + if(tile != null && tile.block().acceptStack(player.inventory.getItem().item, player.inventory.getItem().amount, tile, player) > 0){ Draw.color(Palette.place); Lines.square(tile.drawx(), tile.drawy(), tile.block().size * tilesize / 2f + 1 + Mathf.absin(Timers.time(), 5f, 1f)); Draw.color(); @@ -177,9 +176,9 @@ public class OverlayRenderer { float x = unit.x; float y = unit.y; - if(unit == players[0] && players.length == 1 && snapCamera) { - x = (int)(x + 0.0001f); - y = (int)(y + 0.0001f); + if(unit == players[0] && players.length == 1 && snapCamera){ + x = (int) (x + 0.0001f); + y = (int) (y + 0.0001f); } drawEncloser(x, y - 9f, 2f); @@ -197,7 +196,7 @@ public class OverlayRenderer { float w = (int) (len * 2 * finion); Draw.color(Color.BLACK); - Fill.crect(x - len, y, len*2f, 1); + Fill.crect(x - len, y, len * 2f, 1); if(finion > 0){ Draw.color(color); Fill.crect(x - len, y, Math.max(1, w), 1); @@ -210,7 +209,7 @@ public class OverlayRenderer { float len = 4; Draw.color(Palette.bar); - Fill.crect(x - len, y - 1, len*2f, height + 2f); + Fill.crect(x - len, y - 1, len * 2f, height + 2f); Draw.color(); } } diff --git a/core/src/io/anuke/mindustry/graphics/Palette.java b/core/src/io/anuke/mindustry/graphics/Palette.java index b717d2441f..1f369df6f2 100644 --- a/core/src/io/anuke/mindustry/graphics/Palette.java +++ b/core/src/io/anuke/mindustry/graphics/Palette.java @@ -2,7 +2,7 @@ package io.anuke.mindustry.graphics; import com.badlogic.gdx.graphics.Color; -public class Palette { +public class Palette{ public static final Color bulletYellow = Color.valueOf("ffeec9"); public static final Color bulletYellowBack = Color.valueOf("f9c87a"); diff --git a/core/src/io/anuke/mindustry/graphics/Shaders.java b/core/src/io/anuke/mindustry/graphics/Shaders.java index 21c02966f5..996d8e07fe 100644 --- a/core/src/io/anuke/mindustry/graphics/Shaders.java +++ b/core/src/io/anuke/mindustry/graphics/Shaders.java @@ -13,100 +13,100 @@ import static io.anuke.mindustry.Vars.tilesize; import static io.anuke.mindustry.Vars.world; public class Shaders{ - public static Outline outline; + public static Outline outline; public static BlockBuild blockbuild; public static BlockPreview blockpreview; - public static Shield shield; - public static SurfaceShader water; - public static SurfaceShader lava; - public static SurfaceShader oil; - public static Space space; - public static UnitBuild build; - public static MixShader mix; - public static Shader fullMix; - public static FogShader fog; + public static Shield shield; + public static SurfaceShader water; + public static SurfaceShader lava; + public static SurfaceShader oil; + public static Space space; + public static UnitBuild build; + public static MixShader mix; + public static Shader fullMix; + public static FogShader fog; - public static void init(){ - outline = new Outline(); - blockbuild = new BlockBuild(); - blockpreview = new BlockPreview(); - shield = new Shield(); - water = new SurfaceShader("water"); - lava = new SurfaceShader("lava"); - oil = new SurfaceShader("oil"); - space = new Space(); - build = new UnitBuild(); - mix = new MixShader(); - fog = new FogShader(); - fullMix = new Shader("fullmix", "default"); - } + public static void init(){ + outline = new Outline(); + blockbuild = new BlockBuild(); + blockpreview = new BlockPreview(); + shield = new Shield(); + water = new SurfaceShader("water"); + lava = new SurfaceShader("lava"); + oil = new SurfaceShader("oil"); + space = new Space(); + build = new UnitBuild(); + mix = new MixShader(); + fog = new FogShader(); + fullMix = new Shader("fullmix", "default"); + } - public static class FogShader extends Shader{ - public FogShader(){ - super("fog", "default"); - } - } + public static class FogShader extends Shader{ + public FogShader(){ + super("fog", "default"); + } + } - public static class MixShader extends Shader{ - public Color color = new Color(Color.WHITE); + public static class MixShader extends Shader{ + public Color color = new Color(Color.WHITE); - public MixShader(){ - super("mix", "default"); - } + public MixShader(){ + super("mix", "default"); + } - @Override - public void apply() { - super.apply(); - shader.setUniformf("u_color", color); - } - } + @Override + public void apply(){ + super.apply(); + shader.setUniformf("u_color", color); + } + } - public static class Space extends SurfaceShader{ + public static class Space extends SurfaceShader{ - public Space(){ - super("space2"); - } + public Space(){ + super("space2"); + } - @Override - public void apply(){ - super.apply(); - shader.setUniformf("u_center", world.width() * tilesize/2f, world.height() * tilesize/2f); - } - } + @Override + public void apply(){ + super.apply(); + shader.setUniformf("u_center", world.width() * tilesize / 2f, world.height() * tilesize / 2f); + } + } - public static class UnitBuild extends Shader{ - public float progress, time; - public Color color = new Color(); - public TextureRegion region; + public static class UnitBuild extends Shader{ + public float progress, time; + public Color color = new Color(); + public TextureRegion region; - public UnitBuild() { - super("build", "default"); - } + public UnitBuild(){ + super("build", "default"); + } - @Override - public void apply(){ - shader.setUniformf("u_time", time); - shader.setUniformf("u_color", color); - shader.setUniformf("u_progress", progress); - shader.setUniformf("u_uv", region.getU(), region.getV()); - shader.setUniformf("u_uv2", region.getU2(), region.getV2()); - shader.setUniformf("u_texsize", region.getTexture().getWidth(), region.getTexture().getHeight()); - } - } + @Override + public void apply(){ + shader.setUniformf("u_time", time); + shader.setUniformf("u_color", color); + shader.setUniformf("u_progress", progress); + shader.setUniformf("u_uv", region.getU(), region.getV()); + shader.setUniformf("u_uv2", region.getU2(), region.getV2()); + shader.setUniformf("u_texsize", region.getTexture().getWidth(), region.getTexture().getHeight()); + } + } - public static class Outline extends Shader{ - public Color color = new Color(); + public static class Outline extends Shader{ + public Color color = new Color(); - public Outline(){ - super("outline", "default"); - } - - @Override - public void apply(){ - shader.setUniformf("u_color", color); - shader.setUniformf("u_texsize", region.getTexture().getWidth(), region.getTexture().getHeight()); - } - } + public Outline(){ + super("outline", "default"); + } + + @Override + public void apply(){ + shader.setUniformf("u_color", color); + shader.setUniformf("u_texsize", region.getTexture().getWidth(), region.getTexture().getHeight()); + } + } public static class BlockBuild extends Shader{ public Color color = new Color(); @@ -136,7 +136,7 @@ public class Shaders{ @Override public void apply(){ - // shader.setUniformf("u_progress", progress); + // shader.setUniformf("u_progress", progress); shader.setUniformf("u_color", color); shader.setUniformf("u_uv", region.getU(), region.getV()); shader.setUniformf("u_uv2", region.getU2(), region.getV2()); @@ -144,49 +144,49 @@ public class Shaders{ shader.setUniformf("u_texsize", region.getTexture().getWidth(), region.getTexture().getHeight()); } } - - public static class Shield extends Shader{ - public static final int MAX_HITS = 3*64; - public Color color = new Color(); - public FloatArray hits; - - public Shield(){ - super("shield", "default"); - } - - @Override - public void apply(){ - float scaling = Core.cameraScale / 4f / Core.camera.zoom; - if(hits.size > 0){ - shader.setUniform3fv("u_hits[0]", hits.items, 0, Math.min(hits.size, MAX_HITS)); - shader.setUniformi("u_hitamount", Math.min(hits.size, MAX_HITS)/3); - } - shader.setUniformf("u_dp", Unit.dp.scl(1f)); - shader.setUniformf("u_color", color); - shader.setUniformf("u_time", Timers.time() / Unit.dp.scl(1f)); - shader.setUniformf("u_scaling", scaling); - shader.setUniformf("u_offset", - Core.camera.position.x - Core.camera.viewportWidth/2 * Core.camera.zoom, - Core.camera.position.y - Core.camera.viewportHeight/2 * Core.camera.zoom); - shader.setUniformf("u_texsize", Gdx.graphics.getWidth() / Core.cameraScale * Core.camera.zoom, - Gdx.graphics.getHeight() / Core.cameraScale * Core.camera.zoom); - } - } - public static class SurfaceShader extends Shader{ + public static class Shield extends Shader{ + public static final int MAX_HITS = 3 * 64; + public Color color = new Color(); + public FloatArray hits; - public SurfaceShader(String frag){ - super(frag, "default"); - } + public Shield(){ + super("shield", "default"); + } - @Override - public void apply(){ - shader.setUniformf("camerapos", - Core.camera.position.x - Core.camera.viewportWidth/2 * Core.camera.zoom, - Core.camera.position.y - Core.camera.viewportHeight/2 * Core.camera.zoom); - shader.setUniformf("screensize", Gdx.graphics.getWidth() / Core.cameraScale * Core.camera.zoom, - Gdx.graphics.getHeight() / Core.cameraScale * Core.camera.zoom); - shader.setUniformf("time", Timers.time()); - } - } + @Override + public void apply(){ + float scaling = Core.cameraScale / 4f / Core.camera.zoom; + if(hits.size > 0){ + shader.setUniform3fv("u_hits[0]", hits.items, 0, Math.min(hits.size, MAX_HITS)); + shader.setUniformi("u_hitamount", Math.min(hits.size, MAX_HITS) / 3); + } + shader.setUniformf("u_dp", Unit.dp.scl(1f)); + shader.setUniformf("u_color", color); + shader.setUniformf("u_time", Timers.time() / Unit.dp.scl(1f)); + shader.setUniformf("u_scaling", scaling); + shader.setUniformf("u_offset", + Core.camera.position.x - Core.camera.viewportWidth / 2 * Core.camera.zoom, + Core.camera.position.y - Core.camera.viewportHeight / 2 * Core.camera.zoom); + shader.setUniformf("u_texsize", Gdx.graphics.getWidth() / Core.cameraScale * Core.camera.zoom, + Gdx.graphics.getHeight() / Core.cameraScale * Core.camera.zoom); + } + } + + public static class SurfaceShader extends Shader{ + + public SurfaceShader(String frag){ + super(frag, "default"); + } + + @Override + public void apply(){ + shader.setUniformf("camerapos", + Core.camera.position.x - Core.camera.viewportWidth / 2 * Core.camera.zoom, + Core.camera.position.y - Core.camera.viewportHeight / 2 * Core.camera.zoom); + shader.setUniformf("screensize", Gdx.graphics.getWidth() / Core.cameraScale * Core.camera.zoom, + Gdx.graphics.getHeight() / Core.cameraScale * Core.camera.zoom); + shader.setUniformf("time", Timers.time()); + } + } } diff --git a/core/src/io/anuke/mindustry/graphics/Trail.java b/core/src/io/anuke/mindustry/graphics/Trail.java index 0a21f06d3e..badf28a389 100644 --- a/core/src/io/anuke/mindustry/graphics/Trail.java +++ b/core/src/io/anuke/mindustry/graphics/Trail.java @@ -8,8 +8,10 @@ import io.anuke.ucore.graphics.Fill; import io.anuke.ucore.graphics.Lines; import io.anuke.ucore.util.Mathf; -/**Class that renders a trail.*/ -public class Trail { +/** + * Class that renders a trail. + */ +public class Trail{ private final static float maxJump = 15f; private final int length; private final FloatArray points = new FloatArray(); @@ -26,7 +28,7 @@ public class Trail { points.add(curx, cury); - if(points.size > length*2) { + if(points.size > length * 2){ float[] items = points.items; System.arraycopy(items, 2, items, 0, points.size - 2); points.size -= 2; @@ -48,14 +50,14 @@ public class Trail { float y = points.get(i + 1); float x2 = points.get(i + 2); float y2 = points.get(i + 3); - float s = Mathf.clamp((float)(i) / points.size); + float s = Mathf.clamp((float) (i) / points.size); Lines.stroke(s * stroke); Lines.line(x, y, x2, y2); } if(points.size >= 2){ - Fill.circle(points.get(points.size-2), points.get(points.size-1), stroke/2f); + Fill.circle(points.get(points.size - 2), points.get(points.size - 1), stroke / 2f); } Draw.reset(); diff --git a/core/src/io/anuke/mindustry/input/CursorType.java b/core/src/io/anuke/mindustry/input/CursorType.java index fd55b7998e..3b8773f6cb 100644 --- a/core/src/io/anuke/mindustry/input/CursorType.java +++ b/core/src/io/anuke/mindustry/input/CursorType.java @@ -3,8 +3,10 @@ package io.anuke.mindustry.input; import io.anuke.ucore.function.Callable; import io.anuke.ucore.scene.utils.Cursors; -/**Type of cursor for displaying on desktop.*/ -public enum CursorType { +/** + * Type of cursor for displaying on desktop. + */ +public enum CursorType{ normal(Cursors::restoreCursor), hand(Cursors::setHand), drill(() -> Cursors.set("drill")), @@ -16,7 +18,9 @@ public enum CursorType { this.call = call; } - /**Sets the current system cursor to this.*/ + /** + * Sets the current system cursor to this. + */ void set(){ call.run(); } diff --git a/core/src/io/anuke/mindustry/input/DefaultKeybinds.java b/core/src/io/anuke/mindustry/input/DefaultKeybinds.java index 4941ba130e..94e07bf924 100644 --- a/core/src/io/anuke/mindustry/input/DefaultKeybinds.java +++ b/core/src/io/anuke/mindustry/input/DefaultKeybinds.java @@ -8,58 +8,58 @@ import io.anuke.ucore.core.KeyBinds; import io.anuke.ucore.core.KeyBinds.Category; import io.anuke.ucore.input.Input; -public class DefaultKeybinds { +public class DefaultKeybinds{ public static void load(){ String[] sections = {"player_1"}; - for(String section : sections) { + for(String section : sections){ KeyBinds.defaultSection(section, DeviceType.keyboard, - new Category("General"), - "move_x", new Axis(Input.A, Input.D), - "move_y", new Axis(Input.S, Input.W), - //"select", Input.MOUSE_LEFT, - //"break", Input.MOUSE_RIGHT, - //"shoot", Input.MOUSE_LEFT, - "rotate", new Axis(Input.SCROLL), - "dash", Input.SHIFT_LEFT, - "drop_unit", Input.SHIFT_LEFT, - new Category("View"), - "zoom_hold", Input.CONTROL_LEFT, - "zoom", new Axis(Input.SCROLL), - "zoom_minimap", new Axis(Input.MINUS, Input.PLUS), - "menu", Gdx.app.getType() == ApplicationType.Android ? Input.BACK : Input.ESCAPE, - "pause", Input.SPACE, - "toggle_menus", Input.C, - new Category("Multiplayer"), - "player_list", Input.TAB, - "chat", Input.ENTER, - "chat_history_prev", Input.UP, - "chat_history_next", Input.DOWN, - "chat_scroll", new Axis(Input.SCROLL), - "console", Input.GRAVE + new Category("General"), + "move_x", new Axis(Input.A, Input.D), + "move_y", new Axis(Input.S, Input.W), + //"select", Input.MOUSE_LEFT, + //"break", Input.MOUSE_RIGHT, + //"shoot", Input.MOUSE_LEFT, + "rotate", new Axis(Input.SCROLL), + "dash", Input.SHIFT_LEFT, + "drop_unit", Input.SHIFT_LEFT, + new Category("View"), + "zoom_hold", Input.CONTROL_LEFT, + "zoom", new Axis(Input.SCROLL), + "zoom_minimap", new Axis(Input.MINUS, Input.PLUS), + "menu", Gdx.app.getType() == ApplicationType.Android ? Input.BACK : Input.ESCAPE, + "pause", Input.SPACE, + "toggle_menus", Input.C, + new Category("Multiplayer"), + "player_list", Input.TAB, + "chat", Input.ENTER, + "chat_history_prev", Input.UP, + "chat_history_next", Input.DOWN, + "chat_scroll", new Axis(Input.SCROLL), + "console", Input.GRAVE ); KeyBinds.defaultSection(section, DeviceType.controller, - new Category("General"), - "move_x", new Axis(Input.CONTROLLER_L_STICK_HORIZONTAL_AXIS), - "move_y", new Axis(Input.CONTROLLER_L_STICK_VERTICAL_AXIS), - "cursor_x", new Axis(Input.CONTROLLER_R_STICK_HORIZONTAL_AXIS), - "cursor_y", new Axis(Input.CONTROLLER_R_STICK_VERTICAL_AXIS), - //"select", Input.CONTROLLER_R_BUMPER, - //"break", Input.CONTROLLER_L_BUMPER, - //"shoot", Input.CONTROLLER_R_TRIGGER, - "dash", Input.CONTROLLER_Y, - "rotate_alt", new Axis(Input.CONTROLLER_DPAD_RIGHT, Input.CONTROLLER_DPAD_LEFT), - "rotate", new Axis(Input.CONTROLLER_A, Input.CONTROLLER_B), - new Category("View"), - "zoom_hold", Input.ANY_KEY, - "zoom", new Axis(Input.CONTROLLER_DPAD_DOWN, Input.CONTROLLER_DPAD_UP), - "menu", Input.CONTROLLER_X, - "pause", Input.CONTROLLER_L_TRIGGER, - new Category("Multiplayer"), - "player_list", Input.CONTROLLER_START + new Category("General"), + "move_x", new Axis(Input.CONTROLLER_L_STICK_HORIZONTAL_AXIS), + "move_y", new Axis(Input.CONTROLLER_L_STICK_VERTICAL_AXIS), + "cursor_x", new Axis(Input.CONTROLLER_R_STICK_HORIZONTAL_AXIS), + "cursor_y", new Axis(Input.CONTROLLER_R_STICK_VERTICAL_AXIS), + //"select", Input.CONTROLLER_R_BUMPER, + //"break", Input.CONTROLLER_L_BUMPER, + //"shoot", Input.CONTROLLER_R_TRIGGER, + "dash", Input.CONTROLLER_Y, + "rotate_alt", new Axis(Input.CONTROLLER_DPAD_RIGHT, Input.CONTROLLER_DPAD_LEFT), + "rotate", new Axis(Input.CONTROLLER_A, Input.CONTROLLER_B), + new Category("View"), + "zoom_hold", Input.ANY_KEY, + "zoom", new Axis(Input.CONTROLLER_DPAD_DOWN, Input.CONTROLLER_DPAD_UP), + "menu", Input.CONTROLLER_X, + "pause", Input.CONTROLLER_L_TRIGGER, + new Category("Multiplayer"), + "player_list", Input.CONTROLLER_START ); } diff --git a/core/src/io/anuke/mindustry/input/DesktopInput.java b/core/src/io/anuke/mindustry/input/DesktopInput.java index dc1d462627..a156939e1a 100644 --- a/core/src/io/anuke/mindustry/input/DesktopInput.java +++ b/core/src/io/anuke/mindustry/input/DesktopInput.java @@ -27,40 +27,49 @@ import static io.anuke.mindustry.input.CursorType.*; import static io.anuke.mindustry.input.PlaceMode.*; public class DesktopInput extends InputHandler{ - //controller info - private float controlx, controly; - private boolean controlling; private final String section; - - /**Current cursor type.*/ + //controller info + private float controlx, controly; + private boolean controlling; + /** + * Current cursor type. + */ private CursorType cursorType = normal; - /**Position where the player started dragging a line.*/ + /** + * Position where the player started dragging a line. + */ private int selectX, selectY; - /**Whether selecting mode is active.*/ + /** + * Whether selecting mode is active. + */ private PlaceMode mode; - /**Animation scale for line.*/ + /** + * Animation scale for line. + */ private float selectScale; - public DesktopInput(Player player){ - super(player); - this.section = "player_" + (player.playerIndex + 1); + public DesktopInput(Player player){ + super(player); + this.section = "player_" + (player.playerIndex + 1); } - /**Draws a placement icon for a specific block.*/ - void drawPlace(int x, int y, Block block, int rotation){ + /** + * Draws a placement icon for a specific block. + */ + void drawPlace(int x, int y, Block block, int rotation){ if(validPlace(x, y, block, rotation)){ Draw.color(); TextureRegion[] regions = block.getBlockIcon(); for(TextureRegion region : regions){ - Draw.rect(region, x *tilesize + block.offset(), y * tilesize + block.offset(), + Draw.rect(region, x * tilesize + block.offset(), y * tilesize + block.offset(), region.getRegionWidth() * selectScale, region.getRegionHeight() * selectScale, block.rotate ? rotation * 90 : 0); } }else{ Draw.color(Palette.remove); - Lines.square(x*tilesize + block.offset(), y*tilesize + block.offset(), block.size * tilesize/2f); + Lines.square(x * tilesize + block.offset(), y * tilesize + block.offset(), block.size * tilesize / 2f); } } @@ -75,15 +84,15 @@ public class DesktopInput extends InputHandler{ if(cursor == null) return; - //draw selection(s) - if(mode == placing) { + //draw selection(s) + if(mode == placing){ NormalizeResult result = PlaceUtils.normalizeArea(selectX, selectY, cursor.x, cursor.y, rotation, true, maxLength); - for (int i = 0; i <= result.getLength(); i += recipe.result.size) { + for(int i = 0; i <= result.getLength(); i += recipe.result.size){ int x = selectX + i * Mathf.sign(cursor.x - selectX) * Mathf.bool(result.isX()); int y = selectY + i * Mathf.sign(cursor.y - selectY) * Mathf.bool(!result.isX()); - if (i + recipe.result.size > result.getLength() && recipe.result.rotate) { + if(i + recipe.result.size > result.getLength() && recipe.result.rotate){ Draw.color(!validPlace(x, y, recipe.result, result.rotation) ? Palette.remove : Palette.placeRotate); Draw.grect("place-arrow", x * tilesize + recipe.result.offset(), y * tilesize + recipe.result.offset(), result.rotation * 90 - 90); @@ -99,37 +108,37 @@ public class DesktopInput extends InputHandler{ Draw.color(Palette.remove); - for(int x = dresult.x; x <= dresult.x2; x ++){ - for(int y = dresult.y; y <= dresult.y2; y ++){ + for(int x = dresult.x; x <= dresult.x2; x++){ + for(int y = dresult.y; y <= dresult.y2; y++){ Tile tile = world.tile(x, y); if(tile == null || !validBreak(tile.x, tile.y)) continue; tile = tile.target(); - Lines.poly(tile.drawx(), tile.drawy(), 4, tile.block().size * tilesize/2f, 45 + 15); + Lines.poly(tile.drawx(), tile.drawy(), 4, tile.block().size * tilesize / 2f, 45 + 15); } } Lines.rect(result.x, result.y, result.x2 - result.x, result.y2 - result.y); }else if(isPlacing()){ - if(recipe.result.rotate){ - Draw.color(!validPlace(cursor.x, cursor.y, recipe.result, rotation) ? Palette.remove : Palette.placeRotate); - Draw.grect("place-arrow", cursor.worldx() + recipe.result.offset(), + if(recipe.result.rotate){ + Draw.color(!validPlace(cursor.x, cursor.y, recipe.result, rotation) ? Palette.remove : Palette.placeRotate); + Draw.grect("place-arrow", cursor.worldx() + recipe.result.offset(), cursor.worldy() + recipe.result.offset(), rotation * 90 - 90); } drawPlace(cursor.x, cursor.y, recipe.result, rotation); - recipe.result.drawPlace(cursor.x, cursor.y, rotation, validPlace(cursor.x, cursor.y, recipe.result, rotation)); + recipe.result.drawPlace(cursor.x, cursor.y, rotation, validPlace(cursor.x, cursor.y, recipe.result, rotation)); } Draw.reset(); } - @Override - public void update(){ + @Override + public void update(){ if(Net.active() && Inputs.keyTap("player_list")){ ui.listfrag.toggle(); } - if(player.isDead() || state.is(State.menu) || ui.hasDialog()) return; + if(player.isDead() || state.is(State.menu) || ui.hasDialog()) return; if(recipe != null && !Settings.getBool("desktop-place-help", false)){ ui.showInfo("Desktop controls have been changed.\nTo deselect a block or stop building, [accent]use the middle mouse button[]."); @@ -137,40 +146,40 @@ public class DesktopInput extends InputHandler{ Settings.save(); } - player.isBoosting = Inputs.keyDown("dash"); + player.isBoosting = Inputs.keyDown("dash"); - //deslect if not placing - if(!isPlacing() && mode == placing){ - mode = none; + //deslect if not placing + if(!isPlacing() && mode == placing){ + mode = none; } if(player.isShooting && !canShoot()){ - player.isShooting = false; + player.isShooting = false; } if(isPlacing()){ cursorType = hand; selectScale = Mathf.lerpDelta(selectScale, 1f, 0.2f); }else{ - selectScale = 0f; + selectScale = 0f; } - boolean controller = KeyBinds.getSection(section).device.type == DeviceType.controller; + boolean controller = KeyBinds.getSection(section).device.type == DeviceType.controller; - //zoom and rotate things - if(Inputs.getAxisActive("zoom") && (Inputs.keyDown(section,"zoom_hold") || controller)){ - renderer.scaleCamera((int) Inputs.getAxisTapped(section, "zoom")); - } + //zoom and rotate things + if(Inputs.getAxisActive("zoom") && (Inputs.keyDown(section, "zoom_hold") || controller)){ + renderer.scaleCamera((int) Inputs.getAxisTapped(section, "zoom")); + } - renderer.minimap().zoomBy(-(int)Inputs.getAxisTapped(section,"zoom_minimap")); - rotation = Mathf.mod(rotation + (int)Inputs.getAxisTapped(section,"rotate"), 4); + renderer.minimap().zoomBy(-(int) Inputs.getAxisTapped(section, "zoom_minimap")); + rotation = Mathf.mod(rotation + (int) Inputs.getAxisTapped(section, "rotate"), 4); - Tile cursor = tileAt(control.gdxInput().getX(), control.gdxInput().getY()); + Tile cursor = tileAt(control.gdxInput().getX(), control.gdxInput().getY()); - if(player.isDead()){ + if(player.isDead()){ cursorType = normal; }else if(cursor != null){ - cursor = cursor.target(); + cursor = cursor.target(); cursorType = cursor.block().getCursor(cursor); @@ -187,15 +196,15 @@ public class DesktopInput extends InputHandler{ } } - if(!ui.hasMouse()) { - cursorType.set(); - } + if(!ui.hasMouse()){ + cursorType.set(); + } cursorType = normal; - } + } - @Override - public boolean touchDown (int screenX, int screenY, int pointer, int button) { + @Override + public boolean touchDown(int screenX, int screenY, int pointer, int button){ if(player.isDead() || state.is(State.menu) || ui.hasDialog() || ui.hasMouse()) return false; Tile cursor = tileAt(screenX, screenY); @@ -203,12 +212,12 @@ public class DesktopInput extends InputHandler{ float worldx = Graphics.world(screenX, screenY).x, worldy = Graphics.world(screenX, screenY).y; - if(button == Buttons.LEFT) { //left = begin placing - if (isPlacing()) { + if(button == Buttons.LEFT){ //left = begin placing + if(isPlacing()){ selectX = cursor.x; selectY = cursor.y; mode = placing; - } else { + }else{ //only begin shooting if there's no cursor event if(!tileTapped(cursor) && !tryTapPlayer(worldx, worldy) && player.getPlaceQueue().size == 0 && !droppingItem && !tryBeginMine(cursor) && player.getMineTile() == null){ @@ -232,7 +241,7 @@ public class DesktopInput extends InputHandler{ } @Override - public boolean touchUp (int screenX, int screenY, int pointer, int button) { + public boolean touchUp(int screenX, int screenY, int pointer, int button){ if(button == Buttons.LEFT){ player.isShooting = false; } @@ -260,8 +269,8 @@ public class DesktopInput extends InputHandler{ }else if(mode == breaking){ //touch up while breaking, break everything in selection NormalizeResult result = PlaceUtils.normalizeArea(selectX, selectY, cursor.x, cursor.y, rotation, false, maxLength); - for(int x = 0; x <= Math.abs(result.x2 - result.x); x ++ ){ - for(int y = 0; y <= Math.abs(result.y2 - result.y); y ++){ + for(int x = 0; x <= Math.abs(result.x2 - result.x); x++){ + for(int y = 0; y <= Math.abs(result.y2 - result.y); y++){ int wx = selectX + x * Mathf.sign(cursor.x - selectX); int wy = selectY + y * Mathf.sign(cursor.y - selectY); @@ -278,23 +287,23 @@ public class DesktopInput extends InputHandler{ } @Override - public float getMouseX() { + public float getMouseX(){ return !controlling ? control.gdxInput().getX() : controlx; } @Override - public float getMouseY() { + public float getMouseY(){ return !controlling ? control.gdxInput().getY() : controly; } @Override - public boolean isCursorVisible() { + public boolean isCursorVisible(){ return controlling; } @Override public void updateController(){ - boolean mousemove = Gdx.input.getDeltaX() > 1 || Gdx.input.getDeltaY() > 1; + boolean mousemove = Gdx.input.getDeltaX() > 1 || Gdx.input.getDeltaY() > 1; if(KeyBinds.getSection(section).device.type == DeviceType.controller && (!mousemove || player.playerIndex > 0)){ if(player.playerIndex > 0){ @@ -312,17 +321,17 @@ public class DesktopInput extends InputHandler{ float xa = Inputs.getAxis(section, "cursor_x"); float ya = Inputs.getAxis(section, "cursor_y"); - if(Math.abs(xa) > controllerMin || Math.abs(ya) > controllerMin) { - float scl = Settings.getInt("sensitivity", 100)/100f * Unit.dp.scl(1f); - controlx += xa*baseControllerSpeed*scl; - controly -= ya*baseControllerSpeed*scl; + if(Math.abs(xa) > controllerMin || Math.abs(ya) > controllerMin){ + float scl = Settings.getInt("sensitivity", 100) / 100f * Unit.dp.scl(1f); + controlx += xa * baseControllerSpeed * scl; + controly -= ya * baseControllerSpeed * scl; controlling = true; if(player.playerIndex == 0){ Gdx.input.setCursorCatched(true); } - Inputs.getProcessor().touchDragged((int)getMouseX(), (int)getMouseY(), player.playerIndex); + Inputs.getProcessor().touchDragged((int) getMouseX(), (int) getMouseY(), player.playerIndex); } controlx = Mathf.clamp(controlx, 0, Gdx.graphics.getWidth()); diff --git a/core/src/io/anuke/mindustry/input/InputHandler.java b/core/src/io/anuke/mindustry/input/InputHandler.java index 689a637db1..b3f094a889 100644 --- a/core/src/io/anuke/mindustry/input/InputHandler.java +++ b/core/src/io/anuke/mindustry/input/InputHandler.java @@ -33,226 +33,307 @@ import io.anuke.ucore.util.Translator; import static io.anuke.mindustry.Vars.*; public abstract class InputHandler extends InputAdapter{ - /**Used for dropping items.*/ + /** + * Used for dropping items. + */ final static float playerSelectRange = mobile ? 17f : 11f; - /**Maximum line length.*/ - final static int maxLength = 100; + /** + * Maximum line length. + */ + final static int maxLength = 100; final static Translator stackTrns = new Translator(); - /**Distance on the back from where items originate.*/ - final static float backTrns = 3f; + /** + * Distance on the back from where items originate. + */ + final static float backTrns = 3f; - public final Player player; - public final String section; - public final OverlayFragment frag = new OverlayFragment(this); + public final Player player; + public final String section; + public final OverlayFragment frag = new OverlayFragment(this); - public Recipe recipe; - public int rotation; - public boolean droppingItem; + public Recipe recipe; + public int rotation; + public boolean droppingItem; - public InputHandler(Player player){ - this.player = player; - this.section = "player_" + (player.playerIndex + 1); - Timers.run(1f, () -> frag.build(Core.scene.getRoot())); + public InputHandler(Player player){ + this.player = player; + this.section = "player_" + (player.playerIndex + 1); + Timers.run(1f, () -> frag.build(Core.scene.getRoot())); } //methods to override - public void update(){ + @Remote(targets = Loc.client, called = Loc.server, in = In.entities) + public static void dropItem(Player player, float angle){ + if(Net.server() && !player.inventory.hasItem()){ + throw new ValidateException(player, "Player cannot drop an item."); + } - } - - public float getMouseX(){ - return control.gdxInput().getX(); - } - - public float getMouseY(){ - return control.gdxInput().getY(); + ItemDrop.create(player.inventory.getItem().item, player.inventory.getItem().amount, player.x, player.y, angle); + player.inventory.clearItem(); } - public void resetCursor(){ + @Remote(targets = Loc.both, forward = true, called = Loc.server, in = In.blocks) + public static void transferInventory(Player player, Tile tile){ + if(Net.server() && (!player.inventory.hasItem() || player.isTransferring)){ + throw new ValidateException(player, "Player cannot transfer an item."); + } - } + threads.run(() -> { + if(player == null || tile.entity == null) return; - public boolean isCursorVisible(){ - return false; - } + player.isTransferring = true; - public void buildUI(Group group){ + ItemStack stack = player.inventory.getItem(); + int accepted = tile.block().acceptStack(stack.item, stack.amount, tile, player); - } + boolean clear = stack.amount == accepted; + int sent = Mathf.clamp(accepted / 4, 1, 8); + int removed = accepted / sent; + int[] remaining = {accepted, accepted}; - public void updateController(){ + for(int i = 0; i < sent; i++){ + boolean end = i == sent - 1; + Timers.run(i * 3, () -> { + tile.block().getStackOffset(stack.item, tile, stackTrns); - } + ItemTransfer.create(stack.item, + player.x + Angles.trnsx(player.rotation + 180f, backTrns), player.y + Angles.trnsy(player.rotation + 180f, backTrns), + new Translator(tile.drawx() + stackTrns.x, tile.drawy() + stackTrns.y), () -> { - public void drawOutlined(){ + tile.block().handleStack(stack.item, removed, tile, player); + remaining[1] -= removed; - } + if(end && remaining[1] > 0){ + tile.block().handleStack(stack.item, remaining[1], tile, player); + } + }); - public void drawTop(){ + stack.amount -= removed; + remaining[0] -= removed; - } + if(end){ + stack.amount -= remaining[0]; + if(clear){ + player.inventory.clearItem(); + } + player.isTransferring = false; + } + }); + } + }); + } - public boolean isDrawing(){ - return false; - } + @Remote(targets = Loc.both, called = Loc.server, forward = true, in = In.blocks) + public static void onTileTapped(Player player, Tile tile){ + if(tile == null || player == null) return; + tile.block().tapped(tile, player); + } - /**Handles tile tap events that are not platform specific.*/ - boolean tileTapped(Tile tile){ - tile = tile.target(); + public void update(){ - boolean consumed = false, showedInventory = false, showedConsume = false; + } - //check if tapped block is configurable - if(tile.block().configurable && tile.getTeam() == player.getTeam()){ - consumed = true; - if(((!frag.config.isShown() && tile.block().shouldShowConfigure(tile, player)) //if the config fragment is hidden, show - //alternatively, the current selected block can 'agree' to switch config tiles - || (frag.config.isShown() && frag.config.getSelectedTile().block().onConfigureTileTapped(frag.config.getSelectedTile(), tile)))) { - frag.config.showConfig(tile); - } - //otherwise... - }else if(!frag.config.hasConfigMouse()){ //make sure a configuration fragment isn't on the cursor - //then, if it's shown and the current block 'agrees' to hide, hide it. - if(frag.config.isShown() && frag.config.getSelectedTile().block().onConfigureTileTapped(frag.config.getSelectedTile(), tile)) { - consumed = true; - frag.config.hideConfig(); - } - } + public float getMouseX(){ + return control.gdxInput().getX(); + } - //call tapped event - if(tile.getTeam() == player.getTeam()){ - CallBlocks.onTileTapped(player, tile); - } + public float getMouseY(){ + return control.gdxInput().getY(); + } - //consume tap event if necessary - if(tile.getTeam() == player.getTeam() && tile.block().consumesTap){ - consumed = true; - }else if(tile.getTeam() == player.getTeam() && tile.block().synthetic() && !consumed) { - if(tile.block().hasItems && tile.entity.items.total() > 0) { - frag.inv.showFor(tile); - consumed = true; - showedInventory = true; - } + public void resetCursor(){ - if(tile.block().consumes.hasAny()){ - frag.consume.show(tile); - consumed = true; - showedConsume = true; - } - } + } - if(!showedInventory){ - frag.inv.hide(); - } + public boolean isCursorVisible(){ + return false; + } + + public void buildUI(Group group){ + + } + + public void updateController(){ + + } + + public void drawOutlined(){ + + } + + public void drawTop(){ + + } + + public boolean isDrawing(){ + return false; + } + + /** + * Handles tile tap events that are not platform specific. + */ + boolean tileTapped(Tile tile){ + tile = tile.target(); + + boolean consumed = false, showedInventory = false, showedConsume = false; + + //check if tapped block is configurable + if(tile.block().configurable && tile.getTeam() == player.getTeam()){ + consumed = true; + if(((!frag.config.isShown() && tile.block().shouldShowConfigure(tile, player)) //if the config fragment is hidden, show + //alternatively, the current selected block can 'agree' to switch config tiles + || (frag.config.isShown() && frag.config.getSelectedTile().block().onConfigureTileTapped(frag.config.getSelectedTile(), tile)))){ + frag.config.showConfig(tile); + } + //otherwise... + }else if(!frag.config.hasConfigMouse()){ //make sure a configuration fragment isn't on the cursor + //then, if it's shown and the current block 'agrees' to hide, hide it. + if(frag.config.isShown() && frag.config.getSelectedTile().block().onConfigureTileTapped(frag.config.getSelectedTile(), tile)){ + consumed = true; + frag.config.hideConfig(); + } + } + + //call tapped event + if(tile.getTeam() == player.getTeam()){ + CallBlocks.onTileTapped(player, tile); + } + + //consume tap event if necessary + if(tile.getTeam() == player.getTeam() && tile.block().consumesTap){ + consumed = true; + }else if(tile.getTeam() == player.getTeam() && tile.block().synthetic() && !consumed){ + if(tile.block().hasItems && tile.entity.items.total() > 0){ + frag.inv.showFor(tile); + consumed = true; + showedInventory = true; + } + + if(tile.block().consumes.hasAny()){ + frag.consume.show(tile); + consumed = true; + showedConsume = true; + } + } + + if(!showedInventory){ + frag.inv.hide(); + } if(!showedConsume){ frag.consume.hide(); } - return consumed; - } + return consumed; + } - /**Tries to select the player to drop off items, returns true if successful.*/ - boolean tryTapPlayer(float x, float y){ - if(canTapPlayer(x, y)){ - droppingItem = true; - return true; - } - return false; - } + /** + * Tries to select the player to drop off items, returns true if successful. + */ + boolean tryTapPlayer(float x, float y){ + if(canTapPlayer(x, y)){ + droppingItem = true; + return true; + } + return false; + } - boolean canTapPlayer(float x, float y){ - return Vector2.dst(x, y, player.x, player.y) <= playerSelectRange && player.inventory.hasItem(); - } + boolean canTapPlayer(float x, float y){ + return Vector2.dst(x, y, player.x, player.y) <= playerSelectRange && player.inventory.hasItem(); + } - /**Tries to begin mining a tile, returns true if successful.*/ - boolean tryBeginMine(Tile tile){ - if(canMine(tile)){ - //if a block is clicked twice, reset it - player.setMineTile(player.getMineTile() == tile ? null : tile); - return true; - } - return false; - } + /** + * Tries to begin mining a tile, returns true if successful. + */ + boolean tryBeginMine(Tile tile){ + if(canMine(tile)){ + //if a block is clicked twice, reset it + player.setMineTile(player.getMineTile() == tile ? null : tile); + return true; + } + return false; + } - boolean canMine(Tile tile){ - return !ui.hasMouse() - && tile.floor().drops != null && tile.floor().drops.item.hardness <= player.mech.drillPower - && !tile.floor().playerUnmineable - && player.inventory.canAcceptItem(tile.floor().drops.item) - && Units.getClosestEnemy(player.getTeam(), tile.worldx(), tile.worldy(), 40f, e -> true) == null //don't being mining when an enemy is near - && tile.block() == Blocks.air && player.distanceTo(tile.worldx(), tile.worldy()) <= Player.mineDistance; - } + boolean canMine(Tile tile){ + return !ui.hasMouse() + && tile.floor().drops != null && tile.floor().drops.item.hardness <= player.mech.drillPower + && !tile.floor().playerUnmineable + && player.inventory.canAcceptItem(tile.floor().drops.item) + && Units.getClosestEnemy(player.getTeam(), tile.worldx(), tile.worldy(), 40f, e -> true) == null //don't being mining when an enemy is near + && tile.block() == Blocks.air && player.distanceTo(tile.worldx(), tile.worldy()) <= Player.mineDistance; + } - /**Returns the tile at the specified MOUSE coordinates.*/ - Tile tileAt(float x, float y){ - Vector2 vec = Graphics.world(x, y); - if(isPlacing()){ - vec.sub(recipe.result.offset(), recipe.result.offset()); - } - return world.tileWorld(vec.x, vec.y); - } + /** + * Returns the tile at the specified MOUSE coordinates. + */ + Tile tileAt(float x, float y){ + Vector2 vec = Graphics.world(x, y); + if(isPlacing()){ + vec.sub(recipe.result.offset(), recipe.result.offset()); + } + return world.tileWorld(vec.x, vec.y); + } - public boolean isPlacing(){ - return recipe != null; - } + public boolean isPlacing(){ + return recipe != null; + } - public float mouseAngle(float x, float y){ + public float mouseAngle(float x, float y){ return Graphics.world(getMouseX(), getMouseY()).sub(x, y).angle(); } - public void remove(){ - Inputs.removeProcessor(this); - frag.remove(); + public void remove(){ + Inputs.removeProcessor(this); + frag.remove(); } - public boolean canShoot(){ - return recipe == null && !ui.hasMouse() && !onConfigurable() && !isDroppingItem(); - } - - public boolean onConfigurable(){ - return false; - } + public boolean canShoot(){ + return recipe == null && !ui.hasMouse() && !onConfigurable() && !isDroppingItem(); + } - public boolean isDroppingItem(){ - return droppingItem; - } + public boolean onConfigurable(){ + return false; + } - public void tryDropItems(Tile tile, float x, float y){ - if(!droppingItem || !player.inventory.hasItem() || canTapPlayer(x, y)){ - droppingItem = false; - return; - } + public boolean isDroppingItem(){ + return droppingItem; + } - droppingItem = false; + public void tryDropItems(Tile tile, float x, float y){ + if(!droppingItem || !player.inventory.hasItem() || canTapPlayer(x, y)){ + droppingItem = false; + return; + } - ItemStack stack = player.inventory.getItem(); + droppingItem = false; - if(tile.block().acceptStack(stack.item, stack.amount, tile, player) > 0 && tile.block().hasItems){ - CallBlocks.transferInventory(player, tile); - }else{ - CallEntity.dropItem(player.angleTo(x, y)); - } - } + ItemStack stack = player.inventory.getItem(); - public boolean cursorNear(){ - return true; - } - - public void tryPlaceBlock(int x, int y){ - if(recipe != null && validPlace(x, y, recipe.result, rotation) && cursorNear()){ - placeBlock(x, y, recipe, rotation); - } - } - - public void tryBreakBlock(int x, int y){ - if(cursorNear() && validBreak(x, y)){ - breakBlock(x, y); - } - } - - public boolean validPlace(int x, int y, Block type, int rotation){ + if(tile.block().acceptStack(stack.item, stack.amount, tile, player) > 0 && tile.block().hasItems){ + CallBlocks.transferInventory(player, tile); + }else{ + CallEntity.dropItem(player.angleTo(x, y)); + } + } + + public boolean cursorNear(){ + return true; + } + + public void tryPlaceBlock(int x, int y){ + if(recipe != null && validPlace(x, y, recipe.result, rotation) && cursorNear()){ + placeBlock(x, y, recipe, rotation); + } + } + + public void tryBreakBlock(int x, int y){ + if(cursorNear() && validBreak(x, y)){ + breakBlock(x, y); + } + } + + public boolean validPlace(int x, int y, Block type, int rotation){ for(Tile tile : state.teams.get(player.getTeam()).cores){ if(tile.distanceTo(x * tilesize, y * tilesize) < coreBuildRange){ return Build.validPlace(player.getTeam(), x, y, type, rotation) && @@ -261,89 +342,22 @@ public abstract class InputHandler extends InputAdapter{ } return false; - } - - public boolean validBreak(int x, int y){ - return Build.validBreak(player.getTeam(), x, y); - } - - public void placeBlock(int x, int y, Recipe recipe, int rotation){ + } + + public boolean validBreak(int x, int y){ + return Build.validBreak(player.getTeam(), x, y); + } + + public void placeBlock(int x, int y, Recipe recipe, int rotation){ //todo multiplayer support player.addBuildRequest(new BuildRequest(x, y, rotation, recipe)); - } + } - public void breakBlock(int x, int y){ + public void breakBlock(int x, int y){ - //todo multiplayer support - Tile tile = world.tile(x, y).target(); - player.addBuildRequest(new BuildRequest(tile.x, tile.y)); - } - - @Remote(targets = Loc.client, called = Loc.server, in = In.entities) - public static void dropItem(Player player, float angle){ - if(Net.server() && !player.inventory.hasItem()){ - throw new ValidateException(player, "Player cannot drop an item."); - } - - ItemDrop.create(player.inventory.getItem().item, player.inventory.getItem().amount, player.x, player.y, angle); - player.inventory.clearItem(); - } - - @Remote(targets = Loc.both, forward = true, called = Loc.server, in = In.blocks) - public static void transferInventory(Player player, Tile tile){ - if(Net.server() && (!player.inventory.hasItem() || player.isTransferring)) { - throw new ValidateException(player, "Player cannot transfer an item."); - } - - threads.run(() -> { - if (player == null || tile.entity == null) return; - - player.isTransferring = true; - - ItemStack stack = player.inventory.getItem(); - int accepted = tile.block().acceptStack(stack.item, stack.amount, tile, player); - - boolean clear = stack.amount == accepted; - int sent = Mathf.clamp(accepted / 4, 1, 8); - int removed = accepted / sent; - int[] remaining = {accepted, accepted}; - - for (int i = 0; i < sent; i++) { - boolean end = i == sent - 1; - Timers.run(i * 3, () -> { - tile.block().getStackOffset(stack.item, tile, stackTrns); - - ItemTransfer.create(stack.item, - player.x + Angles.trnsx(player.rotation + 180f, backTrns), player.y + Angles.trnsy(player.rotation + 180f, backTrns), - new Translator(tile.drawx() + stackTrns.x, tile.drawy() + stackTrns.y), () -> { - - tile.block().handleStack(stack.item, removed, tile, player); - remaining[1] -= removed; - - if (end && remaining[1] > 0) { - tile.block().handleStack(stack.item, remaining[1], tile, player); - } - }); - - stack.amount -= removed; - remaining[0] -= removed; - - if (end) { - stack.amount -= remaining[0]; - if (clear) { - player.inventory.clearItem(); - } - player.isTransferring = false; - } - }); - } - }); - } - - @Remote(targets = Loc.both, called = Loc.server, forward = true, in = In.blocks) - public static void onTileTapped(Player player, Tile tile){ - if(tile == null || player == null) return; - tile.block().tapped(tile, player); - } + //todo multiplayer support + Tile tile = world.tile(x, y).target(); + player.addBuildRequest(new BuildRequest(tile.x, tile.y)); + } } diff --git a/core/src/io/anuke/mindustry/input/MobileInput.java b/core/src/io/anuke/mindustry/input/MobileInput.java index 9a260cf94d..4094aa118f 100644 --- a/core/src/io/anuke/mindustry/input/MobileInput.java +++ b/core/src/io/anuke/mindustry/input/MobileInput.java @@ -39,11 +39,14 @@ import static io.anuke.mindustry.Vars.*; import static io.anuke.mindustry.input.PlaceMode.*; public class MobileInput extends InputHandler implements GestureListener{ - private static Rectangle r1 = new Rectangle(), r2 = new Rectangle(); - - /**Maximum speed the player can pan.*/ + /** + * Maximum speed the player can pan. + */ private static final float maxPanSpeed = 1.3f; - /**Distance to edge of screen to start panning.*/ + private static Rectangle r1 = new Rectangle(), r2 = new Rectangle(); + /** + * Distance to edge of screen to start panning. + */ private final float edgePan = io.anuke.ucore.scene.ui.layout.Unit.dp.scl(60f); //gesture data @@ -51,64 +54,92 @@ public class MobileInput extends InputHandler implements GestureListener{ private Vector2 vector = new Vector2(); private float initzoom = -1; private boolean zoomed = false; - /**Set of completed guides.*/ + /** + * Set of completed guides. + */ private ObjectSet guides = new ObjectSet<>(); - /**Position where the player started dragging a line.*/ + /** + * Position where the player started dragging a line. + */ private int lineStartX, lineStartY; - /**Animation scale for line.*/ + /** + * Animation scale for line. + */ private float lineScale; - /**Animation data for crosshair.*/ + /** + * Animation data for crosshair. + */ private float crosshairScale; private TargetTrait lastTarget; - /**List of currently selected tiles to place.*/ + /** + * List of currently selected tiles to place. + */ private Array selection = new Array<>(); - /**Place requests to be removed.*/ + /** + * Place requests to be removed. + */ private Array removals = new Array<>(); - /**Whether or not the player is currently shifting all placed tiles.*/ + /** + * Whether or not the player is currently shifting all placed tiles. + */ private boolean selecting; - /**Whether the player is currently in line-place mode.*/ + /** + * Whether the player is currently in line-place mode. + */ private boolean lineMode; - /**Current place mode.*/ + /** + * Current place mode. + */ private PlaceMode mode = none; - /**Whether no recipe was available when switching to break mode.*/ + /** + * Whether no recipe was available when switching to break mode. + */ private Recipe lastRecipe; - /**Last placed request. Used for drawing block overlay.*/ + /** + * Last placed request. Used for drawing block overlay. + */ private PlaceRequest lastPlaced; - - public MobileInput(Player player){ - super(player); - Inputs.addProcessor(new GestureDetector(20, 0.5f, 0.4f, 0.15f, this)); - } - //region utility methods + public MobileInput(Player player){ + super(player); + Inputs.addProcessor(new GestureDetector(20, 0.5f, 0.4f, 0.15f, this)); + } - /**Check and assign targets for a specific position.*/ + //region utility methods + + /** + * Check and assign targets for a specific position. + */ void checkTargets(float x, float y){ - synchronized (Entities.entityLock) { + synchronized(Entities.entityLock){ Unit unit = Units.getClosestEnemy(player.getTeam(), x, y, 20f, u -> true); - if (unit != null) { + if(unit != null){ player.target = unit; - } else { + }else{ Tile tile = world.tileWorld(x, y); - if (tile != null) tile = tile.target(); + if(tile != null) tile = tile.target(); - if (tile != null && state.teams.areEnemies(player.getTeam(), tile.getTeam())) { + if(tile != null && state.teams.areEnemies(player.getTeam(), tile.getTeam())){ player.target = tile.entity; } } } } - /**Returns whether this tile is in the list of requests, or at least colliding with one.*/ - boolean hasRequest(Tile tile){ + /** + * Returns whether this tile is in the list of requests, or at least colliding with one. + */ + boolean hasRequest(Tile tile){ return getRequest(tile) != null; } - /**Returns whether this block overlaps any selection requests.*/ + /** + * Returns whether this block overlaps any selection requests. + */ boolean checkOverlapPlacement(int x, int y, Block block){ r2.setSize(block.size * tilesize); r2.setCenter(x * tilesize + block.offset(), y * tilesize + block.offset()); @@ -125,13 +156,15 @@ public class MobileInput extends InputHandler implements GestureListener{ return true; } } - return false; + return false; } - /**Returns the selection request that overlaps this tile, or null.*/ + /** + * Returns the selection request that overlaps this tile, or null. + */ PlaceRequest getRequest(Tile tile){ - r2.setSize(tilesize); - r2.setCenter(tile.worldx(), tile.worldy()); + r2.setSize(tilesize); + r2.setCenter(tile.worldx(), tile.worldy()); for(PlaceRequest req : selection){ Tile other = req.tile(); @@ -142,15 +175,15 @@ public class MobileInput extends InputHandler implements GestureListener{ r1.setSize(req.recipe.result.size * tilesize); r1.setCenter(other.worldx() + req.recipe.result.offset(), other.worldy() + req.recipe.result.offset()); - if (r2.overlaps(r1)) { + if(r2.overlaps(r1)){ return req; } - }else { + }else{ r1.setSize(other.block().size * tilesize); r1.setCenter(other.worldx() + other.block().offset(), other.worldy() + other.block().offset()); - if (r2.overlaps(r1)) { + if(r2.overlaps(r1)){ return req; } } @@ -166,7 +199,7 @@ public class MobileInput extends InputHandler implements GestureListener{ void drawRequest(PlaceRequest request){ Tile tile = request.tile(); - if(!request.remove) { + if(!request.remove){ //draw placing request float offset = request.recipe.result.offset(); TextureRegion[] regions = request.recipe.result.getBlockIcon(); @@ -174,7 +207,7 @@ public class MobileInput extends InputHandler implements GestureListener{ Draw.alpha(Mathf.clamp((1f - request.scale) / 0.5f)); Draw.tint(Color.WHITE, Palette.breakInvalid, request.redness); - for (TextureRegion region : regions) { + for(TextureRegion region : regions){ Draw.rect(region, tile.worldx() + offset, tile.worldy() + offset, region.getRegionWidth() * request.scale, region.getRegionHeight() * request.scale, request.recipe.result.rotate ? request.rotation * 90 : 0); @@ -182,7 +215,7 @@ public class MobileInput extends InputHandler implements GestureListener{ }else{ Draw.color(Palette.remove); //draw removing request - Lines.poly(tile.drawx(), tile.drawy(), 4, tile.block().size * tilesize/2f * request.scale, 45 + 15); + Lines.poly(tile.drawx(), tile.drawy(), 4, tile.block().size * tilesize / 2f * request.scale, 45 + 15); } } @@ -207,9 +240,9 @@ public class MobileInput extends InputHandler implements GestureListener{ //region UI and drawing @Override - public void buildUI(Group group) { + public void buildUI(Group group){ - //Create confirm/cancel table + //Create confirm/cancel table new table(){{ abottom().aleft(); @@ -227,12 +260,12 @@ public class MobileInput extends InputHandler implements GestureListener{ //Add an accept button, which places everything. new imagebutton("icon-check", 16 * 2f, () -> { - for (PlaceRequest request : selection) { + for(PlaceRequest request : selection){ Tile tile = request.tile(); //actually place/break all selected blocks - if (tile != null) { - if(!request.remove) { + if(tile != null){ + if(!request.remove){ rotation = request.rotation; recipe = request.recipe; tryPlaceBlock(tile.x, tile.y); @@ -292,12 +325,12 @@ public class MobileInput extends InputHandler implements GestureListener{ } @Override - public boolean isPlacing() { + public boolean isPlacing(){ return super.isPlacing() && mode == placing; } @Override - public void drawOutlined(){ + public void drawOutlined(){ //Draw.color(Palette.placing); //Lines.poly(player.x, player.y, 100, Player.placeDistance); @@ -324,11 +357,11 @@ public class MobileInput extends InputHandler implements GestureListener{ if(tile == null) continue; - if ((!request.remove && validPlace(tile.x, tile.y, request.recipe.result, request.rotation)) - || (request.remove && validBreak(tile.x, tile.y))) { + if((!request.remove && validPlace(tile.x, tile.y, request.recipe.result, request.rotation)) + || (request.remove && validBreak(tile.x, tile.y))){ request.scale = Mathf.lerpDelta(request.scale, 1f, 0.2f); request.redness = Mathf.lerpDelta(request.redness, 0f, 0.2f); - } else { + }else{ request.scale = Mathf.lerpDelta(request.scale, 0.5f, 0.1f); request.redness = Mathf.lerpDelta(request.redness, 1f, 0.2f); } @@ -353,7 +386,7 @@ public class MobileInput extends InputHandler implements GestureListener{ if(tile != null){ //draw placing - if(mode == placing && recipe != null) { + if(mode == placing && recipe != null){ NormalizeDrawResult dresult = PlaceUtils.normalizeDrawArea(recipe.result, lineStartX, lineStartY, tile.x, tile.y, true, maxLength, lineScale); Lines.rect(dresult.x, dresult.y, dresult.x2 - dresult.x, dresult.y2 - dresult.y); @@ -361,20 +394,20 @@ public class MobileInput extends InputHandler implements GestureListener{ NormalizeResult result = PlaceUtils.normalizeArea(lineStartX, lineStartY, tile.x, tile.y, rotation, true, maxLength); //go through each cell and draw the block to place if valid - for (int i = 0; i <= result.getLength(); i += recipe.result.size) { + for(int i = 0; i <= result.getLength(); i += recipe.result.size){ int x = lineStartX + i * Mathf.sign(tile.x - lineStartX) * Mathf.bool(result.isX()); int y = lineStartY + i * Mathf.sign(tile.y - lineStartY) * Mathf.bool(!result.isX()); - if (!checkOverlapPlacement(x, y, recipe.result) && validPlace(x, y, recipe.result, result.rotation)) { + if(!checkOverlapPlacement(x, y, recipe.result) && validPlace(x, y, recipe.result, result.rotation)){ Draw.color(); TextureRegion[] regions = recipe.result.getBlockIcon(); - for (TextureRegion region : regions) { + for(TextureRegion region : regions){ Draw.rect(region, x * tilesize + recipe.result.offset(), y * tilesize + recipe.result.offset(), region.getRegionWidth() * lineScale, region.getRegionHeight() * lineScale, recipe.result.rotate ? result.rotation * 90 : 0); } - } else { + }else{ Draw.color(Palette.breakInvalid); Lines.square(x * tilesize + recipe.result.offset(), y * tilesize + recipe.result.offset(), recipe.result.size * tilesize / 2f); } @@ -390,13 +423,13 @@ public class MobileInput extends InputHandler implements GestureListener{ Draw.alpha(0.6f); Draw.alpha(1f); - for(int x = dresult.x; x <= dresult.x2; x ++){ - for(int y = dresult.y; y <= dresult.y2; y ++){ + for(int x = dresult.x; x <= dresult.x2; x++){ + for(int y = dresult.y; y <= dresult.y2; y++){ Tile other = world.tile(x, y); if(other == null || !validBreak(other.x, other.y)) continue; other = other.target(); - Lines.poly(other.drawx(), other.drawy(), 4, other.block().size * tilesize/2f, 45 + 15); + Lines.poly(other.drawx(), other.drawy(), 4, other.block().size * tilesize / 2f, 45 + 15); } } @@ -420,8 +453,8 @@ public class MobileInput extends InputHandler implements GestureListener{ float radius = Interpolation.swingIn.apply(crosshairScale); - Lines.poly(player.target.getX(), player.target.getY(), 4, 7f * radius, Timers.time()*1.5f); - Lines.spikes(player.target.getX(), player.target.getY(), 3f * radius, 6f * radius, 4, Timers.time()*1.5f); + Lines.poly(player.target.getX(), player.target.getY(), 4, 7f * radius, Timers.time() * 1.5f); + Lines.spikes(player.target.getX(), player.target.getY(), 3f * radius, 6f * radius, 4, Timers.time() * 1.5f); } Draw.reset(); @@ -431,9 +464,9 @@ public class MobileInput extends InputHandler implements GestureListener{ //region input events - @Override - public boolean touchDown(int screenX, int screenY, int pointer, int button){ - if(state.is(State.menu)) return false; + @Override + public boolean touchDown(int screenX, int screenY, int pointer, int button){ + if(state.is(State.menu)) return false; //get tile on cursor Tile cursor = tileAt(screenX, screenY); @@ -453,19 +486,19 @@ public class MobileInput extends InputHandler implements GestureListener{ } } - return false; - } + return false; + } @Override public boolean touchUp(int screenX, int screenY, int pointer, int button){ //place down a line if in line mode - if(lineMode) { + if(lineMode){ Tile tile = tileAt(screenX, screenY); - if (tile == null) return false; + if(tile == null) return false; - if(mode == placing && recipe != null) { + if(mode == placing && recipe != null){ //normalize area NormalizeResult result = PlaceUtils.normalizeArea(lineStartX, lineStartY, tile.x, tile.y, rotation, true, 100); @@ -473,11 +506,11 @@ public class MobileInput extends InputHandler implements GestureListener{ rotation = result.rotation; //place blocks on line - for (int i = 0; i <= result.getLength(); i += recipe.result.size) { + for(int i = 0; i <= result.getLength(); i += recipe.result.size){ int x = lineStartX + i * Mathf.sign(tile.x - lineStartX) * Mathf.bool(result.isX()); int y = lineStartY + i * Mathf.sign(tile.y - lineStartY) * Mathf.bool(!result.isX()); - if (!checkOverlapPlacement(x, y, recipe.result) && validPlace(x, y, recipe.result, result.rotation)) { + if(!checkOverlapPlacement(x, y, recipe.result) && validPlace(x, y, recipe.result, result.rotation)){ PlaceRequest request = new PlaceRequest(x * tilesize, y * tilesize, recipe, result.rotation); request.scale = 1f; selection.add(request); @@ -492,8 +525,8 @@ public class MobileInput extends InputHandler implements GestureListener{ NormalizeResult result = PlaceUtils.normalizeArea(lineStartX, lineStartY, tile.x, tile.y, rotation, false, maxLength); //break everything in area - for(int x = 0; x <= Math.abs(result.x2 - result.x); x ++ ){ - for(int y = 0; y <= Math.abs(result.y2 - result.y); y ++){ + for(int x = 0; x <= Math.abs(result.x2 - result.x); x++){ + for(int y = 0; y <= Math.abs(result.y2 - result.y); y++){ int wx = lineStartX + x * Mathf.sign(tile.x - lineStartX); int wy = lineStartY + y * Mathf.sign(tile.y - lineStartY); @@ -503,7 +536,7 @@ public class MobileInput extends InputHandler implements GestureListener{ tar = tar.target(); - if (!hasRequest(world.tile(tar.x, tar.y)) && validBreak(tar.x, tar.y)) { + if(!hasRequest(world.tile(tar.x, tar.y)) && validBreak(tar.x, tar.y)){ PlaceRequest request = new PlaceRequest(tar.worldx(), tar.worldy()); request.scale = 1f; selection.add(request); @@ -516,7 +549,7 @@ public class MobileInput extends InputHandler implements GestureListener{ }else{ Tile tile = tileAt(screenX, screenY); - if (tile == null) return false; + if(tile == null) return false; tryDropItems(tile.target(), Graphics.world(screenX, screenY).x, Graphics.world(screenX, screenY).y); } @@ -524,7 +557,7 @@ public class MobileInput extends InputHandler implements GestureListener{ } @Override - public boolean longPress(float x, float y) { + public boolean longPress(float x, float y){ if(state.is(State.menu) || mode == none) return false; //get tile on cursor @@ -545,11 +578,11 @@ public class MobileInput extends InputHandler implements GestureListener{ Effects.effect(Fx.tapBlock, cursor.worldx() + recipe.result.offset(), cursor.worldy() + recipe.result.offset(), recipe.result.size); } - return false; - } + return false; + } @Override - public boolean tap(float x, float y, int count, int button) { + public boolean tap(float x, float y, int count, int button){ if(state.is(State.menu) || lineMode) return false; float worldx = Graphics.world(x, y).x, worldy = Graphics.world(x, y).y; @@ -563,12 +596,12 @@ public class MobileInput extends InputHandler implements GestureListener{ if(cursor == null || ui.hasMouse(x, y)) return false; //remove if request present - if(hasRequest(cursor)) { + if(hasRequest(cursor)){ removeRequest(getRequest(cursor)); }else if(mode == placing && isPlacing() && validPlace(cursor.x, cursor.y, recipe.result, rotation) && !checkOverlapPlacement(cursor.x, cursor.y, recipe.result)){ //add to selection queue if it's a valid place position selection.add(lastPlaced = new PlaceRequest(cursor.worldx(), cursor.worldy(), recipe, rotation)); - }else if(mode == breaking && validBreak(cursor.target().x, cursor.target().y) && !hasRequest(cursor.target())) { + }else if(mode == breaking && validBreak(cursor.target().x, cursor.target().y) && !hasRequest(cursor.target())){ //add to selection queue if it's a valid BREAK position cursor = cursor.target(); selection.add(new PlaceRequest(cursor.worldx(), cursor.worldy())); @@ -588,24 +621,24 @@ public class MobileInput extends InputHandler implements GestureListener{ return false; } - @Override - public void update(){ + @Override + public void update(){ //reset state when not placing - if(mode == none){ - selecting = false; - lineMode = false; - removals.addAll(selection); - selection.clear(); + if(mode == none){ + selecting = false; + lineMode = false; + removals.addAll(selection); + selection.clear(); } if(lineMode && mode == placing && recipe == null){ - lineMode = false; + lineMode = false; } //if there is no mode and there's a recipe, switch to placing if(recipe != null && mode == none){ - mode = placing; + mode = placing; } if(recipe != null){ @@ -615,13 +648,13 @@ public class MobileInput extends InputHandler implements GestureListener{ //automatically switch to placing after a new recipe is selected if(lastRecipe != recipe && mode == breaking && recipe != null){ mode = placing; - lastRecipe = recipe; + lastRecipe = recipe; } if(lineMode){ - lineScale = Mathf.lerpDelta(lineScale, 1f, 0.1f); + lineScale = Mathf.lerpDelta(lineScale, 1f, 0.1f); - //When in line mode, pan when near screen edges automatically + //When in line mode, pan when near screen edges automatically if(Gdx.input.isTouched(0) && lineMode){ float screenX = Graphics.mouse().x, screenY = Graphics.mouse().y; @@ -651,19 +684,19 @@ public class MobileInput extends InputHandler implements GestureListener{ Core.camera.position.y += vector.y; } }else{ - lineScale = 0f; + lineScale = 0f; } //remove place requests that have disappeared - for(int i = removals.size - 1; i >= 0; i --){ + for(int i = removals.size - 1; i >= 0; i--){ PlaceRequest request = removals.get(i); if(request.scale <= 0.0001f){ removals.removeIndex(i); - i --; + i--; } } - } + } @Override public boolean pan(float x, float y, float deltaX, float deltaY){ @@ -676,14 +709,14 @@ public class MobileInput extends InputHandler implements GestureListener{ float dx = deltaX * Core.camera.zoom / Core.cameraScale, dy = deltaY * Core.camera.zoom / Core.cameraScale; - if(selecting){ //pan all requests + if(selecting){ //pan all requests for(PlaceRequest req : selection){ if(req.remove) continue; //don't shift removal requests req.x += dx; req.y -= dy; } }else{ - //pan player + //pan player Core.camera.position.x -= dx; Core.camera.position.y += dy; } @@ -692,12 +725,12 @@ public class MobileInput extends InputHandler implements GestureListener{ } @Override - public boolean panStop(float x, float y, int pointer, int button) { + public boolean panStop(float x, float y, int pointer, int button){ return false; } @Override - public boolean pinch (Vector2 initialPointer1, Vector2 initialPointer2, Vector2 pointer1, Vector2 pointer2) { + public boolean pinch(Vector2 initialPointer1, Vector2 initialPointer2, Vector2 pointer1, Vector2 pointer2){ if(pinch1.x < 0){ pinch1.set(initialPointer1); pinch2.set(initialPointer2); @@ -727,28 +760,35 @@ public class MobileInput extends InputHandler implements GestureListener{ } @Override - public void pinchStop () { + public void pinchStop(){ initzoom = -1; pinch2.set(pinch1.set(-1, -1)); zoomed = false; } - @Override public boolean touchDown(float x, float y, int pointer, int button) { return false; } - @Override public boolean fling(float velocityX, float velocityY, int button) { return false; } + @Override + public boolean touchDown(float x, float y, int pointer, int button){ + return false; + } + + @Override + public boolean fling(float velocityX, float velocityY, int button){ + return false; + } //endregion class PlaceRequest{ - float x, y; - Recipe recipe; - int rotation; - boolean remove; + float x, y; + Recipe recipe; + int rotation; + boolean remove; - //animation variables - float scale; - float redness; + //animation variables + float scale; + float redness; - PlaceRequest(float x, float y, Recipe recipe, int rotation) { + PlaceRequest(float x, float y, Recipe recipe, int rotation){ this.x = x; this.y = y; this.recipe = recipe; @@ -756,14 +796,14 @@ public class MobileInput extends InputHandler implements GestureListener{ this.remove = false; } - PlaceRequest(float x, float y) { + PlaceRequest(float x, float y){ this.x = x; this.y = y; this.remove = true; } Tile tile(){ - return world.tileWorld(x - (recipe == null ? 0 : recipe.result.offset()), y - (recipe == null ? 0 : recipe.result.offset())); + return world.tileWorld(x - (recipe == null ? 0 : recipe.result.offset()), y - (recipe == null ? 0 : recipe.result.offset())); } } } diff --git a/core/src/io/anuke/mindustry/input/PlaceUtils.java b/core/src/io/anuke/mindustry/input/PlaceUtils.java index 2d514f9506..b74b5b31a8 100644 --- a/core/src/io/anuke/mindustry/input/PlaceUtils.java +++ b/core/src/io/anuke/mindustry/input/PlaceUtils.java @@ -5,11 +5,12 @@ import io.anuke.ucore.util.Mathf; import static io.anuke.mindustry.Vars.tilesize; -public class PlaceUtils { +public class PlaceUtils{ private static final NormalizeResult result = new NormalizeResult(); private static final NormalizeDrawResult drawResult = new NormalizeDrawResult(); - /**Normalizes a placement area and returns the result, ready to be used for drawing a rectangle. + /** + * Normalizes a placement area and returns the result, ready to be used for drawing a rectangle. * Returned x2 and y2 will always be greater than x and y. * * @param block block that will be drawn @@ -30,12 +31,12 @@ public class PlaceUtils { drawResult.x2 = result.x2 * tilesize; drawResult.y2 = result.y2 * tilesize; - drawResult.x -= block.size * scaling * tilesize/2; - drawResult.x2 += block.size * scaling * tilesize/2; + drawResult.x -= block.size * scaling * tilesize / 2; + drawResult.x2 += block.size * scaling * tilesize / 2; - drawResult.y -= block.size * scaling * tilesize/2; - drawResult.y2 += block.size * scaling * tilesize/2; + drawResult.y -= block.size * scaling * tilesize / 2; + drawResult.y2 += block.size * scaling * tilesize / 2; drawResult.x += offset; drawResult.y += offset; @@ -45,7 +46,8 @@ public class PlaceUtils { return drawResult; } - /**Normalizes a placement area and returns the result. + /** + * Normalizes a placement area and returns the result. * Returned x2 and y2 will always be greater than x and y. * * @param tilex starting X coordinate @@ -58,18 +60,18 @@ public class PlaceUtils { */ public static NormalizeResult normalizeArea(int tilex, int tiley, int endx, int endy, int rotation, boolean snap, int maxLength){ - if(snap) { - if (Math.abs(tilex - endx) > Math.abs(tiley - endy)) { + if(snap){ + if(Math.abs(tilex - endx) > Math.abs(tiley - endy)){ endy = tiley; - } else { + }else{ endx = tilex; } - if (Math.abs(endx - tilex) > maxLength) { + if(Math.abs(endx - tilex) > maxLength){ endx = Mathf.sign(endx - tilex) * maxLength + tilex; } - if (Math.abs(endy - tiley) > maxLength) { + if(Math.abs(endy - tiley) > maxLength){ endy = Mathf.sign(endy - tiley) * maxLength + tiley; } } @@ -110,7 +112,7 @@ public class PlaceUtils { return result; } - static class NormalizeDrawResult { + static class NormalizeDrawResult{ float x, y, x2, y2; } @@ -121,17 +123,23 @@ public class PlaceUtils { return Math.abs(x2 - x) > Math.abs(y2 - y); } - /**Returns length of greater edge of the selection.*/ + /** + * Returns length of greater edge of the selection. + */ int getLength(){ return Math.max(x2 - x, y2 - y); } - /**Returns the X position of a specific index along this area as a line.*/ + /** + * Returns the X position of a specific index along this area as a line. + */ int getScaledX(int i){ return x + (x2 - x > y2 - y ? i : 0); } - /**Returns the Y position of a specific index along this area as a line.*/ + /** + * Returns the Y position of a specific index along this area as a line. + */ int getScaledY(int i){ return y + (x2 - x > y2 - y ? 0 : i); } diff --git a/core/src/io/anuke/mindustry/io/BundleLoader.java b/core/src/io/anuke/mindustry/io/BundleLoader.java index aa786fd9d2..bc97c089e5 100644 --- a/core/src/io/anuke/mindustry/io/BundleLoader.java +++ b/core/src/io/anuke/mindustry/io/BundleLoader.java @@ -13,7 +13,7 @@ import java.util.Locale; import static io.anuke.mindustry.Vars.headless; -public class BundleLoader { +public class BundleLoader{ public static void load(){ Settings.defaults("locale", "default"); @@ -27,10 +27,10 @@ public class BundleLoader { return Locale.getDefault(); }else{ Locale lastLocale; - if (loc.contains("_")) { + if(loc.contains("_")){ String[] split = loc.split("_"); lastLocale = new Locale(split[0], split[1]); - } else { + }else{ lastLocale = new Locale(loc); } @@ -40,7 +40,7 @@ public class BundleLoader { private static void loadBundle(){ I18NBundle.setExceptionOnMissingKey(false); - try { + try{ //try loading external bundle FileHandle handle = Gdx.files.local("bundle"); @@ -51,7 +51,7 @@ public class BundleLoader { if(!headless){ Timers.run(10f, () -> Vars.ui.showInfo("Note: You have successfully loaded an external translation bundle.")); } - }catch (Throwable e){ + }catch(Throwable e){ //no external bundle found FileHandle handle = Gdx.files.internal("bundles/bundle"); diff --git a/core/src/io/anuke/mindustry/io/Changelogs.java b/core/src/io/anuke/mindustry/io/Changelogs.java index c799647f1c..85d482ad00 100644 --- a/core/src/io/anuke/mindustry/io/Changelogs.java +++ b/core/src/io/anuke/mindustry/io/Changelogs.java @@ -8,7 +8,7 @@ import io.anuke.ucore.function.Consumer; import static io.anuke.mindustry.Vars.releasesURL; -public class Changelogs { +public class Changelogs{ public static void getChangelog(Consumer> success, Consumer fail){ Net.http(releasesURL, "GET", result -> { @@ -30,7 +30,7 @@ public class Changelogs { public final String name, description; public final int id, build; - public VersionInfo(String name, String description, int id, int build) { + public VersionInfo(String name, String description, int id, int build){ this.name = name; this.description = description; this.id = id; @@ -38,7 +38,7 @@ public class Changelogs { } @Override - public String toString() { + public String toString(){ return "VersionInfo{" + "name='" + name + '\'' + ", description='" + description + '\'' + diff --git a/core/src/io/anuke/mindustry/io/Map.java b/core/src/io/anuke/mindustry/io/Map.java index 1963995a4c..1959862c79 100644 --- a/core/src/io/anuke/mindustry/io/Map.java +++ b/core/src/io/anuke/mindustry/io/Map.java @@ -6,16 +6,26 @@ import io.anuke.ucore.function.Supplier; import java.io.InputStream; -public class Map { - /**Internal map name. This is the filename, without any extensions.*/ +public class Map{ + /** + * Internal map name. This is the filename, without any extensions. + */ public final String name; - /**Whether this is a custom map.*/ + /** + * Whether this is a custom map. + */ public final boolean custom; - /**Metadata. Author description, display name, etc.*/ + /** + * Metadata. Author description, display name, etc. + */ public final MapMeta meta; - /**Supplies a new input stream with the data of this map.*/ + /** + * Supplies a new input stream with the data of this map. + */ public final Supplier stream; - /**Preview texture.*/ + /** + * Preview texture. + */ public Texture texture; public Map(String name, MapMeta meta, boolean custom, Supplier streamSupplier){ diff --git a/core/src/io/anuke/mindustry/io/MapIO.java b/core/src/io/anuke/mindustry/io/MapIO.java index 4ff93932b9..5b10c9d7ed 100644 --- a/core/src/io/anuke/mindustry/io/MapIO.java +++ b/core/src/io/anuke/mindustry/io/MapIO.java @@ -18,8 +18,10 @@ import java.io.DataOutputStream; import java.io.IOException; import java.io.OutputStream; -/**Reads and writes map files.*/ -public class MapIO { +/** + * Reads and writes map files. + */ +public class MapIO{ private static final int version = 0; private static IntIntMap defaultBlockMap = new IntIntMap(); @@ -36,8 +38,8 @@ public class MapIO { TileDataMarker marker = data.newDataMarker(); Color color = new Color(); - for(int y = 0; y < data.height(); y ++){ - for(int x = 0; x < data.width(); x ++){ + for(int y = 0; y < data.height(); y++){ + for(int x = 0; x < data.width(); x++){ data.read(marker); Block floor = Block.getByID(marker.floor); Block wall = Block.getByID(marker.wall); @@ -45,7 +47,7 @@ public class MapIO { if(wallc == 0 && (wall.update || wall.solid || wall.breakable)) wallc = Team.all[marker.team].intColor; wallc = wallc == 0 ? ColorMapper.getBlockColor(floor) : wallc; if(marker.elevation > 0){ - float scaling = 1f + marker.elevation/8f; + float scaling = 1f + marker.elevation / 8f; color.set(wallc); color.mul(scaling, scaling, scaling, 1f); wallc = Color.rgba8888(color); @@ -62,17 +64,17 @@ public class MapIO { public static MapTileData readPixmap(Pixmap pixmap){ MapTileData data = new MapTileData(pixmap.getWidth(), pixmap.getHeight()); - for(int x = 0; x < data.width(); x ++){ - for(int y = 0; y < data.height(); y ++){ + for(int x = 0; x < data.width(); x++){ + for(int y = 0; y < data.height(); y++){ Block block = ColorMapper.getByColor(pixmap.getPixel(y, pixmap.getWidth() - 1 - x)); if(block == null){ - data.write(x, y, DataPosition.floor, (byte)Blocks.stone.id); + data.write(x, y, DataPosition.floor, (byte) Blocks.stone.id); }else{ - data.write(x, y, DataPosition.floor, (byte)block.id); + data.write(x, y, DataPosition.floor, (byte) block.id); } - data.write(x, y, DataPosition.wall, (byte)Blocks.air.id); + data.write(x, y, DataPosition.wall, (byte) Blocks.air.id); } } @@ -94,25 +96,31 @@ public class MapIO { ds.close(); } - /**Reads tile data, skipping meta.*/ - public static MapTileData readTileData(DataInputStream stream, boolean readOnly) throws IOException { + /** + * Reads tile data, skipping meta. + */ + public static MapTileData readTileData(DataInputStream stream, boolean readOnly) throws IOException{ MapMeta meta = readMapMeta(stream); return readTileData(stream, meta, readOnly); } - /**Does not skip meta. Call after reading meta.*/ - public static MapTileData readTileData(DataInputStream stream, MapMeta meta, boolean readOnly) throws IOException { + /** + * Does not skip meta. Call after reading meta. + */ + public static MapTileData readTileData(DataInputStream stream, MapMeta meta, boolean readOnly) throws IOException{ byte[] bytes = new byte[stream.available()]; stream.readFully(bytes); return new MapTileData(bytes, meta.width, meta.height, meta.blockMap, readOnly); } - /**Reads tile data, skipping meta tags.*/ + /** + * Reads tile data, skipping meta tags. + */ public static MapTileData readTileData(Map map, boolean readOnly){ - try (DataInputStream ds = new DataInputStream(map.stream.get())){ + try(DataInputStream ds = new DataInputStream(map.stream.get())){ return MapIO.readTileData(ds, readOnly); - }catch (IOException e){ + }catch(IOException e){ throw new RuntimeException(e); } } @@ -125,14 +133,14 @@ public class MapIO { byte tagAmount = stream.readByte(); - for(int i = 0; i < tagAmount; i ++){ + for(int i = 0; i < tagAmount; i++){ String name = stream.readUTF(); String value = stream.readUTF(); tags.put(name, value); } short blocks = stream.readShort(); - for(int i = 0; i < blocks; i ++){ + for(int i = 0; i < blocks; i++){ short id = stream.readShort(); String name = stream.readUTF(); Block block = Block.getByName(name); @@ -151,7 +159,7 @@ public class MapIO { public static void writeMapMeta(DataOutputStream stream, MapMeta meta) throws IOException{ stream.writeInt(meta.version); - stream.writeByte((byte)meta.tags.size); + stream.writeByte((byte) meta.tags.size); for(Entry entry : meta.tags.entries()){ stream.writeUTF(entry.key); diff --git a/core/src/io/anuke/mindustry/io/MapMeta.java b/core/src/io/anuke/mindustry/io/MapMeta.java index 7668a76933..ab554148c5 100644 --- a/core/src/io/anuke/mindustry/io/MapMeta.java +++ b/core/src/io/anuke/mindustry/io/MapMeta.java @@ -3,13 +3,13 @@ package io.anuke.mindustry.io; import com.badlogic.gdx.utils.IntIntMap; import com.badlogic.gdx.utils.ObjectMap; -public class MapMeta { +public class MapMeta{ public final int version; public final ObjectMap tags; public final int width, height; public final IntIntMap blockMap; - public MapMeta(int version, ObjectMap tags, int width, int height, IntIntMap blockMap) { + public MapMeta(int version, ObjectMap tags, int width, int height, IntIntMap blockMap){ this.version = version; this.tags = tags; this.width = width; diff --git a/core/src/io/anuke/mindustry/io/MapTileData.java b/core/src/io/anuke/mindustry/io/MapTileData.java index b3fb92e4ab..1a2d307d40 100644 --- a/core/src/io/anuke/mindustry/io/MapTileData.java +++ b/core/src/io/anuke/mindustry/io/MapTileData.java @@ -5,13 +5,15 @@ import io.anuke.ucore.util.Bits; import java.nio.ByteBuffer; -public class MapTileData { - /**Tile size: 4 bytes.
+public class MapTileData{ + /** + * Tile size: 4 bytes.
* 0: ground tile
* 1: wall tile
* 2: rotation + team
* 3: link (x/y)
- * 4: elevation
*/ + * 4: elevation
+ */ private final static int TILE_SIZE = 5; private final ByteBuffer buffer; @@ -36,7 +38,7 @@ public class MapTileData { this.readOnly = readOnly; if(mapping != null && !readOnly){ - for(int i = 0; i < width * height; i ++){ + for(int i = 0; i < width * height; i++){ TileDataMarker marker = new TileDataMarker(); read(marker); buffer.position(i * TILE_SIZE); @@ -59,28 +61,38 @@ public class MapTileData { return height; } - /**Write a byte to a specific position.*/ + /** + * Write a byte to a specific position. + */ public void write(int x, int y, DataPosition position, byte data){ buffer.put((x + width * y) * TILE_SIZE + position.ordinal(), data); } - /**Gets a byte at a specific position.*/ + /** + * Gets a byte at a specific position. + */ public byte read(int x, int y, DataPosition position){ return buffer.get((x + width * y) * TILE_SIZE + position.ordinal()); } - /**Reads and returns the next tile data.*/ + /** + * Reads and returns the next tile data. + */ public TileDataMarker read(TileDataMarker marker){ marker.read(buffer); return marker; } - /**Writes this tile data marker.*/ + /** + * Writes this tile data marker. + */ public void write(TileDataMarker marker){ marker.write(buffer); } - /**Sets read position to the specified coordinates*/ + /** + * Sets read position to the specified coordinates + */ public void position(int x, int y){ buffer.position((x + width * y) * TILE_SIZE); } @@ -93,7 +105,7 @@ public class MapTileData { floor, wall, link, rotationTeam, elevation } - public class TileDataMarker { + public class TileDataMarker{ public byte floor, wall; public byte link; public byte rotation; @@ -110,8 +122,8 @@ public class MapTileData { team = Bits.getRightByte(rt); if(map != null){ - floor = (byte)map.get(floor, floor); - wall = (byte)map.get(wall, wall); + floor = (byte) map.get(floor, floor); + wall = (byte) map.get(wall, wall); } } diff --git a/core/src/io/anuke/mindustry/io/Maps.java b/core/src/io/anuke/mindustry/io/Maps.java index 5ea9e0bf38..92ccdbd779 100644 --- a/core/src/io/anuke/mindustry/io/Maps.java +++ b/core/src/io/anuke/mindustry/io/Maps.java @@ -17,145 +17,171 @@ import java.io.*; import static io.anuke.mindustry.Vars.*; public class Maps implements Disposable{ - /**List of all built-in maps.*/ - private static final String[] defaultMapNames = {}; - /**Tile format version.*/ - private static final int version = 0; + /** + * List of all built-in maps. + */ + private static final String[] defaultMapNames = {}; + /** + * Tile format version. + */ + private static final int version = 0; - /**Maps map names to the real maps.*/ - private ObjectMap maps = new ObjectMap<>(); - /**All maps stored in an ordered array.*/ - private Array allMaps = new ThreadArray<>(); - /**Temporary array used for returning things.*/ - private Array returnArray = new ThreadArray<>(); - /**Used for storing a list of custom map names for GWT.*/ - private Array customMapNames; + /** + * Maps map names to the real maps. + */ + private ObjectMap maps = new ObjectMap<>(); + /** + * All maps stored in an ordered array. + */ + private Array allMaps = new ThreadArray<>(); + /** + * Temporary array used for returning things. + */ + private Array returnArray = new ThreadArray<>(); + /** + * Used for storing a list of custom map names for GWT. + */ + private Array customMapNames; - public Maps(){ + public Maps(){ } - /**Returns a list of all maps, including custom ones.*/ - public Array all(){ - return allMaps; - } + /** + * Returns a list of all maps, including custom ones. + */ + public Array all(){ + return allMaps; + } - /**Returns a list of only custom maps.*/ - public Array customMaps(){ - returnArray.clear(); - for(Map map : allMaps){ - if(map.custom) returnArray.add(map); - } - return returnArray; - } + /** + * Returns a list of only custom maps. + */ + public Array customMaps(){ + returnArray.clear(); + for(Map map : allMaps){ + if(map.custom) returnArray.add(map); + } + return returnArray; + } - /**Returns a list of only default maps.*/ - public Array defaultMaps(){ - returnArray.clear(); - for(Map map : allMaps){ - if(!map.custom) returnArray.add(map); - } - return returnArray; - } + /** + * Returns a list of only default maps. + */ + public Array defaultMaps(){ + returnArray.clear(); + for(Map map : allMaps){ + if(!map.custom) returnArray.add(map); + } + return returnArray; + } - /**Returns map by internal name.*/ - public Map getByName(String name){ - return maps.get(name); - } + /** + * Returns map by internal name. + */ + public Map getByName(String name){ + return maps.get(name); + } - /**Load all maps. Should be called at application start.*/ - public void load(){ - try { - for (String name : defaultMapNames) { - FileHandle file = Gdx.files.internal("maps/" + name + "." + mapExtension); - loadMap(file.nameWithoutExtension(), file::read, false); - } - }catch (IOException e){ - throw new RuntimeException(e); - } + /** + * Load all maps. Should be called at application start. + */ + public void load(){ + try{ + for(String name : defaultMapNames){ + FileHandle file = Gdx.files.internal("maps/" + name + "." + mapExtension); + loadMap(file.nameWithoutExtension(), file::read, false); + } + }catch(IOException e){ + throw new RuntimeException(e); + } - loadCustomMaps(); - } + loadCustomMaps(); + } - /**Save a map. This updates all values and stored data necessary.*/ - public void saveMap(String name, MapTileData data, ObjectMap tags){ - try { - if (!gwt) { - FileHandle file = customMapDirectory.child(name + "." + mapExtension); - MapIO.writeMap(file.write(false), tags, data); - } else { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - MapIO.writeMap(stream, tags, data); - Settings.putString("map-data-" + name, new String(Base64Coder.encode(stream.toByteArray()))); - if(!customMapNames.contains(name, false)){ - customMapNames.add(name); - Settings.putJson("custom-maps", customMapNames); - } - Settings.save(); - } + /** + * Save a map. This updates all values and stored data necessary. + */ + public void saveMap(String name, MapTileData data, ObjectMap tags){ + try{ + if(!gwt){ + FileHandle file = customMapDirectory.child(name + "." + mapExtension); + MapIO.writeMap(file.write(false), tags, data); + }else{ + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + MapIO.writeMap(stream, tags, data); + Settings.putString("map-data-" + name, new String(Base64Coder.encode(stream.toByteArray()))); + if(!customMapNames.contains(name, false)){ + customMapNames.add(name); + Settings.putJson("custom-maps", customMapNames); + } + Settings.save(); + } - if(maps.containsKey(name)){ - if(maps.get(name).texture != null) { - maps.get(name).texture.dispose(); - maps.get(name).texture = null; - } - allMaps.removeValue(maps.get(name), true); - } + if(maps.containsKey(name)){ + if(maps.get(name).texture != null){ + maps.get(name).texture.dispose(); + maps.get(name).texture = null; + } + allMaps.removeValue(maps.get(name), true); + } - Map map = new Map(name, new MapMeta(version, tags, data.width(), data.height(), null), true, getStreamFor(name)); - if (!headless){ - map.texture = new Texture(MapIO.generatePixmap(data)); - } - allMaps.add(map); + Map map = new Map(name, new MapMeta(version, tags, data.width(), data.height(), null), true, getStreamFor(name)); + if(!headless){ + map.texture = new Texture(MapIO.generatePixmap(data)); + } + allMaps.add(map); - maps.put(name, map); - }catch (IOException e){ - throw new RuntimeException(e); - } - } + maps.put(name, map); + }catch(IOException e){ + throw new RuntimeException(e); + } + } - /**Removes a map completely.*/ - public void removeMap(Map map){ - if(map.texture != null){ - map.texture.dispose(); - map.texture = null; - } + /** + * Removes a map completely. + */ + public void removeMap(Map map){ + if(map.texture != null){ + map.texture.dispose(); + map.texture = null; + } - maps.remove(map.name); - allMaps.removeValue(map, true); + maps.remove(map.name); + allMaps.removeValue(map, true); - if (!gwt) { - customMapDirectory.child(map.name + "." + mapExtension).delete(); - } else { - customMapNames.removeValue(map.name, false); - Settings.putString("map-data-" + map.name, ""); - Settings.putJson("custom-maps", customMapNames); - Settings.save(); - } - } + if(!gwt){ + customMapDirectory.child(map.name + "." + mapExtension).delete(); + }else{ + customMapNames.removeValue(map.name, false); + Settings.putString("map-data-" + map.name, ""); + Settings.putJson("custom-maps", customMapNames); + Settings.save(); + } + } - private void loadMap(String name, Supplier supplier, boolean custom) throws IOException{ - try(DataInputStream ds = new DataInputStream(supplier.get())) { + private void loadMap(String name, Supplier supplier, boolean custom) throws IOException{ + try(DataInputStream ds = new DataInputStream(supplier.get())){ MapMeta meta = MapIO.readMapMeta(ds); Map map = new Map(name, meta, custom, supplier); - if (!headless){ - map.texture = new Texture(MapIO.generatePixmap(MapIO.readTileData(ds, meta, true))); - } + if(!headless){ + map.texture = new Texture(MapIO.generatePixmap(MapIO.readTileData(ds, meta, true))); + } maps.put(map.name, map); allMaps.add(map); } - } + } - private void loadCustomMaps(){ - if(!gwt){ + private void loadCustomMaps(){ + if(!gwt){ for(FileHandle file : customMapDirectory.list()){ try{ if(file.extension().equalsIgnoreCase(mapExtension)){ loadMap(file.nameWithoutExtension(), file::read, true); } - }catch (Exception e){ + }catch(Exception e){ Log.err("Failed to load custom map file '{0}'!", file); Log.err(e); } @@ -169,7 +195,7 @@ public class Maps implements Disposable{ String data = Settings.getString("map-data-" + name, ""); byte[] bytes = Base64Coder.decode(data); loadMap(name, () -> new ByteArrayInputStream(bytes), true); - }catch (Exception e){ + }catch(Exception e){ Log.err("Failed to load custom map '{0}'!", name); Log.err(e); } @@ -177,19 +203,21 @@ public class Maps implements Disposable{ } } - /**Returns an input stream supplier for a given map name.*/ + /** + * Returns an input stream supplier for a given map name. + */ private Supplier getStreamFor(String name){ - if(!gwt){ - return customMapDirectory.child(name + "." + mapExtension)::read; - }else{ - String data = Settings.getString("map-data-" + name, ""); - byte[] bytes = Base64Coder.decode(data); - return () -> new ByteArrayInputStream(bytes); - } - } + if(!gwt){ + return customMapDirectory.child(name + "." + mapExtension)::read; + }else{ + String data = Settings.getString("map-data-" + name, ""); + byte[] bytes = Base64Coder.decode(data); + return () -> new ByteArrayInputStream(bytes); + } + } - @Override - public void dispose() { + @Override + public void dispose(){ - } + } } diff --git a/core/src/io/anuke/mindustry/io/SaveFileVersion.java b/core/src/io/anuke/mindustry/io/SaveFileVersion.java index 1a0d94bde9..48ec0b58e8 100644 --- a/core/src/io/anuke/mindustry/io/SaveFileVersion.java +++ b/core/src/io/anuke/mindustry/io/SaveFileVersion.java @@ -6,7 +6,7 @@ import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -public abstract class SaveFileVersion { +public abstract class SaveFileVersion{ public final int version; public SaveFileVersion(int version){ @@ -23,5 +23,6 @@ public abstract class SaveFileVersion { } public abstract void read(DataInputStream stream) throws IOException; + public abstract void write(DataOutputStream stream) throws IOException; } diff --git a/core/src/io/anuke/mindustry/io/SaveIO.java b/core/src/io/anuke/mindustry/io/SaveIO.java index 3f5397ce45..dfd21820c5 100644 --- a/core/src/io/anuke/mindustry/io/SaveIO.java +++ b/core/src/io/anuke/mindustry/io/SaveIO.java @@ -15,148 +15,148 @@ import java.util.zip.InflaterInputStream; import static io.anuke.mindustry.Vars.*; public class SaveIO{ - public static final IntMap versions = new IntMap<>(); - public static final Array versionArray = Array.with( - new Save16() - ); + public static final IntMap versions = new IntMap<>(); + public static final Array versionArray = Array.with( + new Save16() + ); - static{ - for(SaveFileVersion version : versionArray){ - versions.put(version.version, version); - } - } + static{ + for(SaveFileVersion version : versionArray){ + versions.put(version.version, version); + } + } - public static void saveToSlot(int slot){ - if(gwt){ - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - write(stream); - Settings.putString("save-"+slot+"-data", new String(Base64Coder.encode(stream.toByteArray()))); - Settings.save(); - }else{ - FileHandle file = fileFor(slot); - boolean exists = file.exists(); - if(exists) file.moveTo(file.sibling(file.name() + "-backup." + file.extension())); - try { - write(fileFor(slot)); - }catch (Exception e){ - if(exists) file.sibling(file.name() + "-backup." + file.extension()).moveTo(file); - throw new RuntimeException(e); - } - } - } - - public static void loadFromSlot(int slot){ - if(gwt){ - String string = Settings.getString("save-"+slot+"-data", ""); - ByteArrayInputStream stream = new ByteArrayInputStream(Base64Coder.decode(string)); - load(stream); - }else{ - load(fileFor(slot)); - } - } + public static void saveToSlot(int slot){ + if(gwt){ + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + write(stream); + Settings.putString("save-" + slot + "-data", new String(Base64Coder.encode(stream.toByteArray()))); + Settings.save(); + }else{ + FileHandle file = fileFor(slot); + boolean exists = file.exists(); + if(exists) file.moveTo(file.sibling(file.name() + "-backup." + file.extension())); + try{ + write(fileFor(slot)); + }catch(Exception e){ + if(exists) file.sibling(file.name() + "-backup." + file.extension()).moveTo(file); + throw new RuntimeException(e); + } + } + } - public static DataInputStream getSlotStream(int slot){ - if(gwt){ - String string = Settings.getString("save-"+slot+"-data", ""); - byte[] bytes = Base64Coder.decode(string); - return new DataInputStream(new ByteArrayInputStream(bytes)); - }else{ - return new DataInputStream(new InflaterInputStream(fileFor(slot).read())); - } - } - - public static boolean isSaveValid(int slot){ - try { - return isSaveValid(getSlotStream(slot)); - }catch (Exception e){ - return false; - } - } + public static void loadFromSlot(int slot){ + if(gwt){ + String string = Settings.getString("save-" + slot + "-data", ""); + ByteArrayInputStream stream = new ByteArrayInputStream(Base64Coder.decode(string)); + load(stream); + }else{ + load(fileFor(slot)); + } + } - public static boolean isSaveValid(FileHandle file){ - return isSaveValid(new DataInputStream(new InflaterInputStream(file.read()))); - } + public static DataInputStream getSlotStream(int slot){ + if(gwt){ + String string = Settings.getString("save-" + slot + "-data", ""); + byte[] bytes = Base64Coder.decode(string); + return new DataInputStream(new ByteArrayInputStream(bytes)); + }else{ + return new DataInputStream(new InflaterInputStream(fileFor(slot).read())); + } + } - public static boolean isSaveValid(DataInputStream stream){ + public static boolean isSaveValid(int slot){ + try{ + return isSaveValid(getSlotStream(slot)); + }catch(Exception e){ + return false; + } + } - try{ - int version = stream.readInt(); - SaveFileVersion ver = versions.get(version); - ver.getData(stream); - return true; - }catch (Exception e){ - e.printStackTrace(); - return false; - } - } + public static boolean isSaveValid(FileHandle file){ + return isSaveValid(new DataInputStream(new InflaterInputStream(file.read()))); + } - public static SaveMeta getData(int slot){ - return getData(getSlotStream(slot)); - } - - public static SaveMeta getData(DataInputStream stream){ - - try{ - int version = stream.readInt(); - SaveMeta meta = versions.get(version).getData(stream); - stream.close(); - return meta; - }catch (IOException e){ - throw new RuntimeException(e); - } - } - - public static FileHandle fileFor(int slot){ - return saveDirectory.child(slot + "." + Vars.saveExtension); - } + public static boolean isSaveValid(DataInputStream stream){ - public static void write(FileHandle file){ - write(file.write(false)); - } + try{ + int version = stream.readInt(); + SaveFileVersion ver = versions.get(version); + ver.getData(stream); + return true; + }catch(Exception e){ + e.printStackTrace(); + return false; + } + } - public static void write(OutputStream os){ - DataOutputStream stream; - - try{ - stream = new DataOutputStream(new DeflaterOutputStream(os)); - getVersion().write(stream); - stream.close(); - }catch (Exception e){ - throw new RuntimeException(e); - } - } + public static SaveMeta getData(int slot){ + return getData(getSlotStream(slot)); + } - public static void load(FileHandle file){ - try { - load(new InflaterInputStream(file.read())); - }catch (RuntimeException e){ - e.printStackTrace(); - FileHandle backup = file.sibling(file.name() + "-backup." + file.extension()); - if(backup.exists()){ - load(new InflaterInputStream(backup.read())); - } - } - } + public static SaveMeta getData(DataInputStream stream){ - public static void load(InputStream is){ - logic.reset(); + try{ + int version = stream.readInt(); + SaveMeta meta = versions.get(version).getData(stream); + stream.close(); + return meta; + }catch(IOException e){ + throw new RuntimeException(e); + } + } - DataInputStream stream; - - try{ - stream = new DataInputStream(is); - int version = stream.readInt(); - SaveFileVersion ver = versions.get(version); + public static FileHandle fileFor(int slot){ + return saveDirectory.child(slot + "." + Vars.saveExtension); + } - ver.read(stream); + public static void write(FileHandle file){ + write(file.write(false)); + } - stream.close(); - }catch (Exception e){ - throw new RuntimeException(e); - } - } + public static void write(OutputStream os){ + DataOutputStream stream; - public static SaveFileVersion getVersion(){ - return versionArray.peek(); - } + try{ + stream = new DataOutputStream(new DeflaterOutputStream(os)); + getVersion().write(stream); + stream.close(); + }catch(Exception e){ + throw new RuntimeException(e); + } + } + + public static void load(FileHandle file){ + try{ + load(new InflaterInputStream(file.read())); + }catch(RuntimeException e){ + e.printStackTrace(); + FileHandle backup = file.sibling(file.name() + "-backup." + file.extension()); + if(backup.exists()){ + load(new InflaterInputStream(backup.read())); + } + } + } + + public static void load(InputStream is){ + logic.reset(); + + DataInputStream stream; + + try{ + stream = new DataInputStream(is); + int version = stream.readInt(); + SaveFileVersion ver = versions.get(version); + + ver.read(stream); + + stream.close(); + }catch(Exception e){ + throw new RuntimeException(e); + } + } + + public static SaveFileVersion getVersion(){ + return versionArray.peek(); + } } diff --git a/core/src/io/anuke/mindustry/io/SaveMeta.java b/core/src/io/anuke/mindustry/io/SaveMeta.java index f241f8870e..5fd655f6b2 100644 --- a/core/src/io/anuke/mindustry/io/SaveMeta.java +++ b/core/src/io/anuke/mindustry/io/SaveMeta.java @@ -8,7 +8,7 @@ import java.util.Date; import static io.anuke.mindustry.Vars.world; -public class SaveMeta { +public class SaveMeta{ public int version; public String date; public GameMode mode; diff --git a/core/src/io/anuke/mindustry/io/Saves.java b/core/src/io/anuke/mindustry/io/Saves.java index 2e09aa311d..79b5756936 100644 --- a/core/src/io/anuke/mindustry/io/Saves.java +++ b/core/src/io/anuke/mindustry/io/Saves.java @@ -13,7 +13,7 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.*; -public class Saves { +public class Saves{ private int nextSlot; private Array saves = new ThreadArray<>(); private SaveSlot current; @@ -22,7 +22,7 @@ public class Saves { public void load(){ saves.clear(); - for(int i = 0; i < saveSlots; i ++){ + for(int i = 0; i < saveSlots; i++){ if(SaveIO.isSaveValid(i)){ SaveSlot slot = new SaveSlot(i); saves.add(slot); @@ -32,7 +32,7 @@ public class Saves { } } - public SaveSlot getCurrent() { + public SaveSlot getCurrent(){ return current; } @@ -43,14 +43,14 @@ public class Saves { if(!state.is(State.menu) && !state.gameOver && current != null && current.isAutosave()){ time += Timers.delta(); - if(time > Settings.getInt("saveinterval")*60) { + if(time > Settings.getInt("saveinterval") * 60){ saving = true; Timers.run(2f, () -> { - try { + try{ SaveIO.saveToSlot(current.index); current.meta = SaveIO.getData(current.index); - }catch (Exception e){ + }catch(Exception e){ e.printStackTrace(); } saving = false; @@ -77,7 +77,7 @@ public class Saves { public void addSave(String name){ SaveSlot slot = new SaveSlot(nextSlot); - nextSlot ++; + nextSlot++; slot.setName(name); saves.add(slot); SaveIO.saveToSlot(slot.index); @@ -88,7 +88,7 @@ public class Saves { public SaveSlot importSave(FileHandle file) throws IOException{ SaveSlot slot = new SaveSlot(nextSlot); slot.importFile(file); - nextSlot ++; + nextSlot++; slot.setName(file.nameWithoutExtension()); saves.add(slot); slot.meta = SaveIO.getData(slot.index); @@ -129,11 +129,11 @@ public class Saves { } public String getName(){ - return Settings.getString("save-"+index+"-name", "untittled"); + return Settings.getString("save-" + index + "-name", "untittled"); } public void setName(String name){ - Settings.putString("save-"+index+"-name", name); + Settings.putString("save-" + index + "-name", name); Settings.save(); } @@ -150,18 +150,18 @@ public class Saves { } public boolean isAutosave(){ - return Settings.getBool("save-"+index+"-autosave", !gwt); + return Settings.getBool("save-" + index + "-autosave", !gwt); } public void setAutosave(boolean save){ - Settings.putBool("save-"+index + "-autosave", save); + Settings.putBool("save-" + index + "-autosave", save); Settings.save(); } public void importFile(FileHandle file) throws IOException{ try{ file.copyTo(SaveIO.fileFor(index)); - }catch (Exception e){ + }catch(Exception e){ throw new IOException(e); } } @@ -172,7 +172,7 @@ public class Saves { file = file.parent().child(file.nameWithoutExtension() + "." + saveExtension); } SaveIO.fileFor(index).copyTo(file); - }catch (Exception e){ + }catch(Exception e){ throw new IOException(e); } } diff --git a/core/src/io/anuke/mindustry/io/TypeIO.java b/core/src/io/anuke/mindustry/io/TypeIO.java index 152a72bc20..34475643ea 100644 --- a/core/src/io/anuke/mindustry/io/TypeIO.java +++ b/core/src/io/anuke/mindustry/io/TypeIO.java @@ -27,14 +27,16 @@ import java.nio.ByteBuffer; import static io.anuke.mindustry.Vars.*; -/**Class for specifying read/write methods for code generation.*/ -public class TypeIO { +/** + * Class for specifying read/write methods for code generation. + */ +public class TypeIO{ @WriteClass(Player.class) public static void writePlayer(ByteBuffer buffer, Player player){ if(player == null){ buffer.putInt(-1); - }else { + }else{ buffer.putInt(player.id); } } @@ -47,7 +49,7 @@ public class TypeIO { @WriteClass(Unit.class) public static void writeUnit(ByteBuffer buffer, Unit unit){ - buffer.put((byte)unit.getGroup().getID()); + buffer.put((byte) unit.getGroup().getID()); buffer.putInt(unit.getID()); } @@ -55,12 +57,12 @@ public class TypeIO { public static Unit readUnit(ByteBuffer buffer){ byte gid = buffer.get(); int id = buffer.getInt(); - return (Unit)Entities.getGroup(gid).getByID(id); + return (Unit) Entities.getGroup(gid).getByID(id); } @WriteClass(ShooterTrait.class) public static void writeShooter(ByteBuffer buffer, ShooterTrait trait){ - buffer.put((byte)trait.getGroup().getID()); + buffer.put((byte) trait.getGroup().getID()); buffer.putInt(trait.getID()); } @@ -85,10 +87,10 @@ public class TypeIO { @WriteClass(CarriableTrait.class) public static void writeCarriable(ByteBuffer buffer, CarriableTrait unit){ if(unit == null){ - buffer.put((byte)-1); + buffer.put((byte) -1); return; } - buffer.put((byte)unit.getGroup().getID()); + buffer.put((byte) unit.getGroup().getID()); buffer.putInt(unit.getID()); } @@ -99,16 +101,16 @@ public class TypeIO { return null; } int id = buffer.getInt(); - return (CarriableTrait)Entities.getGroup(gid).getByID(id); + return (CarriableTrait) Entities.getGroup(gid).getByID(id); } @WriteClass(CarryTrait.class) public static void writeCarry(ByteBuffer buffer, CarryTrait unit){ if(unit == null){ - buffer.put((byte)-1); + buffer.put((byte) -1); return; } - buffer.put((byte)unit.getGroup().getID()); + buffer.put((byte) unit.getGroup().getID()); buffer.putInt(unit.getID()); } @@ -119,12 +121,12 @@ public class TypeIO { return null; } int id = buffer.getInt(); - return (CarryTrait)Entities.getGroup(gid).getByID(id); + return (CarryTrait) Entities.getGroup(gid).getByID(id); } @WriteClass(BaseUnit.class) public static void writeBaseUnit(ByteBuffer buffer, BaseUnit unit){ - buffer.put((byte)unitGroups[unit.getTeam().ordinal()].getID()); + buffer.put((byte) unitGroups[unit.getTeam().ordinal()].getID()); buffer.putInt(unit.getID()); } @@ -132,7 +134,7 @@ public class TypeIO { public static BaseUnit writeBaseUnit(ByteBuffer buffer){ byte gid = buffer.get(); int id = buffer.getInt(); - return (BaseUnit)Entities.getGroup(gid).getByID(id); + return (BaseUnit) Entities.getGroup(gid).getByID(id); } @WriteClass(Tile.class) @@ -147,7 +149,7 @@ public class TypeIO { @WriteClass(Block.class) public static void writeBlock(ByteBuffer buffer, Block block){ - buffer.put((byte)block.id); + buffer.put((byte) block.id); } @ReadClass(Block.class) @@ -157,7 +159,7 @@ public class TypeIO { @WriteClass(KickReason.class) public static void writeKick(ByteBuffer buffer, KickReason reason){ - buffer.put((byte)reason.ordinal()); + buffer.put((byte) reason.ordinal()); } @ReadClass(KickReason.class) @@ -167,7 +169,7 @@ public class TypeIO { @WriteClass(Team.class) public static void writeTeam(ByteBuffer buffer, Team reason){ - buffer.put((byte)reason.ordinal()); + buffer.put((byte) reason.ordinal()); } @ReadClass(Team.class) @@ -177,7 +179,7 @@ public class TypeIO { @WriteClass(AdminAction.class) public static void writeAction(ByteBuffer buffer, AdminAction reason){ - buffer.put((byte)reason.ordinal()); + buffer.put((byte) reason.ordinal()); } @ReadClass(AdminAction.class) @@ -187,7 +189,7 @@ public class TypeIO { @WriteClass(Effect.class) public static void writeEffect(ByteBuffer buffer, Effect effect){ - buffer.putShort((short)effect.id); + buffer.putShort((short) effect.id); } @ReadClass(Effect.class) @@ -227,7 +229,7 @@ public class TypeIO { @WriteClass(Liquid.class) public static void writeLiquid(ByteBuffer buffer, Liquid liquid){ - buffer.put((byte)liquid.id); + buffer.put((byte) liquid.id); } @ReadClass(Liquid.class) @@ -247,7 +249,7 @@ public class TypeIO { @WriteClass(BulletType.class) public static void writeBulletType(ByteBuffer buffer, BulletType type){ - buffer.put((byte)type.id); + buffer.put((byte) type.id); } @ReadClass(BulletType.class) @@ -257,7 +259,7 @@ public class TypeIO { @WriteClass(Item.class) public static void writeItem(ByteBuffer buffer, Item item){ - buffer.put((byte)item.id); + buffer.put((byte) item.id); } @ReadClass(Item.class) @@ -267,7 +269,7 @@ public class TypeIO { @WriteClass(Recipe.class) public static void writeRecipe(ByteBuffer buffer, Recipe recipe){ - buffer.put((byte)recipe.id); + buffer.put((byte) recipe.id); } @ReadClass(Recipe.class) @@ -277,19 +279,19 @@ public class TypeIO { @WriteClass(String.class) public static void writeString(ByteBuffer buffer, String string){ - if(string != null) { + if(string != null){ byte[] bytes = string.getBytes(); buffer.putShort((short) bytes.length); buffer.put(bytes); }else{ - buffer.putShort((short)-1); + buffer.putShort((short) -1); } } @ReadClass(String.class) public static String readString(ByteBuffer buffer){ short length = buffer.getShort(); - if(length != -1) { + if(length != -1){ byte[] bytes = new byte[length]; buffer.get(bytes); return new String(bytes); @@ -300,7 +302,7 @@ public class TypeIO { @WriteClass(byte[].class) public static void writeBytes(ByteBuffer buffer, byte[] bytes){ - buffer.putShort((short)bytes.length); + buffer.putShort((short) bytes.length); buffer.put(bytes); } @@ -315,10 +317,10 @@ public class TypeIO { @WriteClass(TraceInfo.class) public static void writeTrace(ByteBuffer buffer, TraceInfo info){ buffer.putInt(info.playerid); - buffer.putShort((short)info.ip.getBytes().length); + buffer.putShort((short) info.ip.getBytes().length); buffer.put(info.ip.getBytes()); - buffer.put(info.modclient ? (byte)1 : 0); - buffer.put(info.android ? (byte)1 : 0); + buffer.put(info.modclient ? (byte) 1 : 0); + buffer.put(info.android ? (byte) 1 : 0); buffer.putInt(info.totalBlocksBroken); buffer.putInt(info.structureBlocksBroken); diff --git a/core/src/io/anuke/mindustry/io/Version.java b/core/src/io/anuke/mindustry/io/Version.java index ea5cd6d542..8bd816fb89 100644 --- a/core/src/io/anuke/mindustry/io/Version.java +++ b/core/src/io/anuke/mindustry/io/Version.java @@ -8,7 +8,7 @@ import io.anuke.ucore.util.Strings; import java.io.IOException; -public class Version { +public class Version{ public static String name; public static String type; public static String code; @@ -16,7 +16,7 @@ public class Version { public static String buildName; public static void init(){ - try { + try{ FileHandle file = Gdx.files.internal("version.properties"); ObjectMap map = new ObjectMap<>(); @@ -28,7 +28,7 @@ public class Version { build = Strings.canParseInt(map.get("build")) ? Integer.parseInt(map.get("build")) : -1; buildName = build == -1 ? map.get("build") : "build " + build; - }catch (IOException e){ + }catch(IOException e){ throw new RuntimeException(e); } } diff --git a/core/src/io/anuke/mindustry/io/versions/Save16.java b/core/src/io/anuke/mindustry/io/versions/Save16.java index b116b627cc..ece1178776 100644 --- a/core/src/io/anuke/mindustry/io/versions/Save16.java +++ b/core/src/io/anuke/mindustry/io/versions/Save16.java @@ -27,14 +27,14 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.state; import static io.anuke.mindustry.Vars.world; -public class Save16 extends SaveFileVersion { +public class Save16 extends SaveFileVersion{ public Save16(){ super(16); } @Override - public void read(DataInputStream stream) throws IOException { + public void read(DataInputStream stream) throws IOException{ /*long loadTime = */ stream.readLong(); @@ -61,7 +61,7 @@ public class Save16 extends SaveFileVersion { IntMap blockMap = new IntMap<>(); - for(int i = 0; i < blocksize; i ++){ + for(int i = 0; i < blocksize; i++){ String name = stream.readUTF(); int id = stream.readShort(); @@ -72,9 +72,9 @@ public class Save16 extends SaveFileVersion { byte groups = stream.readByte(); - for (int i = 0; i < groups; i++) { + for(int i = 0; i < groups; i++){ int amount = stream.readInt(); - for (int j = 0; j < amount; j++) { + for(int j = 0; j < amount; j++){ byte typeid = stream.readByte(); SaveTrait trait = (SaveTrait) TypeTrait.getTypeByID(typeid).get(); trait.readSave(stream); @@ -94,8 +94,8 @@ public class Save16 extends SaveFileVersion { Tile[][] tiles = world.createTiles(width, height); - for (int i = 0; i < width * height; i++) { - int x = i % width, y = i /width; + for(int i = 0; i < width * height; i++){ + int x = i % width, y = i / width; byte floorid = stream.readByte(); byte wallid = stream.readByte(); byte elevation = stream.readByte(); @@ -103,9 +103,9 @@ public class Save16 extends SaveFileVersion { Tile tile = new Tile(x, y, floorid, wallid); tile.elevation = elevation; - if (wallid == Blocks.blockpart.id) { + if(wallid == Blocks.blockpart.id){ tile.link = stream.readByte(); - }else if (tile.entity != null) { + }else if(tile.entity != null){ byte tr = stream.readByte(); short health = stream.readShort(); @@ -118,10 +118,10 @@ public class Save16 extends SaveFileVersion { tile.entity.health = health; tile.setRotation(rotation); - if (tile.entity.items != null) tile.entity.items.read(stream); - if (tile.entity.power != null) tile.entity.power.read(stream); - if (tile.entity.liquids != null) tile.entity.liquids.read(stream); - if (tile.entity.cons != null) tile.entity.cons.read(stream); + if(tile.entity.items != null) tile.entity.items.read(stream); + if(tile.entity.power != null) tile.entity.power.read(stream); + if(tile.entity.liquids != null) tile.entity.liquids.read(stream); + if(tile.entity.cons != null) tile.entity.cons.read(stream); tile.entity.read(stream); @@ -132,7 +132,7 @@ public class Save16 extends SaveFileVersion { }else if(wallid == 0){ int consecutives = stream.readUnsignedByte(); - for (int j = i + 1; j < i + 1 + consecutives; j++) { + for(int j = i + 1; j < i + 1 + consecutives; j++){ int newx = j % width, newy = j / width; Tile newTile = new Tile(newx, newy, floorid, wallid); newTile.elevation = elevation; @@ -149,7 +149,7 @@ public class Save16 extends SaveFileVersion { } @Override - public void write(DataOutputStream stream) throws IOException { + public void write(DataOutputStream stream) throws IOException{ //--META-- stream.writeInt(version); //version id stream.writeLong(TimeUtils.millis()); //last saved @@ -168,7 +168,7 @@ public class Save16 extends SaveFileVersion { stream.writeInt(Block.all().size); - for(int i = 0; i < Block.all().size; i ++){ + for(int i = 0; i < Block.all().size; i++){ Block block = Block.all().get(i); stream.writeUTF(block.name); stream.writeShort(block.id); @@ -181,7 +181,7 @@ public class Save16 extends SaveFileVersion { for(EntityGroup group : Entities.getAllGroups()){ if(!group.isEmpty() && group.all().get(0) instanceof SaveTrait){ - groups ++; + groups++; } } @@ -191,8 +191,8 @@ public class Save16 extends SaveFileVersion { if(!group.isEmpty() && group.all().get(0) instanceof SaveTrait){ stream.writeInt(group.size()); for(Entity entity : group.all()){ - stream.writeByte(((SaveTrait)entity).getTypeID()); - ((SaveTrait)entity).writeSave(stream); + stream.writeByte(((SaveTrait) entity).getTypeID()); + ((SaveTrait) entity).writeSave(stream); } } } @@ -205,7 +205,7 @@ public class Save16 extends SaveFileVersion { stream.writeShort(world.width()); stream.writeShort(world.height()); - for (int i = 0; i < world.width() * world.height(); i++) { + for(int i = 0; i < world.width() * world.height(); i++){ Tile tile = world.tile(i); stream.writeByte(tile.getFloorID()); @@ -216,7 +216,7 @@ public class Save16 extends SaveFileVersion { stream.writeByte(tile.link); }else if(tile.entity != null){ stream.writeByte(Bits.packByte(tile.getTeamID(), tile.getRotation())); //team + rotation - stream.writeShort((short)tile.entity.health); //health + stream.writeShort((short) tile.entity.health); //health if(tile.entity.items != null) tile.entity.items.write(stream); if(tile.entity.power != null) tile.entity.power.write(stream); @@ -227,14 +227,14 @@ public class Save16 extends SaveFileVersion { }else if(tile.getWallID() == 0){ int consecutives = 0; - for (int j = i + 1; j < world.width() * world.height() && consecutives < 255; j++) { + for(int j = i + 1; j < world.width() * world.height() && consecutives < 255; j++){ Tile nextTile = world.tile(j); if(nextTile.getFloorID() != tile.getFloorID() || nextTile.getWallID() != 0 || nextTile.elevation != tile.elevation){ break; } - consecutives ++; + consecutives++; } stream.writeByte(consecutives); diff --git a/core/src/io/anuke/mindustry/net/Administration.java b/core/src/io/anuke/mindustry/net/Administration.java index c63f628c2b..bb838d4afd 100644 --- a/core/src/io/anuke/mindustry/net/Administration.java +++ b/core/src/io/anuke/mindustry/net/Administration.java @@ -15,24 +15,30 @@ import io.anuke.ucore.core.Settings; import static io.anuke.mindustry.Vars.headless; import static io.anuke.mindustry.Vars.world; -public class Administration { +public class Administration{ public static final int defaultMaxBrokenBlocks = 15; - public static final int defaultBreakCooldown = 1000*15; + public static final int defaultBreakCooldown = 1000 * 15; - /**All player info. Maps UUIDs to info. This persists throughout restarts.*/ + /** + * All player info. Maps UUIDs to info. This persists throughout restarts. + */ private ObjectMap playerInfo = new ObjectMap<>(); - /**Maps UUIDs to trace infos. This is wiped when a player logs off.*/ + /** + * Maps UUIDs to trace infos. This is wiped when a player logs off. + */ private ObjectMap traceInfo = new ObjectMap<>(); - /**Maps packed coordinates to logs for that coordinate */ + /** + * Maps packed coordinates to logs for that coordinate + */ private IntMap> editLogs = new IntMap<>(); private Array bannedIPs = new Array<>(); public Administration(){ Settings.defaultList( - "antigrief", false, - "antigrief-max", defaultMaxBrokenBlocks, - "antigrief-cooldown", defaultBreakCooldown + "antigrief", false, + "antigrief-max", defaultMaxBrokenBlocks, + "antigrief-cooldown", defaultBreakCooldown ); load(); @@ -42,6 +48,11 @@ public class Administration { return Settings.getBool("antigrief"); } + public void setAntiGrief(boolean antiGrief){ + Settings.putBool("antigrief", antiGrief); + Settings.save(); + } + public boolean allowsCustomClients(){ return Settings.getBool("allow-custom", !headless); } @@ -55,31 +66,26 @@ public class Administration { return false; } - public void setAntiGrief(boolean antiGrief){ - Settings.putBool("antigrief", antiGrief); - Settings.save(); - } - public void setAntiGriefParams(int maxBreak, int cooldown){ Settings.putInt("antigrief-max", maxBreak); Settings.putInt("antigrief-cooldown", cooldown); Settings.save(); } - public IntMap> getEditLogs() { + public IntMap> getEditLogs(){ return editLogs; } - public void logEdit(int x, int y, Player player, Block block, int rotation, EditLog.EditAction action) { - if(block instanceof BlockPart || block instanceof Rock || block instanceof Floor || block instanceof StaticBlock) return; - if(editLogs.containsKey(x + y * world.width())) { - editLogs.get(x + y * world.width()).add(new EditLog(player.name, block, rotation, action)); - } - else { - Array logs = new Array<>(); - logs.add(new EditLog(player.name, block, rotation, action)); - editLogs.put(x + y * world.width(), logs); - } + public void logEdit(int x, int y, Player player, Block block, int rotation, EditLog.EditAction action){ + if(block instanceof BlockPart || block instanceof Rock || block instanceof Floor || block instanceof StaticBlock) + return; + if(editLogs.containsKey(x + y * world.width())){ + editLogs.get(x + y * world.width()).add(new EditLog(player.name, block, rotation, action)); + }else{ + Array logs = new Array<>(); + logs.add(new EditLog(player.name, block, rotation, action)); + editLogs.put(x + y * world.width(), logs); + } } /* @@ -143,18 +149,18 @@ public class Administration { long[] breaks = info.lastBroken; int shiftBy = 0; - for(int i = 0; i < breaks.length && breaks[i] != 0; i ++){ + for(int i = 0; i < breaks.length && breaks[i] != 0; i++){ if(TimeUtils.timeSinceMillis(breaks[i]) >= Settings.getInt("antigrief-cooldown")){ shiftBy = i; } } - for (int i = 0; i < breaks.length; i++) { + for(int i = 0; i < breaks.length; i++){ breaks[i] = (i + shiftBy >= breaks.length) ? 0 : breaks[i + shiftBy]; } int remaining = 0; - for(int i = 0; i < breaks.length; i ++){ + for(int i = 0; i < breaks.length; i++){ if(breaks[i] == 0){ remaining = breaks.length - i; break; @@ -167,17 +173,21 @@ public class Administration { return true; } - /**Call when a player joins to update their information here.*/ + /** + * Call when a player joins to update their information here. + */ public void updatePlayerJoined(String id, String ip, String name){ PlayerInfo info = getCreateInfo(id); info.lastName = name; info.lastIP = ip; - info.timesJoined ++; + info.timesJoined++; if(!info.names.contains(name, false)) info.names.add(name); if(!info.ips.contains(ip, false)) info.ips.add(ip); } - /**Returns trace info by IP.*/ + /** + * Returns trace info by IP. + */ public TraceInfo getTraceByID(String uuid){ if(!traceInfo.containsKey(uuid)) traceInfo.put(uuid, new TraceInfo(uuid)); @@ -188,8 +198,10 @@ public class Administration { traceInfo.clear(); } - /**Bans a player by IP; returns whether this player was already banned. - * If there are players who at any point had this IP, they will be UUID banned as well.*/ + /** + * Bans a player by IP; returns whether this player was already banned. + * If there are players who at any point had this IP, they will be UUID banned as well. + */ public boolean banPlayerIP(String ip){ if(bannedIPs.contains(ip, false)) return false; @@ -206,7 +218,9 @@ public class Administration { return true; } - /**Bans a player by UUID; returns whether this player was already banned.*/ + /** + * Bans a player by UUID; returns whether this player was already banned. + */ public boolean banPlayerID(String id){ if(playerInfo.containsKey(id) && playerInfo.get(id).banned) return false; @@ -218,8 +232,10 @@ public class Administration { return true; } - /**Unbans a player by IP; returns whether this player was banned in the first place. - * This method also unbans any player that was banned and had this IP.*/ + /** + * Unbans a player by IP; returns whether this player was banned in the first place. + * This method also unbans any player that was banned and had this IP. + */ public boolean unbanPlayerIP(String ip){ boolean found = bannedIPs.contains(ip, false); @@ -237,8 +253,10 @@ public class Administration { return found; } - /**Unbans a player by ID; returns whether this player was banned in the first place. - * This also unbans all IPs the player used.*/ + /** + * Unbans a player by ID; returns whether this player was banned in the first place. + * This also unbans all IPs the player used. + */ public boolean unbanPlayerID(String id){ PlayerInfo info = getCreateInfo(id); @@ -252,7 +270,9 @@ public class Administration { return true; } - /**Returns list of all players with admin status*/ + /** + * Returns list of all players with admin status + */ public Array getAdmins(){ Array result = new Array<>(); for(PlayerInfo info : playerInfo.values()){ @@ -263,7 +283,9 @@ public class Administration { return result; } - /**Returns list of all players with admin status*/ + /** + * Returns list of all players with admin status + */ public Array getBanned(){ Array result = new Array<>(); for(PlayerInfo info : playerInfo.values()){ @@ -274,12 +296,16 @@ public class Administration { return result; } - /**Returns all banned IPs. This does not include the IPs of ID-banned players.*/ + /** + * Returns all banned IPs. This does not include the IPs of ID-banned players. + */ public Array getBannedIPs(){ return bannedIPs; } - /**Makes a player an admin. Returns whether this player was already an admin.*/ + /** + * Makes a player an admin. Returns whether this player was already an admin. + */ public boolean adminPlayer(String id, String usid){ PlayerInfo info = getCreateInfo(id); @@ -293,7 +319,9 @@ public class Administration { return true; } - /**Makes a player no longer an admin. Returns whether this player was an admin in the first place.*/ + /** + * Makes a player no longer an admin. Returns whether this player was an admin in the first place. + */ public boolean unAdminPlayer(String id){ PlayerInfo info = getCreateInfo(id); @@ -401,7 +429,8 @@ public class Administration { this.id = id; } - private PlayerInfo(){} + private PlayerInfo(){ + } } } diff --git a/core/src/io/anuke/mindustry/net/EditLog.java b/core/src/io/anuke/mindustry/net/EditLog.java index 33d1eb42e1..ab922ae8e0 100644 --- a/core/src/io/anuke/mindustry/net/EditLog.java +++ b/core/src/io/anuke/mindustry/net/EditLog.java @@ -2,20 +2,20 @@ package io.anuke.mindustry.net; import io.anuke.mindustry.world.Block; -public class EditLog { - public String playername; - public Block block; - public int rotation; - public EditAction action; - - EditLog(String playername, Block block, int rotation, EditAction action) { - this.playername = playername; - this.block = block; - this.rotation = rotation; - this.action = action; - } - - public enum EditAction { - PLACE, BREAK +public class EditLog{ + public String playername; + public Block block; + public int rotation; + public EditAction action; + + EditLog(String playername, Block block, int rotation, EditAction action){ + this.playername = playername; + this.block = block; + this.rotation = rotation; + this.action = action; + } + + public enum EditAction{ + PLACE, BREAK } } diff --git a/core/src/io/anuke/mindustry/net/Host.java b/core/src/io/anuke/mindustry/net/Host.java index 631f74a726..36b5a5ecb9 100644 --- a/core/src/io/anuke/mindustry/net/Host.java +++ b/core/src/io/anuke/mindustry/net/Host.java @@ -1,6 +1,6 @@ package io.anuke.mindustry.net; -public class Host { +public class Host{ public final String name; public final String address; public final String mapname; diff --git a/core/src/io/anuke/mindustry/net/In.java b/core/src/io/anuke/mindustry/net/In.java index 712eec04bb..d00e0aba41 100644 --- a/core/src/io/anuke/mindustry/net/In.java +++ b/core/src/io/anuke/mindustry/net/In.java @@ -1,7 +1,9 @@ package io.anuke.mindustry.net; -/**Stores class nameas for remote method invocation for consistency's sake.*/ -public class In { +/** + * Stores class nameas for remote method invocation for consistency's sake. + */ +public class In{ public static final String normal = "Call"; public static final String entities = "CallEntity"; public static final String blocks = "CallBlocks"; diff --git a/core/src/io/anuke/mindustry/net/Interpolator.java b/core/src/io/anuke/mindustry/net/Interpolator.java index ed983bbbf0..9748544b28 100644 --- a/core/src/io/anuke/mindustry/net/Interpolator.java +++ b/core/src/io/anuke/mindustry/net/Interpolator.java @@ -5,7 +5,7 @@ import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.TimeUtils; import io.anuke.ucore.util.Mathf; -public class Interpolator { +public class Interpolator{ //used for movement public Vector2 target = new Vector2(); public Vector2 last = new Vector2(); @@ -55,7 +55,7 @@ public class Interpolator { values = new float[targets.length]; } - for (int i = 0; i < values.length; i++) { + for(int i = 0; i < values.length; i++){ values[i] = Mathf.slerp(values[i], targets[i], alpha); } }else{ diff --git a/core/src/io/anuke/mindustry/net/Net.java b/core/src/io/anuke/mindustry/net/Net.java index 41d5abc494..bd15fc6614 100644 --- a/core/src/io/anuke/mindustry/net/Net.java +++ b/core/src/io/anuke/mindustry/net/Net.java @@ -25,289 +25,393 @@ import static io.anuke.mindustry.Vars.headless; import static io.anuke.mindustry.Vars.ui; public class Net{ - public static final Object packetPoolLock = new Object(); + public static final Object packetPoolLock = new Object(); - private static boolean server; - private static boolean active; - private static boolean clientLoaded; - private static Array packetQueue = new Array<>(); - private static ObjectMap, Consumer> clientListeners = new ObjectMap<>(); - private static ObjectMap, BiConsumer> serverListeners = new ObjectMap<>(); - private static ClientProvider clientProvider; - private static ServerProvider serverProvider; + private static boolean server; + private static boolean active; + private static boolean clientLoaded; + private static Array packetQueue = new Array<>(); + private static ObjectMap, Consumer> clientListeners = new ObjectMap<>(); + private static ObjectMap, BiConsumer> serverListeners = new ObjectMap<>(); + private static ClientProvider clientProvider; + private static ServerProvider serverProvider; - private static IntMap streams = new IntMap<>(); + private static IntMap streams = new IntMap<>(); - /**Display a network error.*/ - public static void showError(String text){ - if(!headless){ - ui.showError(text); - }else{ - Log.err(text); - } - } + /** + * Display a network error. + */ + public static void showError(String text){ + if(!headless){ + ui.showError(text); + }else{ + Log.err(text); + } + } - /**Sets the client loaded status, or whether it will recieve normal packets from the server.*/ - public static void setClientLoaded(boolean loaded){ - clientLoaded = loaded; + /** + * Sets the client loaded status, or whether it will recieve normal packets from the server. + */ + public static void setClientLoaded(boolean loaded){ + clientLoaded = loaded; - if(loaded){ - //handle all packets that were skipped while loading - for(int i = 0; i < packetQueue.size; i ++){ + if(loaded){ + //handle all packets that were skipped while loading + for(int i = 0; i < packetQueue.size; i++){ Log.info("Processing {0} packet post-load.", ClassReflection.getSimpleName(packetQueue.get(i).getClass())); - handleClientReceived(packetQueue.get(i)); - } - } - //clear inbound packet queue - packetQueue.clear(); - } - - /**Connect to an address.*/ - public static void connect(String ip, int port) throws IOException{ - if(!active) { - clientProvider.connect(ip, port); - active = true; - server = false; - }else{ - throw new IOException("Already connected!"); - } - } + handleClientReceived(packetQueue.get(i)); + } + } + //clear inbound packet queue + packetQueue.clear(); + } - /**Host a server at an address*/ - public static void host(int port) throws IOException{ - serverProvider.host(port); - active = true; - server = true; + /** + * Connect to an address. + */ + public static void connect(String ip, int port) throws IOException{ + if(!active){ + clientProvider.connect(ip, port); + active = true; + server = false; + }else{ + throw new IOException("Already connected!"); + } + } - Timers.runTask(60f, Platform.instance::updateRPC); - } + /** + * Host a server at an address + */ + public static void host(int port) throws IOException{ + serverProvider.host(port); + active = true; + server = true; - /**Closes the server.*/ - public static void closeServer(){ + Timers.runTask(60f, Platform.instance::updateRPC); + } + + /** + * Closes the server. + */ + public static void closeServer(){ serverProvider.close(); server = false; active = false; } public static void disconnect(){ - clientProvider.disconnect(); - server = false; - active = false; - } + clientProvider.disconnect(); + server = false; + active = false; + } - /**Starts discovering servers on a different thread. Does not work with GWT. - * Callback is run on the main libGDX thread.*/ - public static void discoverServers(Consumer> cons){ - clientProvider.discover(cons); - } + /** + * Starts discovering servers on a different thread. Does not work with GWT. + * Callback is run on the main libGDX thread. + */ + public static void discoverServers(Consumer> cons){ + clientProvider.discover(cons); + } - /**Returns a list of all connections IDs.*/ - public static Array getConnections(){ - return (Array)serverProvider.getConnections(); - } + /** + * Returns a list of all connections IDs. + */ + public static Array getConnections(){ + return (Array) serverProvider.getConnections(); + } - /**Returns a connection by ID*/ - public static NetConnection getConnection(int id){ - return serverProvider.getByID(id); - } - - /**Send an object to all connected clients, or to the server if this is a client.*/ - public static void send(Object object, SendMode mode){ - if(server){ - if(serverProvider != null) serverProvider.send(object, mode); - }else { - if(clientProvider != null) clientProvider.send(object, mode); - } - } + /** + * Returns a connection by ID + */ + public static NetConnection getConnection(int id){ + return serverProvider.getByID(id); + } - /**Send an object to a certain client. Server-side only*/ - public static void sendTo(int id, Object object, SendMode mode){ - serverProvider.sendTo(id, object, mode); - } + /** + * Send an object to all connected clients, or to the server if this is a client. + */ + public static void send(Object object, SendMode mode){ + if(server){ + if(serverProvider != null) serverProvider.send(object, mode); + }else{ + if(clientProvider != null) clientProvider.send(object, mode); + } + } - /**Send an object to everyone EXCEPT certain client. Server-side only*/ - public static void sendExcept(int id, Object object, SendMode mode){ - serverProvider.sendExcept(id, object, mode); - } + /** + * Send an object to a certain client. Server-side only + */ + public static void sendTo(int id, Object object, SendMode mode){ + serverProvider.sendTo(id, object, mode); + } - /**Send a stream to a specific client. Server-side only.*/ - public static void sendStream(int id, Streamable stream){ - serverProvider.sendStream(id, stream); - } - - /**Sets the net clientProvider, e.g. what handles sending, recieving and connecting to a server.*/ - public static void setClientProvider(ClientProvider provider){ - Net.clientProvider = provider; - } + /** + * Send an object to everyone EXCEPT certain client. Server-side only + */ + public static void sendExcept(int id, Object object, SendMode mode){ + serverProvider.sendExcept(id, object, mode); + } - /**Sets the net serverProvider, e.g. what handles hosting a server.*/ - public static void setServerProvider(ServerProvider provider){ - Net.serverProvider = provider; - } + /** + * Send a stream to a specific client. Server-side only. + */ + public static void sendStream(int id, Streamable stream){ + serverProvider.sendStream(id, stream); + } - /**Registers a client listener for when an object is recieved.*/ - public static void handleClient(Class type, Consumer listener){ - clientListeners.put(type, listener); - } + /** + * Sets the net clientProvider, e.g. what handles sending, recieving and connecting to a server. + */ + public static void setClientProvider(ClientProvider provider){ + Net.clientProvider = provider; + } - /**Registers a server listener for when an object is recieved.*/ - public static void handleServer(Class type, BiConsumer listener){ - serverListeners.put(type, (BiConsumer) listener); - } - - /**Call to handle a packet being recieved for the client.*/ - public static void handleClientReceived(Object object){ + /** + * Sets the net serverProvider, e.g. what handles hosting a server. + */ + public static void setServerProvider(ServerProvider provider){ + Net.serverProvider = provider; + } - if(object instanceof StreamBegin) { - StreamBegin b = (StreamBegin) object; - streams.put(b.id, new StreamBuilder(b)); - }else if(object instanceof StreamChunk) { - StreamChunk c = (StreamChunk)object; - StreamBuilder builder = streams.get(c.id); - if(builder == null){ - throw new RuntimeException("Recieved stream chunk without a StreamBegin beforehand!"); - } - builder.add(c.data); - if(builder.isDone()){ - streams.remove(builder.id); - handleClientReceived(builder.build()); - } - }else if(clientListeners.get(object.getClass()) != null){ + /** + * Registers a client listener for when an object is recieved. + */ + public static void handleClient(Class type, Consumer listener){ + clientListeners.put(type, listener); + } - if(clientLoaded || ((object instanceof Packet) && ((Packet) object).isImportant())){ - if(clientListeners.get(object.getClass()) != null) clientListeners.get(object.getClass()).accept(object); - synchronized (packetPoolLock) { - Pooling.free(object); - } - }else if(!((object instanceof Packet) && ((Packet) object).isUnimportant())){ - packetQueue.add(object); - Log.info("Queuing packet {0}.", ClassReflection.getSimpleName(object.getClass())); - }else{ - synchronized (packetPoolLock) { - Pooling.free(object); - } - } - }else{ - Log.err("Unhandled packet type: '{0}'!", ClassReflection.getSimpleName(object.getClass())); - } - } + /** + * Registers a server listener for when an object is recieved. + */ + public static void handleServer(Class type, BiConsumer listener){ + serverListeners.put(type, (BiConsumer) listener); + } - /**Call to handle a packet being recieved for the server.*/ - public static void handleServerReceived(int connection, Object object){ + /** + * Call to handle a packet being recieved for the client. + */ + public static void handleClientReceived(Object object){ - if(serverListeners.get(object.getClass()) != null){ - if(serverListeners.get(object.getClass()) != null) serverListeners.get(object.getClass()).accept(connection, object); - synchronized (packetPoolLock) { - Pooling.free(object); - } - }else{ - Log.err("Unhandled packet type: '{0}'!", ClassReflection.getSimpleName(object.getClass())); - } - } + if(object instanceof StreamBegin){ + StreamBegin b = (StreamBegin) object; + streams.put(b.id, new StreamBuilder(b)); + }else if(object instanceof StreamChunk){ + StreamChunk c = (StreamChunk) object; + StreamBuilder builder = streams.get(c.id); + if(builder == null){ + throw new RuntimeException("Recieved stream chunk without a StreamBegin beforehand!"); + } + builder.add(c.data); + if(builder.isDone()){ + streams.remove(builder.id); + handleClientReceived(builder.build()); + } + }else if(clientListeners.get(object.getClass()) != null){ - /**Pings a host in an new thread. If an error occured, failed() should be called with the exception. */ - public static void pingHost(String address, int port, Consumer valid, Consumer failed){ - clientProvider.pingHost(address, port, valid, failed); - } + if(clientLoaded || ((object instanceof Packet) && ((Packet) object).isImportant())){ + if(clientListeners.get(object.getClass()) != null) + clientListeners.get(object.getClass()).accept(object); + synchronized(packetPoolLock){ + Pooling.free(object); + } + }else if(!((object instanceof Packet) && ((Packet) object).isUnimportant())){ + packetQueue.add(object); + Log.info("Queuing packet {0}.", ClassReflection.getSimpleName(object.getClass())); + }else{ + synchronized(packetPoolLock){ + Pooling.free(object); + } + } + }else{ + Log.err("Unhandled packet type: '{0}'!", ClassReflection.getSimpleName(object.getClass())); + } + } - /**Update client ping.*/ - public static void updatePing(){ - clientProvider.updatePing(); - } + /** + * Call to handle a packet being recieved for the server. + */ + public static void handleServerReceived(int connection, Object object){ - /**Get the client ping. Only valid after updatePing().*/ - public static int getPing(){ - return server() ? 0 : clientProvider.getPing(); - } - - /**Whether the net is active, e.g. whether this is a multiplayer game.*/ - public static boolean active(){ - return active; - } - - /**Whether this is a server or not.*/ - public static boolean server(){ - return server && active; - } + if(serverListeners.get(object.getClass()) != null){ + if(serverListeners.get(object.getClass()) != null) + serverListeners.get(object.getClass()).accept(connection, object); + synchronized(packetPoolLock){ + Pooling.free(object); + } + }else{ + Log.err("Unhandled packet type: '{0}'!", ClassReflection.getSimpleName(object.getClass())); + } + } - /**Whether this is a client or not.*/ - public static boolean client(){ - return !server && active; - } + /** + * Pings a host in an new thread. If an error occured, failed() should be called with the exception. + */ + public static void pingHost(String address, int port, Consumer valid, Consumer failed){ + clientProvider.pingHost(address, port, valid, failed); + } - public static void dispose(){ - if(clientProvider != null) clientProvider.dispose(); - if(serverProvider != null) serverProvider.dispose(); - clientProvider = null; - serverProvider = null; - server = false; - active = false; - } + /** + * Update client ping. + */ + public static void updatePing(){ + clientProvider.updatePing(); + } - public static void http(String url, String method, Consumer listener, Consumer failure){ - HttpRequest req = new HttpRequestBuilder().newRequest() - .method(method).url(url).build(); + /** + * Get the client ping. Only valid after updatePing(). + */ + public static int getPing(){ + return server() ? 0 : clientProvider.getPing(); + } - Gdx.net.sendHttpRequest(req, new HttpResponseListener() { - @Override - public void handleHttpResponse(HttpResponse httpResponse) { - listener.accept(httpResponse.getResultAsString()); - } + /** + * Whether the net is active, e.g. whether this is a multiplayer game. + */ + public static boolean active(){ + return active; + } - @Override - public void failed(Throwable t) { - failure.accept(t); - } + /** + * Whether this is a server or not. + */ + public static boolean server(){ + return server && active; + } - @Override - public void cancelled() {} - }); - } + /** + * Whether this is a client or not. + */ + public static boolean client(){ + return !server && active; + } - /**Client implementation.*/ - public interface ClientProvider { - /**Connect to a server.*/ - void connect(String ip, int port) throws IOException; - /**Send an object to the server.*/ - void send(Object object, SendMode mode); - /**Update the ping. Should be done every second or so.*/ - void updatePing(); - /**Get ping in milliseconds. Will only be valid after a call to updatePing.*/ - int getPing(); - /**Disconnect from the server.*/ - void disconnect(); - /**Discover servers. This should run the callback regardless of whether any servers are found. Should not block. - * Callback should be run on libGDX main thread.*/ + public static void dispose(){ + if(clientProvider != null) clientProvider.dispose(); + if(serverProvider != null) serverProvider.dispose(); + clientProvider = null; + serverProvider = null; + server = false; + active = false; + } + + public static void http(String url, String method, Consumer listener, Consumer failure){ + HttpRequest req = new HttpRequestBuilder().newRequest() + .method(method).url(url).build(); + + Gdx.net.sendHttpRequest(req, new HttpResponseListener(){ + @Override + public void handleHttpResponse(HttpResponse httpResponse){ + listener.accept(httpResponse.getResultAsString()); + } + + @Override + public void failed(Throwable t){ + failure.accept(t); + } + + @Override + public void cancelled(){ + } + }); + } + + public enum SendMode{ + tcp, udp + } + + /** + * Client implementation. + */ + public interface ClientProvider{ + /** + * Connect to a server. + */ + void connect(String ip, int port) throws IOException; + + /** + * Send an object to the server. + */ + void send(Object object, SendMode mode); + + /** + * Update the ping. Should be done every second or so. + */ + void updatePing(); + + /** + * Get ping in milliseconds. Will only be valid after a call to updatePing. + */ + int getPing(); + + /** + * Disconnect from the server. + */ + void disconnect(); + + /** + * Discover servers. This should run the callback regardless of whether any servers are found. Should not block. + * Callback should be run on libGDX main thread. + */ void discover(Consumer> callback); - /**Ping a host. If an error occured, failed() should be called with the exception. */ + + /** + * Ping a host. If an error occured, failed() should be called with the exception. + */ void pingHost(String address, int port, Consumer valid, Consumer failed); - /**Close all connections.*/ - void dispose(); - } - /**Server implementation.*/ - public interface ServerProvider { - /**Host a server at specified port.*/ - void host(int port) throws IOException; - /**Sends a large stream of data to a specific client.*/ - void sendStream(int id, Streamable stream); - /**Send an object to everyone connected.*/ - void send(Object object, SendMode mode); - /**Send an object to a specific client ID.*/ - void sendTo(int id, Object object, SendMode mode); - /**Send an object to everyone except a client ID.*/ - void sendExcept(int id, Object object, SendMode mode); - /**Close the server connection.*/ - void close(); - /**Return all connected users.*/ - Array getConnections(); - /**Returns a connection by ID.*/ - NetConnection getByID(int id); - /**Close all connections.*/ - void dispose(); - } + /** + * Close all connections. + */ + void dispose(); + } - public enum SendMode{ - tcp, udp - } + /** + * Server implementation. + */ + public interface ServerProvider{ + /** + * Host a server at specified port. + */ + void host(int port) throws IOException; + + /** + * Sends a large stream of data to a specific client. + */ + void sendStream(int id, Streamable stream); + + /** + * Send an object to everyone connected. + */ + void send(Object object, SendMode mode); + + /** + * Send an object to a specific client ID. + */ + void sendTo(int id, Object object, SendMode mode); + + /** + * Send an object to everyone except a client ID. + */ + void sendExcept(int id, Object object, SendMode mode); + + /** + * Close the server connection. + */ + void close(); + + /** + * Return all connected users. + */ + Array getConnections(); + + /** + * Returns a connection by ID. + */ + NetConnection getByID(int id); + + /** + * Close all connections. + */ + void dispose(); + } } diff --git a/core/src/io/anuke/mindustry/net/NetConnection.java b/core/src/io/anuke/mindustry/net/NetConnection.java index b4b25a76be..9c66726434 100644 --- a/core/src/io/anuke/mindustry/net/NetConnection.java +++ b/core/src/io/anuke/mindustry/net/NetConnection.java @@ -2,14 +2,18 @@ package io.anuke.mindustry.net; import io.anuke.mindustry.net.Net.SendMode; -public abstract class NetConnection { +public abstract class NetConnection{ public final int id; public final String address; - /**The current base snapshot that the client is absolutely confirmed to have recieved. - * All sent snapshots should be taking the diff from this base snapshot, if it isn't null.*/ + /** + * The current base snapshot that the client is absolutely confirmed to have recieved. + * All sent snapshots should be taking the diff from this base snapshot, if it isn't null. + */ public byte[] currentBaseSnapshot; - /**ID of the current base snapshot.*/ + /** + * ID of the current base snapshot. + */ public int currentBaseID = -1; public int lastSentBase = -1; @@ -17,9 +21,13 @@ public abstract class NetConnection { public byte[] lastSentRawSnapshot; public int lastSentSnapshotID = -1; - /**ID of last recieved client snapshot.*/ + /** + * ID of last recieved client snapshot. + */ public int lastRecievedClientSnapshot = -1; - /**Timestamp of last recieved snapshot.*/ + /** + * Timestamp of last recieved snapshot. + */ public long lastRecievedClientTime; public boolean hasConnected = false; @@ -34,5 +42,6 @@ public abstract class NetConnection { } public abstract void send(Object object, SendMode mode); + public abstract void close(); } diff --git a/core/src/io/anuke/mindustry/net/NetEvents.java b/core/src/io/anuke/mindustry/net/NetEvents.java index 749513a3a4..01dbfe5b90 100644 --- a/core/src/io/anuke/mindustry/net/NetEvents.java +++ b/core/src/io/anuke/mindustry/net/NetEvents.java @@ -9,7 +9,7 @@ import io.anuke.mindustry.entities.Player; import static io.anuke.mindustry.Vars.maxTextLength; import static io.anuke.mindustry.Vars.playerGroup; -public class NetEvents { +public class NetEvents{ @Remote(called = Loc.server, targets = Loc.both, forward = true) public static void sendMessage(Player player, String message){ diff --git a/core/src/io/anuke/mindustry/net/NetworkIO.java b/core/src/io/anuke/mindustry/net/NetworkIO.java index 1625079e2e..8ece7638a3 100644 --- a/core/src/io/anuke/mindustry/net/NetworkIO.java +++ b/core/src/io/anuke/mindustry/net/NetworkIO.java @@ -24,7 +24,7 @@ import java.nio.ByteBuffer; import static io.anuke.mindustry.Vars.*; -public class NetworkIO { +public class NetworkIO{ public static void writeWorld(Player player, OutputStream os){ @@ -59,7 +59,7 @@ public class NetworkIO { stream.writeShort(world.width()); stream.writeShort(world.height()); - for (int i = 0; i < world.width() * world.height(); i++) { + for(int i = 0; i < world.width() * world.height(); i++){ Tile tile = world.tile(i); stream.writeByte(tile.getFloorID()); @@ -70,7 +70,7 @@ public class NetworkIO { stream.writeByte(tile.link); }else if(tile.entity != null){ stream.writeByte(Bits.packByte(tile.getTeamID(), tile.getRotation())); //team + rotation - stream.writeShort((short)tile.entity.health); //health + stream.writeShort((short) tile.entity.health); //health if(tile.entity.items != null) tile.entity.items.write(stream); if(tile.entity.power != null) tile.entity.power.write(stream); @@ -81,14 +81,14 @@ public class NetworkIO { }else if(tile.getWallID() == 0){ int consecutives = 0; - for (int j = i + 1; j < world.width() * world.height() && consecutives < 255; j++) { + for(int j = i + 1; j < world.width() * world.height() && consecutives < 255; j++){ Tile nextTile = world.tile(j); if(nextTile.getFloorID() != tile.getFloorID() || nextTile.getWallID() != 0 || nextTile.elevation != tile.elevation){ break; } - consecutives ++; + consecutives++; } stream.writeByte(consecutives); @@ -107,12 +107,14 @@ public class NetworkIO { } } - }catch (IOException e){ + }catch(IOException e){ throw new RuntimeException(e); } } - /**Return whether a custom map is expected, and thus whether the client should wait for additional data.*/ + /** + * Return whether a custom map is expected, and thus whether the client should wait for additional data. + */ public static void loadWorld(InputStream is){ Player player = players[0]; @@ -132,7 +134,7 @@ public class NetworkIO { ObjectMap tags = new ObjectMap<>(); byte tagSize = stream.readByte(); - for (int i = 0; i < tagSize; i++) { + for(int i = 0; i < tagSize; i++){ String key = stream.readUTF(); String value = stream.readUTF(); tags.put(key, value); @@ -169,8 +171,8 @@ public class NetworkIO { Tile[][] tiles = world.createTiles(width, height); - for (int i = 0; i < width * height; i++) { - int x = i % width, y = i /width; + for(int i = 0; i < width * height; i++){ + int x = i % width, y = i / width; byte floorid = stream.readByte(); byte wallid = stream.readByte(); byte elevation = stream.readByte(); @@ -178,9 +180,9 @@ public class NetworkIO { Tile tile = new Tile(x, y, floorid, wallid); tile.elevation = elevation; - if (wallid == Blocks.blockpart.id) { + if(wallid == Blocks.blockpart.id){ tile.link = stream.readByte(); - }else if (tile.entity != null) { + }else if(tile.entity != null){ byte tr = stream.readByte(); short health = stream.readShort(); @@ -191,16 +193,16 @@ public class NetworkIO { tile.entity.health = health; tile.setRotation(rotation); - if (tile.entity.items != null) tile.entity.items.read(stream); - if (tile.entity.power != null) tile.entity.power.read(stream); - if (tile.entity.liquids != null) tile.entity.liquids.read(stream); - if (tile.entity.cons != null) tile.entity.cons.read(stream); + if(tile.entity.items != null) tile.entity.items.read(stream); + if(tile.entity.power != null) tile.entity.power.read(stream); + if(tile.entity.liquids != null) tile.entity.liquids.read(stream); + if(tile.entity.cons != null) tile.entity.cons.read(stream); tile.entity.read(stream); }else if(wallid == 0){ int consecutives = stream.readUnsignedByte(); - for (int j = i + 1; j < i + 1 + consecutives; j++) { + for(int j = i + 1; j < i + 1 + consecutives; j++){ int newx = j % width, newy = j / width; Tile newTile = new Tile(newx, newy, floorid, wallid); newTile.elevation = elevation; @@ -217,13 +219,13 @@ public class NetworkIO { state.teams = new TeamInfo(); byte teams = stream.readByte(); - for (int i = 0; i < teams; i++) { + for(int i = 0; i < teams; i++){ Team team = Team.all[stream.readByte()]; boolean ally = stream.readBoolean(); short cores = stream.readShort(); state.teams.add(team, ally); - for (int j = 0; j < cores; j++) { + for(int j = 0; j < cores; j++){ state.teams.get(team).cores.add(world.tile(stream.readInt())); } @@ -234,7 +236,7 @@ public class NetworkIO { world.endMapLoad(); - }catch (IOException e){ + }catch(IOException e){ throw new RuntimeException(e); } } @@ -250,10 +252,10 @@ public class NetworkIO { ByteBuffer buffer = ByteBuffer.allocate(128); - buffer.put((byte)host.getBytes().length); + buffer.put((byte) host.getBytes().length); buffer.put(host.getBytes()); - buffer.put((byte)map.getBytes().length); + buffer.put((byte) map.getBytes().length); buffer.put(map.getBytes()); buffer.putInt(playerGroup.size()); diff --git a/core/src/io/anuke/mindustry/net/Packet.java b/core/src/io/anuke/mindustry/net/Packet.java index 91f9b3a7de..ef34666dbb 100644 --- a/core/src/io/anuke/mindustry/net/Packet.java +++ b/core/src/io/anuke/mindustry/net/Packet.java @@ -5,10 +5,14 @@ import com.badlogic.gdx.utils.Pool.Poolable; import java.nio.ByteBuffer; public interface Packet extends Poolable{ - default void read(ByteBuffer buffer){} - default void write(ByteBuffer buffer){} + default void read(ByteBuffer buffer){ + } - default void reset() {} + default void write(ByteBuffer buffer){ + } + + default void reset(){ + } default boolean isImportant(){ return false; diff --git a/core/src/io/anuke/mindustry/net/Packets.java b/core/src/io/anuke/mindustry/net/Packets.java index 7ba866677f..bcadd4c5cc 100644 --- a/core/src/io/anuke/mindustry/net/Packets.java +++ b/core/src/io/anuke/mindustry/net/Packets.java @@ -16,15 +16,34 @@ import java.nio.ByteBuffer; import static io.anuke.mindustry.Vars.world; -/**Class for storing all packets.*/ -public class Packets { +/** + * Class for storing all packets. + */ +public class Packets{ + + public enum KickReason{ + kick, invalidPassword, clientOutdated, serverOutdated, banned, gameover(true), recentKick, nameInUse, idInUse, fastShoot, nameEmpty, customClient; + public final boolean quiet; + + KickReason(){ + quiet = false; + } + + KickReason(boolean quiet){ + this.quiet = quiet; + } + } + + public enum AdminAction{ + kick, ban, trace, wave + } public static class Connect implements Packet{ public int id; public String addressTCP; @Override - public boolean isImportant() { + public boolean isImportant(){ return true; } } @@ -33,7 +52,7 @@ public class Packets { public int id; @Override - public boolean isImportant() { + public boolean isImportant(){ return true; } } @@ -49,17 +68,17 @@ public class Packets { public int color; @Override - public void write(ByteBuffer buffer) { + public void write(ByteBuffer buffer){ buffer.putInt(Version.build); IOUtils.writeString(buffer, name); IOUtils.writeString(buffer, usid); - buffer.put(mobile ? (byte)1 : 0); + buffer.put(mobile ? (byte) 1 : 0); buffer.putInt(color); buffer.put(Base64Coder.decode(uuid)); } @Override - public void read(ByteBuffer buffer) { + public void read(ByteBuffer buffer){ version = buffer.getInt(); name = IOUtils.readString(buffer); usid = IOUtils.readString(buffer); @@ -78,7 +97,7 @@ public class Packets { public int writeLength; @Override - public void read(ByteBuffer buffer) { + public void read(ByteBuffer buffer){ type = buffer.get(); priority = buffer.get(); writeLength = buffer.getShort(); @@ -88,29 +107,29 @@ public class Packets { } @Override - public void write(ByteBuffer buffer) { + public void write(ByteBuffer buffer){ buffer.put(type); buffer.put(priority); - buffer.putShort((short)writeLength); + buffer.putShort((short) writeLength); writeBuffer.position(0); - for(int i = 0; i < writeLength; i ++){ + for(int i = 0; i < writeLength; i++){ buffer.put(writeBuffer.get()); } } @Override - public void reset() { + public void reset(){ priority = 0; } @Override - public boolean isImportant() { + public boolean isImportant(){ return priority == 1; } @Override - public boolean isUnimportant() { + public boolean isUnimportant(){ return priority == 2; } } @@ -127,7 +146,7 @@ public class Packets { public BuildRequest currentRequest; @Override - public void write(ByteBuffer buffer) { + public void write(ByteBuffer buffer){ Player player = Vars.players[0]; buffer.putInt(lastSnapshot); @@ -138,33 +157,33 @@ public class Packets { buffer.putFloat(player.y); buffer.putFloat(player.pointerX); buffer.putFloat(player.pointerY); - buffer.put(player.isBoosting ? (byte)1 : 0); - buffer.put(player.isShooting ? (byte)1 : 0); + buffer.put(player.isBoosting ? (byte) 1 : 0); + buffer.put(player.isShooting ? (byte) 1 : 0); - buffer.put((byte)(Mathf.clamp(player.getVelocity().x, -Unit.maxAbsVelocity, Unit.maxAbsVelocity) * Unit.velocityPercision)); - buffer.put((byte)(Mathf.clamp(player.getVelocity().y, -Unit.maxAbsVelocity, Unit.maxAbsVelocity) * Unit.velocityPercision)); + buffer.put((byte) (Mathf.clamp(player.getVelocity().x, -Unit.maxAbsVelocity, Unit.maxAbsVelocity) * Unit.velocityPercision)); + buffer.put((byte) (Mathf.clamp(player.getVelocity().y, -Unit.maxAbsVelocity, Unit.maxAbsVelocity) * Unit.velocityPercision)); //saving 4 bytes, yay? - buffer.putShort((short)(player.rotation*2)); - buffer.putShort((short)(player.baseRotation*2)); + buffer.putShort((short) (player.rotation * 2)); + buffer.putShort((short) (player.baseRotation * 2)); buffer.putInt(player.getMineTile() == null ? -1 : player.getMineTile().packedPosition()); BuildRequest request = player.getCurrentRequest(); if(request != null){ - buffer.put(request.remove ? (byte)1 : 0); + buffer.put(request.remove ? (byte) 1 : 0); buffer.putInt(world.toPacked(request.x, request.y)); if(!request.remove){ - buffer.put((byte)request.recipe.id); - buffer.put((byte)request.rotation); + buffer.put((byte) request.recipe.id); + buffer.put((byte) request.rotation); } }else{ - buffer.put((byte)-1); + buffer.put((byte) -1); } } @Override - public void read(ByteBuffer buffer) { + public void read(ByteBuffer buffer){ lastSnapshot = buffer.getInt(); snapid = buffer.getInt(); timeSent = buffer.getLong(); @@ -177,17 +196,17 @@ public class Packets { shooting = buffer.get() == 1; xv = buffer.get() / Unit.velocityPercision; yv = buffer.get() / Unit.velocityPercision; - rotation = buffer.getShort()/2f; - baseRotation = buffer.getShort()/2f; + rotation = buffer.getShort() / 2f; + baseRotation = buffer.getShort() / 2f; mining = world.tile(buffer.getInt()); byte type = buffer.get(); - if (type != -1) { + if(type != -1){ int position = buffer.getInt(); - if (type == 1) { //remove + if(type == 1){ //remove currentRequest = new BuildRequest(position % world.width(), position / world.width()); - } else { //place + }else{ //place byte recipe = buffer.get(); byte rotation = buffer.get(); currentRequest = new BuildRequest(position % world.width(), position / world.width(), rotation, Recipe.getByID(recipe)); @@ -198,41 +217,28 @@ public class Packets { } } - public enum KickReason{ - kick, invalidPassword, clientOutdated, serverOutdated, banned, gameover(true), recentKick, nameInUse, idInUse, fastShoot, nameEmpty, customClient; - public final boolean quiet; - - KickReason(){ quiet = false; } - - KickReason(boolean quiet){ - this.quiet = quiet; - } - } - - public enum AdminAction{ - kick, ban, trace, wave - } - - /**Marks the beginning of a stream.*/ + /** + * Marks the beginning of a stream. + */ public static class StreamBegin implements Packet{ private static int lastid; - public int id = lastid ++; + public int id = lastid++; public int total; public Class type; @Override - public void write(ByteBuffer buffer) { + public void write(ByteBuffer buffer){ buffer.putInt(id); buffer.putInt(total); buffer.put(Registrator.getID(type)); } @Override - public void read(ByteBuffer buffer) { + public void read(ByteBuffer buffer){ id = buffer.getInt(); total = buffer.getInt(); - type = (Class)Registrator.getByID(buffer.get()); + type = (Class) Registrator.getByID(buffer.get()); } } @@ -241,14 +247,14 @@ public class Packets { public byte[] data; @Override - public void write(ByteBuffer buffer) { + public void write(ByteBuffer buffer){ buffer.putInt(id); - buffer.putShort((short)data.length); + buffer.putShort((short) data.length); buffer.put(data); } @Override - public void read(ByteBuffer buffer) { + public void read(ByteBuffer buffer){ id = buffer.getInt(); data = new byte[buffer.getShort()]; buffer.get(data); diff --git a/core/src/io/anuke/mindustry/net/Registrator.java b/core/src/io/anuke/mindustry/net/Registrator.java index f35a991d50..6dbabdcc15 100644 --- a/core/src/io/anuke/mindustry/net/Registrator.java +++ b/core/src/io/anuke/mindustry/net/Registrator.java @@ -3,25 +3,24 @@ package io.anuke.mindustry.net; import com.badlogic.gdx.utils.ObjectIntMap; import com.badlogic.gdx.utils.reflect.ClassReflection; import io.anuke.mindustry.net.Packets.*; -import io.anuke.mindustry.net.Packets.StreamBegin; -import io.anuke.mindustry.net.Packets.StreamChunk; -public class Registrator { +public class Registrator{ private static Class[] classes = { - StreamBegin.class, - StreamChunk.class, - WorldStream.class, - ConnectPacket.class, - ClientSnapshotPacket.class, - InvokePacket.class + StreamBegin.class, + StreamChunk.class, + WorldStream.class, + ConnectPacket.class, + ClientSnapshotPacket.class, + InvokePacket.class }; private static ObjectIntMap> ids = new ObjectIntMap<>(); static{ if(classes.length > 127) throw new RuntimeException("Can't have more than 127 registered classes!"); - for(int i = 0; i < classes.length; i ++){ + for(int i = 0; i < classes.length; i++){ if(!ClassReflection.isAssignableFrom(Packet.class, classes[i]) && - !ClassReflection.isAssignableFrom(Streamable.class, classes[i])) throw new RuntimeException("Not a packet: " + classes[i]); + !ClassReflection.isAssignableFrom(Streamable.class, classes[i])) + throw new RuntimeException("Not a packet: " + classes[i]); ids.put(classes[i], i); } } @@ -31,7 +30,7 @@ public class Registrator { } public static byte getID(Class type){ - return (byte)ids.get(type, -1); + return (byte) ids.get(type, -1); } public static Class[] getClasses(){ diff --git a/core/src/io/anuke/mindustry/net/Streamable.java b/core/src/io/anuke/mindustry/net/Streamable.java index 5815fdd845..a27ea42ced 100644 --- a/core/src/io/anuke/mindustry/net/Streamable.java +++ b/core/src/io/anuke/mindustry/net/Streamable.java @@ -11,6 +11,11 @@ import java.io.IOException; public class Streamable implements Packet{ public transient ByteArrayInputStream stream; + @Override + public boolean isImportant(){ + return true; + } + public static class StreamBuilder{ public final int id; public final Class type; @@ -25,15 +30,15 @@ public class Streamable implements Packet{ } public void add(byte[] bytes){ - try { + try{ stream.write(bytes); - }catch (IOException e){ + }catch(IOException e){ throw new RuntimeException(e); } } public Streamable build(){ - try { + try{ Streamable s = ClassReflection.newInstance(type); s.stream = new ByteArrayInputStream(stream.toByteArray()); return s; @@ -46,9 +51,4 @@ public class Streamable implements Packet{ return stream.size() >= total; } } - - @Override - public boolean isImportant() { - return true; - } } diff --git a/core/src/io/anuke/mindustry/net/TraceInfo.java b/core/src/io/anuke/mindustry/net/TraceInfo.java index 996363e1a7..512c8e85ff 100644 --- a/core/src/io/anuke/mindustry/net/TraceInfo.java +++ b/core/src/io/anuke/mindustry/net/TraceInfo.java @@ -1,10 +1,10 @@ package io.anuke.mindustry.net; import com.badlogic.gdx.utils.IntIntMap; -import io.anuke.mindustry.world.Block; import io.anuke.mindustry.content.blocks.Blocks; +import io.anuke.mindustry.world.Block; -public class TraceInfo { +public class TraceInfo{ public int playerid; public String ip; public boolean modclient; diff --git a/core/src/io/anuke/mindustry/net/ValidateException.java b/core/src/io/anuke/mindustry/net/ValidateException.java index 35fe5d6114..89923702de 100644 --- a/core/src/io/anuke/mindustry/net/ValidateException.java +++ b/core/src/io/anuke/mindustry/net/ValidateException.java @@ -2,11 +2,13 @@ package io.anuke.mindustry.net; import io.anuke.mindustry.entities.Player; -/**Thrown when a client sends invalid information.*/ +/** + * Thrown when a client sends invalid information. + */ public class ValidateException extends RuntimeException{ public final Player player; - public ValidateException(Player player, String s) { + public ValidateException(Player player, String s){ super(s); this.player = player; } diff --git a/core/src/io/anuke/mindustry/type/AmmoEntry.java b/core/src/io/anuke/mindustry/type/AmmoEntry.java index fc14db1cce..a4e0559df1 100644 --- a/core/src/io/anuke/mindustry/type/AmmoEntry.java +++ b/core/src/io/anuke/mindustry/type/AmmoEntry.java @@ -1,11 +1,13 @@ package io.anuke.mindustry.type; -/**Used to store ammo amounts in units and turrets.*/ +/** + * Used to store ammo amounts in units and turrets. + */ public class AmmoEntry{ public AmmoType type; public int amount; - public AmmoEntry(AmmoType type, int amount) { + public AmmoEntry(AmmoType type, int amount){ this.type = type; this.amount = amount; } diff --git a/core/src/io/anuke/mindustry/type/AmmoType.java b/core/src/io/anuke/mindustry/type/AmmoType.java index 88ad721a9e..90a5eb0601 100644 --- a/core/src/io/anuke/mindustry/type/AmmoType.java +++ b/core/src/io/anuke/mindustry/type/AmmoType.java @@ -11,32 +11,52 @@ public class AmmoType implements Content{ private static Array allTypes = new Array<>(32); public final byte id; - /**The item used. Always null if liquid isn't.*/ + /** + * The item used. Always null if liquid isn't. + */ public final Item item; - /**The liquid used. Always null if item isn't.*/ + /** + * The liquid used. Always null if item isn't. + */ public final Liquid liquid; - /**The resulting bullet. Never null.*/ + /** + * The resulting bullet. Never null. + */ public final BulletType bullet; - /**For item ammo, this is amount given per ammo item. - * For liquid ammo, this is amount used per shot.*/ + /** + * For item ammo, this is amount given per ammo item. + * For liquid ammo, this is amount used per shot. + */ public final float quantityMultiplier; - /**Reload speed multiplier.*/ + /** + * Reload speed multiplier. + */ public float reloadMultiplier = 1f; - /**Bullet recoil strength.*/ + /** + * Bullet recoil strength. + */ public float recoil = 0f; - /**Additional inaccuracy in degrees.*/ + /** + * Additional inaccuracy in degrees. + */ public float inaccuracy; - /**Effect created when shooting.*/ + /** + * Effect created when shooting. + */ public Effect shootEffect = Fx.none; - /**Extra smoke effect created when shooting.*/ + /** + * Extra smoke effect created when shooting. + */ public Effect smokeEffect = Fx.none; { - this.id = (byte)(lastID++); + this.id = (byte) (lastID++); allTypes.add(this); } - /**Creates an AmmoType with no liquid or item. Used for power-based ammo.*/ + /** + * Creates an AmmoType with no liquid or item. Used for power-based ammo. + */ public AmmoType(BulletType result){ this.item = null; this.liquid = null; @@ -45,7 +65,9 @@ public class AmmoType implements Content{ this.reloadMultiplier = 1f; } - /**Creates an AmmoType with an item.*/ + /** + * Creates an AmmoType with an item. + */ public AmmoType(Item item, BulletType result, float multiplier){ this.item = item; this.liquid = null; @@ -53,7 +75,9 @@ public class AmmoType implements Content{ this.quantityMultiplier = multiplier; } - /**Creates an AmmoType with a liquid.*/ + /** + * Creates an AmmoType with a liquid. + */ public AmmoType(Liquid liquid, BulletType result, float multiplier){ this.item = null; this.liquid = liquid; @@ -61,26 +85,28 @@ public class AmmoType implements Content{ this.quantityMultiplier = multiplier; } - /**Returns maximum distance the bullet this ammo type has can travel.*/ - public float getRange(){ - return bullet.speed * bullet.lifetime; - } - - @Override - public String getContentTypeName() { - return "ammotype"; - } - - @Override - public Array getAll() { - return allTypes; - } - - public static Array all() { + public static Array all(){ return allTypes; } public static AmmoType getByID(int id){ return allTypes.get(id); } + + /** + * Returns maximum distance the bullet this ammo type has can travel. + */ + public float getRange(){ + return bullet.speed * bullet.lifetime; + } + + @Override + public String getContentTypeName(){ + return "ammotype"; + } + + @Override + public Array getAll(){ + return allTypes; + } } diff --git a/core/src/io/anuke/mindustry/type/Category.java b/core/src/io/anuke/mindustry/type/Category.java index 58ccdb9015..6d75054b2f 100644 --- a/core/src/io/anuke/mindustry/type/Category.java +++ b/core/src/io/anuke/mindustry/type/Category.java @@ -1,5 +1,5 @@ package io.anuke.mindustry.type; -public enum Category { - weapon, production, distribution, liquid, power, defense, crafting, units +public enum Category{ + weapon, production, distribution, liquid, power, defense, crafting, units } diff --git a/core/src/io/anuke/mindustry/type/ContentList.java b/core/src/io/anuke/mindustry/type/ContentList.java index dc288f21ef..edeb482dca 100644 --- a/core/src/io/anuke/mindustry/type/ContentList.java +++ b/core/src/io/anuke/mindustry/type/ContentList.java @@ -3,11 +3,17 @@ package io.anuke.mindustry.type; import com.badlogic.gdx.utils.Array; import io.anuke.mindustry.game.Content; -/**Interface for a list of content to be loaded in {@link io.anuke.mindustry.core.ContentLoader}.*/ -public interface ContentList { - /**This method should create all the content.*/ +/** + * Interface for a list of content to be loaded in {@link io.anuke.mindustry.core.ContentLoader}. + */ +public interface ContentList{ + /** + * This method should create all the content. + */ void load(); - /**This method should return the list of the content of this type, for further loading.*/ + /** + * This method should return the list of the content of this type, for further loading. + */ Array getAll(); } diff --git a/core/src/io/anuke/mindustry/type/Item.java b/core/src/io/anuke/mindustry/type/Item.java index 31c66b51ba..18653372c6 100644 --- a/core/src/io/anuke/mindustry/type/Item.java +++ b/core/src/io/anuke/mindustry/type/Item.java @@ -15,95 +15,111 @@ import io.anuke.ucore.util.Strings; import io.anuke.ucore.util.ThreadArray; public class Item implements Comparable, UnlockableContent{ - private static final ThreadArray items = new ThreadArray<>(); + private static final ThreadArray items = new ThreadArray<>(); - public final int id; - public final String name; - public final String description; - public final Color color; - public TextureRegion region; + public final int id; + public final String name; + public final String description; + public final Color color; + public TextureRegion region; - /**type of the item; used for tabs and core acceptance. default value is {@link ItemType#resource}.*/ - public ItemType type = ItemType.resource; - /**how explosive this item is.*/ - public float explosiveness = 0f; - /**flammability above 0.3 makes this eleigible for item burners.*/ - public float flammability = 0f; - /**how radioactive this item is. 0=none, 1=chernobyl ground zero*/ - public float radioactivity; - /**how effective this item is as flux for smelting. 0 = not a flux, 0.5 = normal flux, 1 = very good*/ - public float fluxiness = 0f; - /**drill hardness of the item*/ - public int hardness = 0; - /**the burning color of this item*/ - public Color flameColor = Palette.darkFlame.cpy(); - /**base material cost of this item, used for calculating place times - * 1 cost = 1 tick added to build time*/ - public float cost = 3f; + /** + * type of the item; used for tabs and core acceptance. default value is {@link ItemType#resource}. + */ + public ItemType type = ItemType.resource; + /** + * how explosive this item is. + */ + public float explosiveness = 0f; + /** + * flammability above 0.3 makes this eleigible for item burners. + */ + public float flammability = 0f; + /** + * how radioactive this item is. 0=none, 1=chernobyl ground zero + */ + public float radioactivity; + /** + * how effective this item is as flux for smelting. 0 = not a flux, 0.5 = normal flux, 1 = very good + */ + public float fluxiness = 0f; + /** + * drill hardness of the item + */ + public int hardness = 0; + /** + * the burning color of this item + */ + public Color flameColor = Palette.darkFlame.cpy(); + /** + * base material cost of this item, used for calculating place times + * 1 cost = 1 tick added to build time + */ + public float cost = 3f; - public Item(String name, Color color) { - this.id = items.size; - this.name = name; - this.color = color; - this.description = Bundles.getOrNull("item." + this.name + ".description"); + public Item(String name, Color color){ + this.id = items.size; + this.name = name; + this.color = color; + this.description = Bundles.getOrNull("item." + this.name + ".description"); - items.add(this); + items.add(this); - if(!Bundles.has("item." + this.name + ".name")){ + if(!Bundles.has("item." + this.name + ".name")){ Log.err("Warning: item '" + name + "' is missing a localized name. Add the follow to bundle.properties:"); Log.err("item." + this.name + ".name=" + Strings.capitalize(name.replace('-', '_'))); } - } + } - public void load(){ - this.region = Draw.region("item-" + name); - } + public static Array all(){ + return Item.items; + } - @Override - public void displayInfo(Table table) { - ContentDisplay.displayItem(table, this); - } + public static Item getByID(int id){ + return items.get(id); + } - @Override - public String localizedName(){ - return Bundles.get("item." + this.name + ".name"); - } + public void load(){ + this.region = Draw.region("item-" + name); + } - @Override - public TextureRegion getContentIcon() { - return region; - } + @Override + public void displayInfo(Table table){ + ContentDisplay.displayItem(table, this); + } - @Override - public String toString() { - return localizedName(); - } + @Override + public String localizedName(){ + return Bundles.get("item." + this.name + ".name"); + } - @Override - public int compareTo(Item item) { - return Integer.compare(id, item.id); - } + @Override + public TextureRegion getContentIcon(){ + return region; + } - @Override - public String getContentName() { - return name; - } + @Override + public String toString(){ + return localizedName(); + } - @Override - public String getContentTypeName() { - return "item"; - } + @Override + public int compareTo(Item item){ + return Integer.compare(id, item.id); + } - @Override - public Array getAll() { - return all(); - } + @Override + public String getContentName(){ + return name; + } - public static Array all() { - return Item.items; - } + @Override + public String getContentTypeName(){ + return "item"; + } - public static Item getByID(int id){ - return items.get(id); - } + @Override + public Array getAll(){ + return all(); + } } diff --git a/core/src/io/anuke/mindustry/type/ItemStack.java b/core/src/io/anuke/mindustry/type/ItemStack.java index 6ab73ca5af..5714050602 100644 --- a/core/src/io/anuke/mindustry/type/ItemStack.java +++ b/core/src/io/anuke/mindustry/type/ItemStack.java @@ -1,15 +1,15 @@ package io.anuke.mindustry.type; public class ItemStack{ - public io.anuke.mindustry.type.Item item; - public int amount; - - public ItemStack(io.anuke.mindustry.type.Item item, int amount){ - this.item = item; - this.amount = amount; - } + public io.anuke.mindustry.type.Item item; + public int amount; - public boolean equals(ItemStack other){ - return other != null && other.item == item && other.amount == amount; - } + public ItemStack(io.anuke.mindustry.type.Item item, int amount){ + this.item = item; + this.amount = amount; + } + + public boolean equals(ItemStack other){ + return other != null && other.item == item && other.amount == amount; + } } diff --git a/core/src/io/anuke/mindustry/type/ItemType.java b/core/src/io/anuke/mindustry/type/ItemType.java index 5a80183522..fb676443bb 100644 --- a/core/src/io/anuke/mindustry/type/ItemType.java +++ b/core/src/io/anuke/mindustry/type/ItemType.java @@ -1,10 +1,16 @@ package io.anuke.mindustry.type; -public enum ItemType { - /**Not used for anything besides crafting inside blocks.*/ +public enum ItemType{ + /** + * Not used for anything besides crafting inside blocks. + */ resource, - /**Can be used for constructing blocks. Only materials are accepted into the core.*/ + /** + * Can be used for constructing blocks. Only materials are accepted into the core. + */ material, - /**Only used as ammo for turrets.*/ + /** + * Only used as ammo for turrets. + */ ammo } diff --git a/core/src/io/anuke/mindustry/type/Liquid.java b/core/src/io/anuke/mindustry/type/Liquid.java index 4672c529d4..20410304c1 100644 --- a/core/src/io/anuke/mindustry/type/Liquid.java +++ b/core/src/io/anuke/mindustry/type/Liquid.java @@ -13,87 +13,105 @@ import io.anuke.ucore.util.Bundles; import io.anuke.ucore.util.ThreadArray; public class Liquid implements UnlockableContent{ - private static final Array liquids = new ThreadArray<>(); + private static final Array liquids = new ThreadArray<>(); - public final Color color; - public final String name; - public final String description; - public final int id; + public final Color color; + public final String name; + public final String description; + public final int id; - /**0-1, 0 is completely inflammable, anything above that may catch fire when exposed to heat, 0.5+ is very flammable.*/ - public float flammability; - /**temperature: 0.5 is 'room' temperature, 0 is very cold, 1 is molten hot*/ - public float temperature = 0.5f; - /**how much heat this liquid can store. 0.75=water (high), anything lower is probably less dense and bad at cooling.*/ - public float heatCapacity = 0.5f; - /**how thick this liquid is. 0.5=water (relatively viscous), 1 would be something like tar (very slow)*/ - public float viscosity = 0.5f; - /**how prone to exploding this liquid is, when heated. 0 = nothing, 1 = nuke*/ - public float explosiveness; - /**the burning color of this liquid*/ - public Color flameColor = Color.valueOf("ffb763"); - /**The associated status effect.*/ - public StatusEffect effect = StatusEffects.none; - /**Pump tier. Controls which pumps can use this liquid.*/ - public int tier; - /**Displayed icon.*/ - public TextureRegion iconRegion; - - public Liquid(String name, Color color) { - this.name = name; - this.color = new Color(color); + /** + * 0-1, 0 is completely inflammable, anything above that may catch fire when exposed to heat, 0.5+ is very flammable. + */ + public float flammability; + /** + * temperature: 0.5 is 'room' temperature, 0 is very cold, 1 is molten hot + */ + public float temperature = 0.5f; + /** + * how much heat this liquid can store. 0.75=water (high), anything lower is probably less dense and bad at cooling. + */ + public float heatCapacity = 0.5f; + /** + * how thick this liquid is. 0.5=water (relatively viscous), 1 would be something like tar (very slow) + */ + public float viscosity = 0.5f; + /** + * how prone to exploding this liquid is, when heated. 0 = nothing, 1 = nuke + */ + public float explosiveness; + /** + * the burning color of this liquid + */ + public Color flameColor = Color.valueOf("ffb763"); + /** + * The associated status effect. + */ + public StatusEffect effect = StatusEffects.none; + /** + * Pump tier. Controls which pumps can use this liquid. + */ + public int tier; + /** + * Displayed icon. + */ + public TextureRegion iconRegion; - this.id = liquids.size; - this.description = Bundles.getOrNull("liquid." + name + ".description"); + public Liquid(String name, Color color){ + this.name = name; + this.color = new Color(color); - Liquid.liquids.add(this); - } + this.id = liquids.size; + this.description = Bundles.getOrNull("liquid." + name + ".description"); - @Override - public void load() { - iconRegion = Draw.region("liquid-icon-" + name); - } + Liquid.liquids.add(this); + } - @Override - public void displayInfo(Table table) { - ContentDisplay.displayLiquid(table, this); - } + public static Array all(){ + return Liquid.liquids; + } - @Override - public String localizedName(){ - return Bundles.get("liquid."+ this.name + ".name"); - } + public static Liquid getByID(int id){ + return liquids.get(id); + } - @Override - public TextureRegion getContentIcon() { - return iconRegion; - } + @Override + public void load(){ + iconRegion = Draw.region("liquid-icon-" + name); + } - @Override - public String toString(){ - return localizedName(); - } + @Override + public void displayInfo(Table table){ + ContentDisplay.displayLiquid(table, this); + } - @Override - public String getContentName() { - return name; - } + @Override + public String localizedName(){ + return Bundles.get("liquid." + this.name + ".name"); + } - @Override - public String getContentTypeName() { - return "liquid"; - } + @Override + public TextureRegion getContentIcon(){ + return iconRegion; + } - @Override - public Array getAll() { - return all(); - } + @Override + public String toString(){ + return localizedName(); + } - public static Array all() { - return Liquid.liquids; - } + @Override + public String getContentName(){ + return name; + } - public static Liquid getByID(int id){ - return liquids.get(id); - } + @Override + public String getContentTypeName(){ + return "liquid"; + } + + @Override + public Array getAll(){ + return all(); + } } diff --git a/core/src/io/anuke/mindustry/type/Mech.java b/core/src/io/anuke/mindustry/type/Mech.java index c2397535ff..98640fe292 100644 --- a/core/src/io/anuke/mindustry/type/Mech.java +++ b/core/src/io/anuke/mindustry/type/Mech.java @@ -10,64 +10,64 @@ import io.anuke.ucore.scene.ui.layout.Table; //TODO merge unit type with mech public class Mech extends Upgrade implements UnlockableContent{ - public boolean flying; + public boolean flying; - public float speed = 1.1f; - public float maxSpeed = 1.1f; - public float boostSpeed = 0.75f; - public float drag = 0.4f; - public float mass = 1f; - public float armor = 1f; + public float speed = 1.1f; + public float maxSpeed = 1.1f; + public float boostSpeed = 0.75f; + public float drag = 0.4f; + public float mass = 1f; + public float armor = 1f; - public float mineSpeed = 1f; - public int drillPower = -1; - public float carryWeight = 10f; - public float buildPower = 1f; - public boolean canRepair = false; - public Color trailColor = Color.valueOf("ffd37f"); + public float mineSpeed = 1f; + public int drillPower = -1; + public float carryWeight = 10f; + public float buildPower = 1f; + public boolean canRepair = false; + public Color trailColor = Color.valueOf("ffd37f"); - public float weaponOffsetX, weaponOffsetY; + public float weaponOffsetX, weaponOffsetY; - public Weapon weapon = Weapons.blaster; + public Weapon weapon = Weapons.blaster; - public int itemCapacity = 30; - public int ammoCapacity = 100; + public int itemCapacity = 30; + public int ammoCapacity = 100; - public TextureRegion baseRegion, legRegion, region, iconRegion; + public TextureRegion baseRegion, legRegion, region, iconRegion; - public Mech(String name, boolean flying){ - super(name); - this.flying = flying; - } - - @Override - public void displayInfo(Table table) { - ContentDisplay.displayMech(table, this); - } - - @Override - public TextureRegion getContentIcon() { - return iconRegion; - } + public Mech(String name, boolean flying){ + super(name); + this.flying = flying; + } @Override - public String getContentName() { + public void displayInfo(Table table){ + ContentDisplay.displayMech(table, this); + } + + @Override + public TextureRegion getContentIcon(){ + return iconRegion; + } + + @Override + public String getContentName(){ return name; } @Override - public String getContentTypeName() { + public String getContentTypeName(){ return "mech"; } - @Override - public void load() { - if (!flying){ - legRegion = Draw.region(name + "-leg"); - baseRegion = Draw.region(name + "-base"); - } + @Override + public void load(){ + if(!flying){ + legRegion = Draw.region(name + "-leg"); + baseRegion = Draw.region(name + "-base"); + } - region = Draw.region(name); - iconRegion = Draw.region("mech-icon-"+ name); - } + region = Draw.region(name); + iconRegion = Draw.region("mech-icon-" + name); + } } diff --git a/core/src/io/anuke/mindustry/type/Recipe.java b/core/src/io/anuke/mindustry/type/Recipe.java index ccfba98f21..36b5e4bca6 100644 --- a/core/src/io/anuke/mindustry/type/Recipe.java +++ b/core/src/io/anuke/mindustry/type/Recipe.java @@ -19,9 +19,7 @@ import io.anuke.ucore.util.Strings; import java.util.Arrays; -import static io.anuke.mindustry.Vars.control; -import static io.anuke.mindustry.Vars.debug; -import static io.anuke.mindustry.Vars.headless; +import static io.anuke.mindustry.Vars.*; public class Recipe implements UnlockableContent{ private static int lastid; @@ -40,7 +38,7 @@ public class Recipe implements UnlockableContent{ private Recipe[] recipeDependencies; public Recipe(Category category, Block result, ItemStack... requirements){ - this.id = lastid ++; + this.id = lastid++; this.result = result; this.requirements = requirements; this.category = category; @@ -58,96 +56,10 @@ public class Recipe implements UnlockableContent{ recipeMap.put(result, this); } - public Recipe setDependencies(Block... blocks){ - this.dependencies = blocks; - return this; - } - - public Recipe setDesktop(){ - desktopOnly = true; - return this; - } - - public Recipe setDebug(){ - debugOnly = true; - return this; - } - - @Override - public boolean isHidden() { - return debugOnly; - } - - @Override - public void displayInfo(Table table) { - ContentDisplay.displayRecipe(table, this); - } - - @Override - public String localizedName() { - return result.formalName; - } - - @Override - public TextureRegion getContentIcon() { - return result.getEditorIcon(); - } - - @Override - public void init() { - if(!Bundles.has("block." + result.name + ".name")) { - Log.err("WARNING: Recipe block '{0}' does not have a formal name defined. Add the following to bundle.properties:", result.name); - Log.err("block.{0}.name={1}", result.name, Strings.capitalize(result.name.replace('-', '_'))); - }/*else if(result.fullDescription == null){ - Log.err("WARNING: Recipe block '{0}' does not have a description defined.", result.name); - }*/ - } - - @Override - public String getContentName() { - return result.name; - } - - @Override - public String getContentTypeName() { - return "recipe"; - } - - @Override - public void onUnlock() { - for(OrderedMap map : result.stats.toMap().values()){ - for(StatValue value : map.values()){ - if(value instanceof ContentStatValue){ - ContentStatValue stat = (ContentStatValue)value; - UnlockableContent[] content = stat.getValueContent(); - for(UnlockableContent c : content){ - control.database().unlockContent(c); - } - } - } - } - } - - @Override - public UnlockableContent[] getDependencies() { - if(dependencies == null){ - return null; - }else if(recipeDependencies == null){ - recipeDependencies = new Recipe[dependencies.length]; - for (int i = 0; i < recipeDependencies.length; i++) { - recipeDependencies[i] = Recipe.getByResult(dependencies[i]); - } - } - return recipeDependencies; - } - - @Override - public Array getAll() { - return allRecipes; - } - - /**Returns unlocked recipes in a category. - * Do not call on the server backend, as unlocking does not exist!*/ + /** + * Returns unlocked recipes in a category. + * Do not call on the server backend, as unlocking does not exist! + */ public static void getUnlockedByCategory(Category category, Array r){ if(headless){ throw new RuntimeException("Not enabled on the headless backend!"); @@ -155,17 +67,19 @@ public class Recipe implements UnlockableContent{ r.clear(); for(Recipe recipe : allRecipes){ - if(recipe.category == category && (Vars.control.database().isUnlocked(recipe) || (debug && recipe.debugOnly))) { + if(recipe.category == category && (Vars.control.database().isUnlocked(recipe) || (debug && recipe.debugOnly))){ r.add(recipe); } } } - /**Returns all recipes in a category.*/ + /** + * Returns all recipes in a category. + */ public static void getByCategory(Category category, Array r){ r.clear(); for(Recipe recipe : allRecipes){ - if(recipe.category == category) { + if(recipe.category == category){ r.add(recipe); } } @@ -186,4 +100,92 @@ public class Recipe implements UnlockableContent{ return allRecipes.get(id); } } + + public Recipe setDesktop(){ + desktopOnly = true; + return this; + } + + public Recipe setDebug(){ + debugOnly = true; + return this; + } + + @Override + public boolean isHidden(){ + return debugOnly; + } + + @Override + public void displayInfo(Table table){ + ContentDisplay.displayRecipe(table, this); + } + + @Override + public String localizedName(){ + return result.formalName; + } + + @Override + public TextureRegion getContentIcon(){ + return result.getEditorIcon(); + } + + @Override + public void init(){ + if(!Bundles.has("block." + result.name + ".name")){ + Log.err("WARNING: Recipe block '{0}' does not have a formal name defined. Add the following to bundle.properties:", result.name); + Log.err("block.{0}.name={1}", result.name, Strings.capitalize(result.name.replace('-', '_'))); + }/*else if(result.fullDescription == null){ + Log.err("WARNING: Recipe block '{0}' does not have a description defined.", result.name); + }*/ + } + + @Override + public String getContentName(){ + return result.name; + } + + @Override + public String getContentTypeName(){ + return "recipe"; + } + + @Override + public void onUnlock(){ + for(OrderedMap map : result.stats.toMap().values()){ + for(StatValue value : map.values()){ + if(value instanceof ContentStatValue){ + ContentStatValue stat = (ContentStatValue) value; + UnlockableContent[] content = stat.getValueContent(); + for(UnlockableContent c : content){ + control.database().unlockContent(c); + } + } + } + } + } + + @Override + public UnlockableContent[] getDependencies(){ + if(dependencies == null){ + return null; + }else if(recipeDependencies == null){ + recipeDependencies = new Recipe[dependencies.length]; + for(int i = 0; i < recipeDependencies.length; i++){ + recipeDependencies[i] = Recipe.getByResult(dependencies[i]); + } + } + return recipeDependencies; + } + + public Recipe setDependencies(Block... blocks){ + this.dependencies = blocks; + return this; + } + + @Override + public Array getAll(){ + return allRecipes; + } } diff --git a/core/src/io/anuke/mindustry/type/StatusEffect.java b/core/src/io/anuke/mindustry/type/StatusEffect.java index 17a04881de..fa9bcab529 100644 --- a/core/src/io/anuke/mindustry/type/StatusEffect.java +++ b/core/src/io/anuke/mindustry/type/StatusEffect.java @@ -7,75 +7,90 @@ import io.anuke.mindustry.entities.Unit; import io.anuke.mindustry.game.Content; public class StatusEffect implements Content{ - private static final Array array = new Array<>(); - private static int lastid; + private static final Array array = new Array<>(); + private static int lastid; - /**Duration of this status effect in ticks at maximum power.*/ - public final float baseDuration; - public final int id; + /** + * Duration of this status effect in ticks at maximum power. + */ + public final float baseDuration; + public final int id; - public float damageMultiplier = 1f; //damage dealt - public float armorMultiplier = 1f; //armor points - public float speedMultiplier = 1f; //speed + public float damageMultiplier = 1f; //damage dealt + public float armorMultiplier = 1f; //armor points + public float speedMultiplier = 1f; //speed - /**Set of 'opposite' effects, which will decrease the duration of this effect when applied.*/ - protected ObjectSet opposites = new ObjectSet<>(); - /**The strength of time decrease when met with an opposite effect, as a fraction of the other's duration.*/ - protected float oppositeScale = 0.5f; + /** + * Set of 'opposite' effects, which will decrease the duration of this effect when applied. + */ + protected ObjectSet opposites = new ObjectSet<>(); + /** + * The strength of time decrease when met with an opposite effect, as a fraction of the other's duration. + */ + protected float oppositeScale = 0.5f; - public StatusEffect(float baseDuration){ - this.baseDuration = baseDuration; + public StatusEffect(float baseDuration){ + this.baseDuration = baseDuration; - id = lastid++; - array.add(this); - } + id = lastid++; + array.add(this); + } - /**Runs every tick on the affected unit while time is greater than 0.*/ - public void update(Unit unit, float time){} + public static StatusEffect getByID(int id){ + return array.get(id); + } - /**Called when transitioning between two status effects. - * @param to The state to transition to - * @param time The current status effect time - * @param newTime The time that the new status effect will last*/ - public StatusEntry getTransition(Unit unit, StatusEffect to, float time, float newTime, StatusEntry result){ - if(opposites.contains(to)){ - time -= newTime*oppositeScale; - if(time > 0) { - return result.set(this, time); - } - } + public static Array all(){ + return array; + } - return result.set(to, newTime); - } + /** + * Runs every tick on the affected unit while time is greater than 0. + */ + public void update(Unit unit, float time){ + } - /**Called when this effect transitions to a new status effect.*/ - public void onTransition(Unit unit, StatusEffect to){} + /** + * Called when transitioning between two status effects. + * + * @param to The state to transition to + * @param time The current status effect time + * @param newTime The time that the new status effect will last + */ + public StatusEntry getTransition(Unit unit, StatusEffect to, float time, float newTime, StatusEntry result){ + if(opposites.contains(to)){ + time -= newTime * oppositeScale; + if(time > 0){ + return result.set(this, time); + } + } - public boolean isOpposite(StatusEffect other){ - return opposites.size > 0 && opposites.contains(other); - } + return result.set(to, newTime); + } - public void setOpposites(StatusEffect... effects){ - for(StatusEffect e : effects){ - opposites.add(e); - } - } + /** + * Called when this effect transitions to a new status effect. + */ + public void onTransition(Unit unit, StatusEffect to){ + } - @Override - public String getContentTypeName() { - return "statuseffect"; - } + public boolean isOpposite(StatusEffect other){ + return opposites.size > 0 && opposites.contains(other); + } - @Override - public Array getAll() { - return null; - } + public void setOpposites(StatusEffect... effects){ + for(StatusEffect e : effects){ + opposites.add(e); + } + } - public static StatusEffect getByID(int id){ - return array.get(id); - } + @Override + public String getContentTypeName(){ + return "statuseffect"; + } - public static Array all(){ - return array; - } + @Override + public Array getAll(){ + return null; + } } diff --git a/core/src/io/anuke/mindustry/type/Upgrade.java b/core/src/io/anuke/mindustry/type/Upgrade.java index 192e6ced1c..49157843ec 100644 --- a/core/src/io/anuke/mindustry/type/Upgrade.java +++ b/core/src/io/anuke/mindustry/type/Upgrade.java @@ -15,13 +15,29 @@ public abstract class Upgrade implements Content{ public final String description; public Upgrade(String name){ - this.id = lastid ++; + this.id = lastid++; this.name = name; - this.description = Bundles.get("upgrade."+name+".description"); + this.description = Bundles.get("upgrade." + name + ".description"); upgrades.add(this); } + public static void forEach(Consumer type, Predicate pred){ + for(Upgrade u : upgrades){ + if(pred.test(u)){ + type.accept((T) u); + } + } + } + + public static Array all(){ + return upgrades; + } + + public static T getByID(byte id){ + return (T) upgrades.get(id); + } + public String localizedName(){ return Bundles.get("upgrade." + name + ".name"); } @@ -32,23 +48,7 @@ public abstract class Upgrade implements Content{ } @Override - public Array getAll() { + public Array getAll(){ return all(); } - - public static void forEach(Consumer type, Predicate pred){ - for(Upgrade u : upgrades){ - if(pred.test(u)){ - type.accept((T)u); - } - } - } - - public static Array all() { - return upgrades; - } - - public static T getByID(byte id){ - return (T)upgrades.get(id); - } } diff --git a/core/src/io/anuke/mindustry/type/Weapon.java b/core/src/io/anuke/mindustry/type/Weapon.java index cfb8f8eeb1..a5d3610048 100644 --- a/core/src/io/anuke/mindustry/type/Weapon.java +++ b/core/src/io/anuke/mindustry/type/Weapon.java @@ -19,164 +19,191 @@ import io.anuke.ucore.util.Angles; import io.anuke.ucore.util.Mathf; import io.anuke.ucore.util.Translator; -public class Weapon extends Upgrade { - /**minimum cursor distance from player, fixes 'cross-eyed' shooting.*/ - protected static float minPlayerDist = 20f; - /**ammo type map. set with setAmmo()*/ - protected OrderedMap ammoMap = new OrderedMap<>(); - /**shell ejection effect*/ - protected Effect ejectEffect = Fx.none; - /**weapon reload in frames*/ - protected float reload; - /**amount of shots per fire*/ - protected int shots = 1; - /**spacing in degrees between multiple shots, if applicable*/ - protected float spacing = 12f; - /**inaccuracy of degrees of each shot*/ - protected float inaccuracy = 0f; - /**intensity and duration of each shot's screen shake*/ - protected float shake = 0f; - /**visual weapon knockback.*/ - protected float recoil = 1.5f; - /**shoot barrel y offset*/ - protected float length = 3f; - /**shoot barrel x offset.*/ - protected float width = 4f; - /**fraction of velocity that is random*/ - protected float velocityRnd = 0f; - /**whether to shoot the weapons in different arms one after another, rather than all at once*/ - protected boolean roundrobin = false; - /**translator for vector calulations*/ - protected Translator tr = new Translator(); +public class Weapon extends Upgrade{ + /** + * minimum cursor distance from player, fixes 'cross-eyed' shooting. + */ + protected static float minPlayerDist = 20f; + public TextureRegion equipRegion, region; + /** + * ammo type map. set with setAmmo() + */ + protected OrderedMap ammoMap = new OrderedMap<>(); + /** + * shell ejection effect + */ + protected Effect ejectEffect = Fx.none; + /** + * weapon reload in frames + */ + protected float reload; + /** + * amount of shots per fire + */ + protected int shots = 1; + /** + * spacing in degrees between multiple shots, if applicable + */ + protected float spacing = 12f; + /** + * inaccuracy of degrees of each shot + */ + protected float inaccuracy = 0f; + /** + * intensity and duration of each shot's screen shake + */ + protected float shake = 0f; + /** + * visual weapon knockback. + */ + protected float recoil = 1.5f; + /** + * shoot barrel y offset + */ + protected float length = 3f; + /** + * shoot barrel x offset. + */ + protected float width = 4f; + /** + * fraction of velocity that is random + */ + protected float velocityRnd = 0f; + /** + * whether to shoot the weapons in different arms one after another, rather than all at once + */ + protected boolean roundrobin = false; + /** + * translator for vector calulations + */ + protected Translator tr = new Translator(); - public TextureRegion equipRegion, region; + protected Weapon(String name){ + super(name); + } - protected Weapon(String name){ - super(name); - } + @Remote(targets = Loc.server, called = Loc.both, in = In.entities, unreliable = true) + public static void onPlayerShootWeapon(Player player, float x, float y, float rotation, boolean left){ + if(player == null) return; + //clients do not see their own shoot events: they are simulated completely clientside to prevent laggy visuals + //messing with the firerate or any other stats does not affect the server (take that, script kiddies!) + if(Net.client() && player == Vars.players[0]){ + return; + } - @Override - public void load() { - equipRegion = Draw.region(name + "-equip"); - region = Draw.region(name); - } + shootDirect(player, x, y, rotation, left); + } - @Override - public String getContentTypeName() { - return "weapon"; - } + @Remote(targets = Loc.server, called = Loc.both, in = In.entities, unreliable = true) + public static void onGenericShootWeapon(ShooterTrait shooter, float x, float y, float rotation, boolean left){ + if(shooter == null) return; + shootDirect(shooter, x, y, rotation, left); + } - public void update(ShooterTrait shooter, float pointerX, float pointerY){ + public static void shootDirect(ShooterTrait shooter, float x, float y, float rotation, boolean left){ + Weapon weapon = shooter.getWeapon(); + + Angles.shotgun(weapon.shots, weapon.spacing, rotation, f -> weapon.bullet(shooter, x, y, f + Mathf.range(weapon.inaccuracy))); + + AmmoType type = shooter.getInventory().getAmmo(); + + if(type == null) return; + + weapon.tr.trns(rotation + 180f, type.recoil); + + shooter.getVelocity().add(weapon.tr); + + weapon.tr.trns(rotation, 3f); + + Effects.shake(weapon.shake, weapon.shake, x, y); + Effects.effect(weapon.ejectEffect, x, y, rotation * -Mathf.sign(left)); + Effects.effect(type.shootEffect, x + weapon.tr.x, y + weapon.tr.y, rotation, shooter); + Effects.effect(type.smokeEffect, x + weapon.tr.x, y + weapon.tr.y, rotation, shooter); + + //reset timer for remote players + shooter.getTimer().get(shooter.getShootTimer(left), weapon.reload); + } + + @Override + public void load(){ + equipRegion = Draw.region(name + "-equip"); + region = Draw.region(name); + } + + @Override + public String getContentTypeName(){ + return "weapon"; + } + + public void update(ShooterTrait shooter, float pointerX, float pointerY){ update(shooter, true, pointerX, pointerY); update(shooter, false, pointerX, pointerY); } - private void update(ShooterTrait shooter, boolean left, float pointerX, float pointerY){ - if(shooter.getInventory().hasAmmo() && shooter.getTimer().get(shooter.getShootTimer(left), reload)){ - if(roundrobin){ - shooter.getTimer().reset(shooter.getShootTimer(!left), reload/2f); - } + private void update(ShooterTrait shooter, boolean left, float pointerX, float pointerY){ + if(shooter.getInventory().hasAmmo() && shooter.getTimer().get(shooter.getShootTimer(left), reload)){ + if(roundrobin){ + shooter.getTimer().reset(shooter.getShootTimer(!left), reload / 2f); + } - tr.set(pointerX, pointerY).sub(shooter.getX(), shooter.getY()); - if(tr.len() < minPlayerDist) tr.setLength(minPlayerDist); + tr.set(pointerX, pointerY).sub(shooter.getX(), shooter.getY()); + if(tr.len() < minPlayerDist) tr.setLength(minPlayerDist); - float cx = tr.x + shooter.getX(), cy = tr.y + shooter.getY(); + float cx = tr.x + shooter.getX(), cy = tr.y + shooter.getY(); - float ang = tr.angle(); - tr.trns(ang - 90, width * Mathf.sign(left), length); + float ang = tr.angle(); + tr.trns(ang - 90, width * Mathf.sign(left), length); - shoot(shooter, shooter.getX() + tr.x, shooter.getY() + tr.y, Angles.angle(shooter.getX() + tr.x, shooter.getY() + tr.y, cx, cy), left); - } - } + shoot(shooter, shooter.getX() + tr.x, shooter.getY() + tr.y, Angles.angle(shooter.getX() + tr.x, shooter.getY() + tr.y, cx, cy), left); + } + } - public float getRecoil(ShooterTrait player, boolean left){ - return (1f-Mathf.clamp(player.getTimer().getTime(player.getShootTimer(left))/reload))*recoil; - } + public float getRecoil(ShooterTrait player, boolean left){ + return (1f - Mathf.clamp(player.getTimer().getTime(player.getShootTimer(left)) / reload)) * recoil; + } - public float getRecoil() { - return recoil; - } + public float getRecoil(){ + return recoil; + } - public float getReload(){ - return reload; - } + public float getReload(){ + return reload; + } - public void shoot(ShooterTrait p, float x, float y, float angle, boolean left){ - if(Net.client()){ - //call it directly, don't invoke on server - shootDirect(p, x, y, angle, left); - }else{ - if(p instanceof Player){ //players need special weapon handling logic - CallEntity.onPlayerShootWeapon((Player)p, x, y, angle, left); + public void shoot(ShooterTrait p, float x, float y, float angle, boolean left){ + if(Net.client()){ + //call it directly, don't invoke on server + shootDirect(p, x, y, angle, left); + }else{ + if(p instanceof Player){ //players need special weapon handling logic + CallEntity.onPlayerShootWeapon((Player) p, x, y, angle, left); }else{ CallEntity.onGenericShootWeapon(p, x, y, angle, left); } - } + } - p.getInventory().useAmmo(); - } - - public Iterable getAcceptedItems(){ - return ammoMap.orderedKeys(); - } - - public AmmoType getAmmoType(Item item){ - return ammoMap.get(item); - } - - protected void setAmmo(AmmoType... types){ - for(AmmoType type : types){ - ammoMap.put(type.item, type); - } - } - - void bullet(ShooterTrait owner, float x, float y, float angle){ - if(owner == null || !owner.getInventory().hasAmmo()) return; - - tr.trns(angle, 3f); - Bullet.create(owner.getInventory().getAmmo().bullet, - owner, owner.getTeam(), x + tr.x, y + tr.y, angle, (1f-velocityRnd) + Mathf.random(velocityRnd)); - } - - @Remote(targets = Loc.server, called = Loc.both, in = In.entities, unreliable = true) - public static void onPlayerShootWeapon(Player player, float x, float y, float rotation, boolean left){ - if(player == null) return; - //clients do not see their own shoot events: they are simulated completely clientside to prevent laggy visuals - //messing with the firerate or any other stats does not affect the server (take that, script kiddies!) - if(Net.client() && player == Vars.players[0]){ - return; - } - - shootDirect(player, x, y, rotation, left); - } - - @Remote(targets = Loc.server, called = Loc.both, in = In.entities, unreliable = true) - public static void onGenericShootWeapon(ShooterTrait shooter, float x, float y, float rotation, boolean left){ - if(shooter == null) return; - shootDirect(shooter, x, y, rotation, left); + p.getInventory().useAmmo(); } - public static void shootDirect(ShooterTrait shooter, float x, float y, float rotation, boolean left){ - Weapon weapon = shooter.getWeapon(); + public Iterable getAcceptedItems(){ + return ammoMap.orderedKeys(); + } - Angles.shotgun(weapon.shots, weapon.spacing, rotation, f -> weapon.bullet(shooter, x, y, f + Mathf.range(weapon.inaccuracy))); + public AmmoType getAmmoType(Item item){ + return ammoMap.get(item); + } - AmmoType type = shooter.getInventory().getAmmo(); + protected void setAmmo(AmmoType... types){ + for(AmmoType type : types){ + ammoMap.put(type.item, type); + } + } - if(type == null) return; + void bullet(ShooterTrait owner, float x, float y, float angle){ + if(owner == null || !owner.getInventory().hasAmmo()) return; - weapon.tr.trns(rotation + 180f, type.recoil); - - shooter.getVelocity().add(weapon.tr); - - weapon.tr.trns(rotation, 3f); - - Effects.shake(weapon.shake, weapon.shake, x, y); - Effects.effect(weapon.ejectEffect, x, y, rotation * -Mathf.sign(left)); - Effects.effect(type.shootEffect, x + weapon.tr.x, y + weapon.tr.y, rotation, shooter); - Effects.effect(type.smokeEffect, x + weapon.tr.x, y + weapon.tr.y, rotation, shooter); - - //reset timer for remote players - shooter.getTimer().get(shooter.getShootTimer(left), weapon.reload); - } + tr.trns(angle, 3f); + Bullet.create(owner.getInventory().getAmmo().bullet, + owner, owner.getTeam(), x + tr.x, y + tr.y, angle, (1f - velocityRnd) + Mathf.random(velocityRnd)); + } } diff --git a/core/src/io/anuke/mindustry/type/WeatherEvent.java b/core/src/io/anuke/mindustry/type/WeatherEvent.java index 24577af6b9..66871cc439 100644 --- a/core/src/io/anuke/mindustry/type/WeatherEvent.java +++ b/core/src/io/anuke/mindustry/type/WeatherEvent.java @@ -12,22 +12,12 @@ public class WeatherEvent implements Content{ public final String name; public WeatherEvent(String name){ - this.id = lastid ++; + this.id = lastid++; this.name = name; all.add(this); } - @Override - public String getContentTypeName() { - return "weatherevent"; - } - - @Override - public Array getAll() { - return all(); - } - public static Array all(){ return all; } @@ -35,4 +25,14 @@ public class WeatherEvent implements Content{ public static WeatherEvent getByID(int id){ return all.get(id); } + + @Override + public String getContentTypeName(){ + return "weatherevent"; + } + + @Override + public Array getAll(){ + return all(); + } } diff --git a/core/src/io/anuke/mindustry/ui/BorderImage.java b/core/src/io/anuke/mindustry/ui/BorderImage.java index ab6e1c6b86..bc9ed30002 100644 --- a/core/src/io/anuke/mindustry/ui/BorderImage.java +++ b/core/src/io/anuke/mindustry/ui/BorderImage.java @@ -10,34 +10,35 @@ import io.anuke.ucore.scene.ui.Image; import io.anuke.ucore.scene.ui.layout.Unit; public class BorderImage extends Image{ - private float thickness = 3f; - - public BorderImage(){} - - public BorderImage(Texture texture){ - super(texture); - } - - public BorderImage(Texture texture, float thick){ - super(texture); - thickness = thick; - } + private float thickness = 3f; - public BorderImage(TextureRegion region, float thick){ - super(region); - thickness = thick; - } - - @Override - public void draw(Batch batch, float alpha){ - super.draw(batch, alpha); - - float scaleX = getScaleX(); - float scaleY = getScaleY(); - - Draw.color(Palette.accent); - Lines.stroke(Unit.dp.scl(thickness)); - Lines.rect(x + imageX, y + imageY, imageWidth * scaleX, imageHeight * scaleY); - Draw.reset(); - } + public BorderImage(){ + } + + public BorderImage(Texture texture){ + super(texture); + } + + public BorderImage(Texture texture, float thick){ + super(texture); + thickness = thick; + } + + public BorderImage(TextureRegion region, float thick){ + super(region); + thickness = thick; + } + + @Override + public void draw(Batch batch, float alpha){ + super.draw(batch, alpha); + + float scaleX = getScaleX(); + float scaleY = getScaleY(); + + Draw.color(Palette.accent); + Lines.stroke(Unit.dp.scl(thickness)); + Lines.rect(x + imageX, y + imageY, imageWidth * scaleX, imageHeight * scaleY); + Draw.reset(); + } } diff --git a/core/src/io/anuke/mindustry/ui/ContentDisplay.java b/core/src/io/anuke/mindustry/ui/ContentDisplay.java index 916654b314..181e5704f1 100644 --- a/core/src/io/anuke/mindustry/ui/ContentDisplay.java +++ b/core/src/io/anuke/mindustry/ui/ContentDisplay.java @@ -18,16 +18,16 @@ import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.scene.ui.layout.Table; import io.anuke.ucore.util.Bundles; -public class ContentDisplay { +public class ContentDisplay{ public static void displayRecipe(Table table, Recipe recipe){ Block block = recipe.result; table.table(title -> { - int size = 8*6; + int size = 8 * 6; if(block instanceof Turret){ - size = (8 * block.size + 2) * (7 - block.size*2); + size = (8 * block.size + 2) * (7 - block.size * 2); } title.addImage(Draw.region("block-icon-" + block.name)).size(size); @@ -58,7 +58,7 @@ public class ContentDisplay { table.add("$text.category." + cat.name()).color(Palette.accent).fillX(); table.row(); - for (BlockStat stat : map.keys()){ + for(BlockStat stat : map.keys()){ table.table(inset -> { inset.left(); inset.add("[LIGHT_GRAY]" + stat.localized() + ":[] "); @@ -92,13 +92,13 @@ public class ContentDisplay { table.left().defaults().fillX(); - table.add(Bundles.format("text.item.explosiveness", (int)(item.explosiveness * 100))); + table.add(Bundles.format("text.item.explosiveness", (int) (item.explosiveness * 100))); table.row(); - table.add(Bundles.format("text.item.flammability", (int)(item.flammability * 100))); + table.add(Bundles.format("text.item.flammability", (int) (item.flammability * 100))); table.row(); - table.add(Bundles.format("text.item.radioactivity", (int)(item.radioactivity * 100))); + table.add(Bundles.format("text.item.radioactivity", (int) (item.radioactivity * 100))); table.row(); - table.add(Bundles.format("text.item.fluxiness", (int)(item.fluxiness * 100))); + table.add(Bundles.format("text.item.fluxiness", (int) (item.fluxiness * 100))); table.row(); table.add(Bundles.format("text.item.hardness", item.hardness)); table.row(); @@ -127,15 +127,15 @@ public class ContentDisplay { table.left().defaults().fillX(); - table.add(Bundles.format("text.item.explosiveness", (int)(liquid.explosiveness * 100))); + table.add(Bundles.format("text.item.explosiveness", (int) (liquid.explosiveness * 100))); table.row(); - table.add(Bundles.format("text.item.flammability", (int)(liquid.flammability * 100))); + table.add(Bundles.format("text.item.flammability", (int) (liquid.flammability * 100))); table.row(); - table.add(Bundles.format("text.liquid.heatcapacity", (int)(liquid.heatCapacity * 100))); + table.add(Bundles.format("text.liquid.heatcapacity", (int) (liquid.heatCapacity * 100))); table.row(); - table.add(Bundles.format("text.liquid.temperature", (int)(liquid.temperature * 100))); + table.add(Bundles.format("text.liquid.temperature", (int) (liquid.temperature * 100))); table.row(); - table.add(Bundles.format("text.liquid.viscosity", (int)(liquid.viscosity * 100))); + table.add(Bundles.format("text.liquid.viscosity", (int) (liquid.viscosity * 100))); table.row(); } diff --git a/core/src/io/anuke/mindustry/ui/GridImage.java b/core/src/io/anuke/mindustry/ui/GridImage.java index 13d2c175b0..01792f5fbb 100644 --- a/core/src/io/anuke/mindustry/ui/GridImage.java +++ b/core/src/io/anuke/mindustry/ui/GridImage.java @@ -2,41 +2,40 @@ package io.anuke.mindustry.ui; import com.badlogic.gdx.graphics.g2d.Batch; import com.badlogic.gdx.graphics.g2d.TextureRegion; - import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.scene.Element; public class GridImage extends Element{ - private int imageWidth, imageHeight; - - public GridImage(int w, int h){ - this.imageWidth = w; - this.imageHeight = h; - } + private int imageWidth, imageHeight; - public void draw(Batch batch, float alpha){ - TextureRegion blank = Draw.region("white"); - - float xspace = (getWidth() / imageWidth); - float yspace = (getHeight() / imageHeight); - float s = 1f; + public GridImage(int w, int h){ + this.imageWidth = w; + this.imageHeight = h; + } - int minspace = 10; + public void draw(Batch batch, float alpha){ + TextureRegion blank = Draw.region("white"); - int jumpx = (int)(Math.max(minspace, xspace) / xspace); - int jumpy = (int)(Math.max(minspace, yspace)/ yspace); - - for(int x = 0; x <= imageWidth; x += jumpx){ - batch.draw(blank, (int)(getX() + xspace * x - s), getY() - s, 2, getHeight()+ (x == imageWidth ? 1: 0)); - } - - for(int y = 0; y <= imageHeight; y += jumpy){ - batch.draw(blank, getX() - s, (int)(getY() + y * yspace - s), getWidth(), 2); - } - } - - public void setImageSize(int w, int h){ - this.imageWidth = w; - this.imageHeight = h; - } + float xspace = (getWidth() / imageWidth); + float yspace = (getHeight() / imageHeight); + float s = 1f; + + int minspace = 10; + + int jumpx = (int) (Math.max(minspace, xspace) / xspace); + int jumpy = (int) (Math.max(minspace, yspace) / yspace); + + for(int x = 0; x <= imageWidth; x += jumpx){ + batch.draw(blank, (int) (getX() + xspace * x - s), getY() - s, 2, getHeight() + (x == imageWidth ? 1 : 0)); + } + + for(int y = 0; y <= imageHeight; y += jumpy){ + batch.draw(blank, getX() - s, (int) (getY() + y * yspace - s), getWidth(), 2); + } + } + + public void setImageSize(int w, int h){ + this.imageWidth = w; + this.imageHeight = h; + } } diff --git a/core/src/io/anuke/mindustry/ui/IntFormat.java b/core/src/io/anuke/mindustry/ui/IntFormat.java index ad753bd871..6c4431a4b0 100644 --- a/core/src/io/anuke/mindustry/ui/IntFormat.java +++ b/core/src/io/anuke/mindustry/ui/IntFormat.java @@ -2,13 +2,15 @@ package io.anuke.mindustry.ui; import io.anuke.ucore.util.Bundles; -/**A low-garbage way to format bundle strings.*/ -public class IntFormat { +/** + * A low-garbage way to format bundle strings. + */ +public class IntFormat{ private final StringBuilder builder = new StringBuilder(); private final String text; private int lastValue = Integer.MIN_VALUE; - public IntFormat(String text) { + public IntFormat(String text){ this.text = text; } diff --git a/core/src/io/anuke/mindustry/ui/ItemImage.java b/core/src/io/anuke/mindustry/ui/ItemImage.java index 0786016780..5dab85a67d 100644 --- a/core/src/io/anuke/mindustry/ui/ItemImage.java +++ b/core/src/io/anuke/mindustry/ui/ItemImage.java @@ -9,9 +9,9 @@ import io.anuke.ucore.scene.ui.layout.Stack; import io.anuke.ucore.scene.ui.layout.Table; import io.anuke.ucore.scene.ui.layout.Unit; -public class ItemImage extends Stack { +public class ItemImage extends Stack{ - public ItemImage(TextureRegion region, Supplier text) { + public ItemImage(TextureRegion region, Supplier text){ Table t = new Table().left().bottom(); t.label(text).color(Color.DARK_GRAY).padBottom(-22).get().setFontScale(Unit.dp.scl(0.5f)); @@ -22,7 +22,7 @@ public class ItemImage extends Stack { add(t); } - public ItemImage(ItemStack stack) { + public ItemImage(ItemStack stack){ Table t = new Table().left().bottom(); t.add(stack.amount + "").color(Color.DARK_GRAY).padBottom(-22).get().setFontScale(Unit.dp.scl(0.5f)); diff --git a/core/src/io/anuke/mindustry/ui/Links.java b/core/src/io/anuke/mindustry/ui/Links.java index 517083b91f..09ebd501d8 100644 --- a/core/src/io/anuke/mindustry/ui/Links.java +++ b/core/src/io/anuke/mindustry/ui/Links.java @@ -3,18 +3,18 @@ package io.anuke.mindustry.ui; import com.badlogic.gdx.graphics.Color; import io.anuke.ucore.util.Bundles; -public class Links { +public class Links{ private static LinkEntry[] links; private static void createLinks(){ links = new LinkEntry[]{ - new LinkEntry("discord", "https://discord.gg/BKADYds", Color.valueOf("7289da")), - new LinkEntry("trello", "https://trello.com/b/aE2tcUwF", Color.valueOf("026aa7")), - new LinkEntry("wiki", "http://mindustry.wikia.com/wiki/Mindustry_Wiki", Color.valueOf("0f142f")), - new LinkEntry("itch.io", "https://anuke.itch.io/mindustry", Color.valueOf("fa5c5c")), - new LinkEntry("google-play", "https://play.google.com/store/apps/details?id=io.anuke.mindustry", Color.valueOf("689f38")), - new LinkEntry("github", "https://github.com/Anuken/Mindustry/", Color.valueOf("24292e")), - new LinkEntry("dev-builds", "https://github.com/Anuken/Mindustry/wiki", Color.valueOf("fafbfc")) + new LinkEntry("discord", "https://discord.gg/BKADYds", Color.valueOf("7289da")), + new LinkEntry("trello", "https://trello.com/b/aE2tcUwF", Color.valueOf("026aa7")), + new LinkEntry("wiki", "http://mindustry.wikia.com/wiki/Mindustry_Wiki", Color.valueOf("0f142f")), + new LinkEntry("itch.io", "https://anuke.itch.io/mindustry", Color.valueOf("fa5c5c")), + new LinkEntry("google-play", "https://play.google.com/store/apps/details?id=io.anuke.mindustry", Color.valueOf("689f38")), + new LinkEntry("github", "https://github.com/Anuken/Mindustry/", Color.valueOf("24292e")), + new LinkEntry("dev-builds", "https://github.com/Anuken/Mindustry/wiki", Color.valueOf("fafbfc")) }; } @@ -30,10 +30,10 @@ public class Links { public final String name, description, link; public final Color color; - public LinkEntry(String name, String link, Color color) { + public LinkEntry(String name, String link, Color color){ this.name = name; this.color = color; - this.description = Bundles.getNotNull("text.link." + name +".description"); + this.description = Bundles.getNotNull("text.link." + name + ".description"); this.link = link; } } diff --git a/core/src/io/anuke/mindustry/ui/MenuButton.java b/core/src/io/anuke/mindustry/ui/MenuButton.java index 5bce02d2b1..ec3b273d39 100644 --- a/core/src/io/anuke/mindustry/ui/MenuButton.java +++ b/core/src/io/anuke/mindustry/ui/MenuButton.java @@ -7,32 +7,32 @@ import io.anuke.ucore.scene.ui.TextButton; public class MenuButton extends TextButton{ - public MenuButton(String icon, String text, Listenable clicked){ - this(icon, text, null, clicked); - } - - public MenuButton(String icon, String text, String description, Listenable clicked){ - super("default"); - float s = 66f; + public MenuButton(String icon, String text, Listenable clicked){ + this(icon, text, null, clicked); + } - clicked(clicked); + public MenuButton(String icon, String text, String description, Listenable clicked){ + super("default"); + float s = 66f; - clearChildren(); + clicked(clicked); - margin(0); + clearChildren(); - table(t -> { - t.addImage(icon).size(14*3); - t.update(() -> t.setBackground(getClickListener().isOver() || getClickListener().isVisualPressed() ? "button-over" : "button")); - }).size(s - 5, s); + margin(0); + + table(t -> { + t.addImage(icon).size(14 * 3); + t.update(() -> t.setBackground(getClickListener().isOver() || getClickListener().isVisualPressed() ? "button-over" : "button")); + }).size(s - 5, s); - table(t -> { - t.add(text).wrap().growX().get().setAlignment(Align.center, Align.left); - if(description != null){ - t.row(); - t.add(description).color(Color.LIGHT_GRAY); - } - }).padLeft(5).growX(); - } + table(t -> { + t.add(text).wrap().growX().get().setAlignment(Align.center, Align.left); + if(description != null){ + t.row(); + t.add(description).color(Color.LIGHT_GRAY); + } + }).padLeft(5).growX(); + } } diff --git a/core/src/io/anuke/mindustry/ui/Minimap.java b/core/src/io/anuke/mindustry/ui/Minimap.java index 9e469bac1f..1b0f563cf6 100644 --- a/core/src/io/anuke/mindustry/ui/Minimap.java +++ b/core/src/io/anuke/mindustry/ui/Minimap.java @@ -16,7 +16,7 @@ import io.anuke.ucore.scene.ui.layout.Table; import static io.anuke.mindustry.Vars.renderer; import static io.anuke.mindustry.Vars.showFog; -public class Minimap extends Table { +public class Minimap extends Table{ public Minimap(){ super("button"); @@ -26,17 +26,17 @@ public class Minimap extends Table { Image image = new Image(new TextureRegionDrawable(new TextureRegion())){ @Override - public void draw(Batch batch, float parentAlpha) { + public void draw(Batch batch, float parentAlpha){ if(renderer.minimap().getRegion() == null) return; - TextureRegionDrawable draw = (TextureRegionDrawable)getDrawable(); + TextureRegionDrawable draw = (TextureRegionDrawable) getDrawable(); draw.getRegion().setRegion(renderer.minimap().getRegion()); super.draw(batch, parentAlpha); if(renderer.minimap().getTexture() != null){ renderer.minimap().drawEntities(x, y, width, height); } - if(showFog) { + if(showFog){ renderer.fog().getTexture().setFilter(TextureFilter.Nearest, TextureFilter.Nearest); draw.getRegion().setTexture(renderer.fog().getTexture()); @@ -53,7 +53,7 @@ public class Minimap extends Table { }; addListener(new InputListener(){ - public boolean scrolled (InputEvent event, float x, float y, int amount) { + public boolean scrolled(InputEvent event, float x, float y, int amount){ renderer.minimap().zoomBy(amount); return true; } diff --git a/core/src/io/anuke/mindustry/ui/MobileButton.java b/core/src/io/anuke/mindustry/ui/MobileButton.java index c2d7585c80..dfbf1b161c 100644 --- a/core/src/io/anuke/mindustry/ui/MobileButton.java +++ b/core/src/io/anuke/mindustry/ui/MobileButton.java @@ -4,9 +4,9 @@ import com.badlogic.gdx.utils.Align; import io.anuke.ucore.function.Listenable; import io.anuke.ucore.scene.ui.ImageButton; -public class MobileButton extends ImageButton { +public class MobileButton extends ImageButton{ - public MobileButton(String icon, float isize, String text, Listenable listener) { + public MobileButton(String icon, float isize, String text, Listenable listener){ super(icon); resizeImage(isize); clicked(listener); diff --git a/core/src/io/anuke/mindustry/ui/dialogs/AboutDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/AboutDialog.java index 027f595892..ed489a0630 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/AboutDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/AboutDialog.java @@ -16,7 +16,7 @@ import io.anuke.ucore.util.OS; import static io.anuke.mindustry.Vars.ios; import static io.anuke.mindustry.Vars.ui; -public class AboutDialog extends FloatingDialog { +public class AboutDialog extends FloatingDialog{ private static ObjectSet bannedItems = ObjectSet.with("google-play", "itch.io", "dev-builds", "trello"); public AboutDialog(){ @@ -51,21 +51,21 @@ public class AboutDialog extends FloatingDialog { table.table(i -> { i.background("button"); - i.addImage("icon-" + link.name).size(14*3f); - }).size(h-5, h); + i.addImage("icon-" + link.name).size(14 * 3f); + }).size(h - 5, h); table.table(inset -> { - inset.add("[accent]"+link.name.replace("-", " ")).growX().left(); + inset.add("[accent]" + link.name.replace("-", " ")).growX().left(); inset.row(); inset.labelWrap(link.description).width(w - 100f).color(Color.LIGHT_GRAY).growX(); }).padLeft(8); - table.addImageButton("icon-link", 14*3, () -> { + table.addImageButton("icon-link", 14 * 3, () -> { if(!Gdx.net.openURI(link.link)){ ui.showError("$text.linkfail"); Gdx.app.getClipboard().setContents(link.link); } - }).size(h-5, h); + }).size(h - 5, h); in.add(table).size(w, h).padTop(5).row(); } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/AdminsDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/AdminsDialog.java index c95577232f..dbfd1273f6 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/AdminsDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/AdminsDialog.java @@ -7,7 +7,7 @@ import io.anuke.ucore.scene.ui.layout.Table; import static io.anuke.mindustry.Vars.*; -public class AdminsDialog extends FloatingDialog { +public class AdminsDialog extends FloatingDialog{ public AdminsDialog(){ super("$text.server.admins"); @@ -40,7 +40,7 @@ public class AdminsDialog extends FloatingDialog { res.labelWrap("[LIGHT_GRAY]" + info.lastName).width(w - h - 24f); res.add().growX(); - res.addImageButton("icon-cancel", 14*3, () -> { + res.addImageButton("icon-cancel", 14 * 3, () -> { ui.showConfirm("$text.confirm", "$text.confirmunadmin", () -> { netServer.admins.unAdminPlayer(info.id); for(Player player : playerGroup.all()){ diff --git a/core/src/io/anuke/mindustry/ui/dialogs/BansDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/BansDialog.java index d8165c0ae6..012bbe6ee1 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/BansDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/BansDialog.java @@ -6,7 +6,7 @@ import io.anuke.ucore.scene.ui.layout.Table; import static io.anuke.mindustry.Vars.*; -public class BansDialog extends FloatingDialog { +public class BansDialog extends FloatingDialog{ public BansDialog(){ super("$text.server.bans"); @@ -40,7 +40,7 @@ public class BansDialog extends FloatingDialog { res.labelWrap("IP: [LIGHT_GRAY]" + info.lastIP + "\n[]Name: [LIGHT_GRAY]" + info.lastName).width(w - h - 24f); res.add().growX(); - res.addImageButton("icon-cancel", 14*3, () -> { + res.addImageButton("icon-cancel", 14 * 3, () -> { ui.showConfirm("$text.confirm", "$text.confirmunban", () -> { netServer.admins.unbanPlayerID(info.id); setup(); diff --git a/core/src/io/anuke/mindustry/ui/dialogs/ChangelogDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/ChangelogDialog.java index b525117aa9..62c3aa646f 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/ChangelogDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/ChangelogDialog.java @@ -25,7 +25,7 @@ public class ChangelogDialog extends FloatingDialog{ content().add("$text.changelog.loading"); - if(!ios && !OS.isMac) { + if(!ios && !OS.isMac){ Changelogs.getChangelog(result -> { versions = result; Gdx.app.postRunnable(this::setup); diff --git a/core/src/io/anuke/mindustry/ui/dialogs/ColorPickDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/ColorPickDialog.java index 8b8db3f845..5a06b32a73 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/ColorPickDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/ColorPickDialog.java @@ -22,7 +22,7 @@ public class ColorPickDialog extends Dialog{ Table table = new Table(); content().add(table); - for(int i = 0; i < playerColors.length; i ++){ + for(int i = 0; i < playerColors.length; i++){ Color color = playerColors[i]; ImageButton button = table.addImageButton("white", "toggle", 34, () -> { @@ -32,12 +32,12 @@ public class ColorPickDialog extends Dialog{ button.setChecked(players[0].color.equals(color)); button.getStyle().imageUpColor = color; - if(i%4 == 3){ + if(i % 4 == 3){ table.row(); } } - keyDown(key->{ + keyDown(key -> { if(key == Keys.ESCAPE || key == Keys.BACK) hide(); }); diff --git a/core/src/io/anuke/mindustry/ui/dialogs/ContentInfoDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/ContentInfoDialog.java index 2e1a0973d9..fe791c4030 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/ContentInfoDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/ContentInfoDialog.java @@ -4,7 +4,7 @@ import io.anuke.mindustry.game.UnlockableContent; import io.anuke.ucore.scene.ui.ScrollPane; import io.anuke.ucore.scene.ui.layout.Table; -public class ContentInfoDialog extends FloatingDialog { +public class ContentInfoDialog extends FloatingDialog{ public ContentInfoDialog(){ super("$text.info.title"); diff --git a/core/src/io/anuke/mindustry/ui/dialogs/ControlsDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/ControlsDialog.java index 987866200a..c9c2901a6b 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/ControlsDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/ControlsDialog.java @@ -7,24 +7,24 @@ import io.anuke.ucore.scene.ui.Image; import io.anuke.ucore.scene.ui.KeybindDialog; public class ControlsDialog extends KeybindDialog{ - - public ControlsDialog(){ - setDialog(); - - setFillParent(true); - title().setAlignment(Align.center); - getTitleTable().row(); - getTitleTable().add(new Image("white")) - .growX().height(3f).pad(4f).get().setColor(Palette.accent); - } - - @Override - public void addCloseButton(){ - buttons().addImageTextButton("$text.back", "icon-arrow-left", 30f, this::hide).size(230f, 64f); - - keyDown(key->{ - if(key == Keys.ESCAPE || key == Keys.BACK) - hide(); - }); - } + + public ControlsDialog(){ + setDialog(); + + setFillParent(true); + title().setAlignment(Align.center); + getTitleTable().row(); + getTitleTable().add(new Image("white")) + .growX().height(3f).pad(4f).get().setColor(Palette.accent); + } + + @Override + public void addCloseButton(){ + buttons().addImageTextButton("$text.back", "icon-arrow-left", 30f, this::hide).size(230f, 64f); + + keyDown(key -> { + if(key == Keys.ESCAPE || key == Keys.BACK) + hide(); + }); + } } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/DiscordDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/DiscordDialog.java index 19e889b863..e4571b2447 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/DiscordDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/DiscordDialog.java @@ -8,7 +8,7 @@ import io.anuke.ucore.scene.ui.Dialog; import static io.anuke.mindustry.Vars.discordURL; import static io.anuke.mindustry.Vars.ui; -public class DiscordDialog extends Dialog { +public class DiscordDialog extends Dialog{ public DiscordDialog(){ super("", "dialog"); @@ -39,10 +39,10 @@ public class DiscordDialog extends Dialog { buttons().defaults().size(170f, 50); buttons().addButton("$text.back", this::hide); - buttons().addButton("$text.copylink", () ->{ + buttons().addButton("$text.copylink", () -> { Gdx.app.getClipboard().setContents(discordURL); }); - buttons().addButton("$text.openlink", () ->{ + buttons().addButton("$text.openlink", () -> { if(!Gdx.net.openURI(discordURL)){ ui.showError("$text.linkfail"); Gdx.app.getClipboard().setContents(discordURL); diff --git a/core/src/io/anuke/mindustry/ui/dialogs/FileChooser.java b/core/src/io/anuke/mindustry/ui/dialogs/FileChooser.java index 2e48a60898..c61ac8d4bd 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/FileChooser.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/FileChooser.java @@ -23,320 +23,319 @@ import java.util.Arrays; import static io.anuke.mindustry.Vars.gwt; -public class FileChooser extends FloatingDialog { - private Table files; - private FileHandle homeDirectory = gwt ? Gdx.files.internal("") : Gdx.files.absolute(OS.isMac ? OS.getProperty("user.home") + "/Downloads/" : +public class FileChooser extends FloatingDialog{ + public static Predicate pngFilter = file -> file.extension().equalsIgnoreCase("png"); + public static Predicate mapFilter = file -> file.extension().equalsIgnoreCase(Vars.mapExtension); + public static Predicate jpegFilter = file -> file.extension().equalsIgnoreCase("png") || file.extension().equalsIgnoreCase("jpg") || file.extension().equalsIgnoreCase("jpeg"); + public static Predicate defaultFilter = file -> true; + private Table files; + private FileHandle homeDirectory = gwt ? Gdx.files.internal("") : Gdx.files.absolute(OS.isMac ? OS.getProperty("user.home") + "/Downloads/" : Gdx.files.getExternalStoragePath()); - private FileHandle directory = homeDirectory; - private ScrollPane pane; - private TextField navigation, filefield; - private TextButton ok; - private FileHistory stack = new FileHistory(); - private Predicate filter; - private Consumer selectListener; - private boolean open; - - public FileChooser(String title, boolean open, Consumer result){ - this(title, defaultFilter, open, result); - } + private FileHandle directory = homeDirectory; + private ScrollPane pane; + private TextField navigation, filefield; + private TextButton ok; + private FileHistory stack = new FileHistory(); + private Predicate filter; + private Consumer selectListener; + private boolean open; - public FileChooser(String title, Predicate filter, boolean open, Consumer result){ - super(title); - this.open = open; - this.filter = filter; - this.selectListener = result; - } + public FileChooser(String title, boolean open, Consumer result){ + this(title, defaultFilter, open, result); + } - private void setupWidgets(){ - //getCell(content()).maxWidth(UIUtils.portrait() ? Gdx.graphics.getWidth() : Gdx.graphics.getWidth()/Unit.dp.scl(2f)); - content().margin(-10); - - Table content = new Table(); - - filefield = new TextField(); - filefield.setOnlyFontChars(false); - if(!open) Platform.instance.addDialog(filefield); - filefield.setDisabled(open); + public FileChooser(String title, Predicate filter, boolean open, Consumer result){ + super(title); + this.open = open; + this.filter = filter; + this.selectListener = result; + } - ok = new TextButton(open ? "$text.load" : "$text.save"); - - ok.clicked(() -> { - if(ok.isDisabled()) return; - if(selectListener != null) - selectListener.accept(directory.child(filefield.getText())); - hide(); - }); - - filefield.changed(() -> { - ok.setDisabled(filefield.getText().replace(" ", "").isEmpty()); - }); - - filefield.change(); - - TextButton cancel = new TextButton("$text.cancel"); - cancel.clicked(this::hide); + private void setupWidgets(){ + //getCell(content()).maxWidth(UIUtils.portrait() ? Gdx.graphics.getWidth() : Gdx.graphics.getWidth()/Unit.dp.scl(2f)); + content().margin(-10); - navigation = new TextField(""); - navigation.setTouchable(Touchable.disabled); + Table content = new Table(); - files = new Table(); - files.marginRight(10); - files.marginLeft(3); + filefield = new TextField(); + filefield.setOnlyFontChars(false); + if(!open) Platform.instance.addDialog(filefield); + filefield.setDisabled(open); - pane = new ScrollPane(files){ - public float getPrefHeight(){ - return Gdx.graphics.getHeight(); - } - }; - pane.setOverscroll(false, false); - pane.setFadeScrollBars(false); + ok = new TextButton(open ? "$text.load" : "$text.save"); - updateFiles(true); + ok.clicked(() -> { + if(ok.isDisabled()) return; + if(selectListener != null) + selectListener.accept(directory.child(filefield.getText())); + hide(); + }); - Table icontable = new Table(); - - float isize = 14*2; + filefield.changed(() -> { + ok.setDisabled(filefield.getText().replace(" ", "").isEmpty()); + }); - ImageButton up = new ImageButton("icon-folder-parent"); - up.resizeImage(isize); - up.clicked(()->{ - directory = directory.parent(); - updateFiles(true); - }); + filefield.change(); - //Macs are confined to the Downloads/ directory - if(OS.isMac){ - up.setDisabled(true); - } + TextButton cancel = new TextButton("$text.cancel"); + cancel.clicked(this::hide); - ImageButton back = new ImageButton("icon-arrow-left"); - back.resizeImage(isize); - - ImageButton forward = new ImageButton("icon-arrow-right"); - forward.resizeImage(isize); - - forward.clicked(()-> stack.forward()); - - back.clicked(()-> stack.back()); - - ImageButton home = new ImageButton("icon-home"); - home.resizeImage(isize); - home.clicked(()->{ - directory = homeDirectory; - updateFiles(true); - }); - - icontable.defaults().height(50).growX().uniform(); - icontable.add(home); - icontable.add(back); - icontable.add(forward); - icontable.add(up); - - Table fieldcontent = new Table(); - fieldcontent.bottom().left().add(new Label("File Name:")); - fieldcontent.add(filefield).height(40f).fillX().expandX().padLeft(10f); - - Table buttons = new Table(); - buttons.defaults().growX().height(50); - buttons.add(cancel); - buttons.add(ok); - - content.top().left(); - content.add(icontable).expandX().fillX(); - content.row(); + navigation = new TextField(""); + navigation.setTouchable(Touchable.disabled); - content.center().add(pane).width(UIUtils.portrait() ? Gdx.graphics.getWidth()/Unit.dp.scl(1) : Gdx.graphics.getWidth()/Unit.dp.scl(2)).colspan(3).grow(); - content.row(); - - if(!open){ - content.bottom().left().add(fieldcontent).colspan(3).grow().padTop(-2).padBottom(2); - content.row(); - } + files = new Table(); + files.marginRight(10); + files.marginLeft(3); - content.add(buttons).growX(); - - content().add(content); - } - - private void updateFileFieldStatus(){ - if(!open){ - ok.setDisabled(filefield.getText().replace(" ", "").isEmpty()); - }else{ - ok.setDisabled(!directory.child(filefield.getText()).exists() || directory.child(filefield.getText()).isDirectory()); - } - } + pane = new ScrollPane(files){ + public float getPrefHeight(){ + return Gdx.graphics.getHeight(); + } + }; + pane.setOverscroll(false, false); + pane.setFadeScrollBars(false); - private FileHandle[] getFileNames(){ - FileHandle[] handles = directory.list(file -> !file.getName().startsWith(".")); + updateFiles(true); - Arrays.sort(handles, (a, b) ->{ - if(a.isDirectory() && !b.isDirectory()) return -1; - if( !a.isDirectory() && b.isDirectory()) return 1; - return a.name().toUpperCase().compareTo(b.name().toUpperCase()); - }); - return handles; - } + Table icontable = new Table(); - private void updateFiles(boolean push){ - if(push) stack.push(directory); - //if is mac, don't display extra info since you can only ever go to downloads - navigation.setText(OS.isMac ? directory.name() : directory.toString()); - - GlyphLayout layout = Pools.obtain(GlyphLayout.class); - - layout.setText(Core.font, navigation.getText()); - - if(layout.width < navigation.getWidth()){ - navigation.setCursorPosition(0); - }else{ - navigation.setCursorPosition(navigation.getText().length()); - } - - Pools.free(layout); + float isize = 14 * 2; - files.clearChildren(); - files.top().left(); - FileHandle[] names = getFileNames(); + ImageButton up = new ImageButton("icon-folder-parent"); + up.resizeImage(isize); + up.clicked(() -> { + directory = directory.parent(); + updateFiles(true); + }); - //macs are confined to the Downloads/ directory - if(!OS.isMac) { - Image upimage = new Image("icon-folder-parent"); - TextButton upbutton = new TextButton(".." + directory.toString()); - upbutton.clicked(() -> { - directory = directory.parent(); - updateFiles(true); - }); + //Macs are confined to the Downloads/ directory + if(OS.isMac){ + up.setDisabled(true); + } - upbutton.left().add(upimage).padRight(4f).size(14 * 2); - upbutton.getLabel().setAlignment(Align.left); - upbutton.getCells().reverse(); + ImageButton back = new ImageButton("icon-arrow-left"); + back.resizeImage(isize); - files.add(upbutton).align(Align.topLeft).fillX().expandX().height(50).pad(2).colspan(2); - files.row(); - } - - ButtonGroup group = new ButtonGroup<>(); - group.setMinCheckCount(0); + ImageButton forward = new ImageButton("icon-arrow-right"); + forward.resizeImage(isize); - for(FileHandle file : names){ - if( !file.isDirectory() && !filter.test(file)) continue; //skip non-filtered files + forward.clicked(() -> stack.forward()); - String filename = file.name(); + back.clicked(() -> stack.back()); - TextButton button = new TextButton(shorten(filename), "toggle"); - group.add(button); - - button.clicked(()->{ - if( !file.isDirectory()){ - filefield.setText(filename); - updateFileFieldStatus(); - }else{ - directory = directory.child(filename); - updateFiles(true); - } - }); - - filefield.changed(()->{ - button.setChecked(filename.equals(filefield.getText())); - }); - - Image image = new Image(file.isDirectory() ? "icon-folder" : "icon-file-text"); - - button.add(image).padRight(4f).size(14*2f); - button.getCells().reverse(); - files.top().left().add(button).align(Align.topLeft).fillX().expandX() - .height(50).pad(2).padTop(0).padBottom(0).colspan(2); - button.getLabel().setAlignment(Align.left); - files.row(); - } + ImageButton home = new ImageButton("icon-home"); + home.resizeImage(isize); + home.clicked(() -> { + directory = homeDirectory; + updateFiles(true); + }); - pane.setScrollY(0f); - updateFileFieldStatus(); - - if(open) filefield.clearText(); - } + icontable.defaults().height(50).growX().uniform(); + icontable.add(home); + icontable.add(back); + icontable.add(forward); + icontable.add(up); - private String shorten(String string){ - int max = 30; - if(string.length() <= max){ - return string; - }else{ - return string.substring(0, max - 3).concat("..."); - } - } + Table fieldcontent = new Table(); + fieldcontent.bottom().left().add(new Label("File Name:")); + fieldcontent.add(filefield).height(40f).fillX().expandX().padLeft(10f); - @Override - public Dialog show(){ - Timers.runTask(2f, () -> { - content().clear(); - setupWidgets(); - super.show(); - Core.scene.setScrollFocus(pane); - }); - return this; - } + Table buttons = new Table(); + buttons.defaults().growX().height(50); + buttons.add(cancel); + buttons.add(ok); - public void fileSelected(Consumer listener){ - this.selectListener = listener; - } + content.top().left(); + content.add(icontable).expandX().fillX(); + content.row(); - public class FileHistory{ - private Array history = new Array<>(); - private int index; + content.center().add(pane).width(UIUtils.portrait() ? Gdx.graphics.getWidth() / Unit.dp.scl(1) : Gdx.graphics.getWidth() / Unit.dp.scl(2)).colspan(3).grow(); + content.row(); - public FileHistory(){ + if(!open){ + content.bottom().left().add(fieldcontent).colspan(3).grow().padTop(-2).padBottom(2); + content.row(); + } - } + content.add(buttons).growX(); - public void push(FileHandle file){ - if(index != history.size) history.truncate(index); - history.add(file); - index ++; - } + content().add(content); + } - public void back(){ - if( !canBack()) return; - index --; - directory = history.get(index - 1); - updateFiles(false); - } + private void updateFileFieldStatus(){ + if(!open){ + ok.setDisabled(filefield.getText().replace(" ", "").isEmpty()); + }else{ + ok.setDisabled(!directory.child(filefield.getText()).exists() || directory.child(filefield.getText()).isDirectory()); + } + } - public void forward(){ - if( !canForward()) return; - directory = history.get(index); - index ++; - updateFiles(false); - } + private FileHandle[] getFileNames(){ + FileHandle[] handles = directory.list(file -> !file.getName().startsWith(".")); - public boolean canForward(){ - return !(index >= history.size); - } + Arrays.sort(handles, (a, b) -> { + if(a.isDirectory() && !b.isDirectory()) return -1; + if(!a.isDirectory() && b.isDirectory()) return 1; + return a.name().toUpperCase().compareTo(b.name().toUpperCase()); + }); + return handles; + } - public boolean canBack(){ - return !(index == 1) && index > 0; - } + private void updateFiles(boolean push){ + if(push) stack.push(directory); + //if is mac, don't display extra info since you can only ever go to downloads + navigation.setText(OS.isMac ? directory.name() : directory.toString()); - void print(){ + GlyphLayout layout = Pools.obtain(GlyphLayout.class); - System.out.println("\n\n\n\n\n\n"); - int i = 0; - for(FileHandle file : history){ - i ++; - if(index == i){ - System.out.println("[[" + file.toString() + "]]"); - }else{ - System.out.println("--" + file.toString() + "--"); - } - } - } - } + layout.setText(Core.font, navigation.getText()); - public interface FileHandleFilter{ - boolean accept(FileHandle file); - } + if(layout.width < navigation.getWidth()){ + navigation.setCursorPosition(0); + }else{ + navigation.setCursorPosition(navigation.getText().length()); + } - public static Predicate pngFilter = file -> file.extension().equalsIgnoreCase("png"); - public static Predicate mapFilter = file -> file.extension().equalsIgnoreCase(Vars.mapExtension); - public static Predicate jpegFilter = file -> file.extension().equalsIgnoreCase("png") || file.extension().equalsIgnoreCase("jpg") || file.extension().equalsIgnoreCase("jpeg"); - public static Predicate defaultFilter = file -> true; + Pools.free(layout); + + files.clearChildren(); + files.top().left(); + FileHandle[] names = getFileNames(); + + //macs are confined to the Downloads/ directory + if(!OS.isMac){ + Image upimage = new Image("icon-folder-parent"); + TextButton upbutton = new TextButton(".." + directory.toString()); + upbutton.clicked(() -> { + directory = directory.parent(); + updateFiles(true); + }); + + upbutton.left().add(upimage).padRight(4f).size(14 * 2); + upbutton.getLabel().setAlignment(Align.left); + upbutton.getCells().reverse(); + + files.add(upbutton).align(Align.topLeft).fillX().expandX().height(50).pad(2).colspan(2); + files.row(); + } + + ButtonGroup group = new ButtonGroup<>(); + group.setMinCheckCount(0); + + for(FileHandle file : names){ + if(!file.isDirectory() && !filter.test(file)) continue; //skip non-filtered files + + String filename = file.name(); + + TextButton button = new TextButton(shorten(filename), "toggle"); + group.add(button); + + button.clicked(() -> { + if(!file.isDirectory()){ + filefield.setText(filename); + updateFileFieldStatus(); + }else{ + directory = directory.child(filename); + updateFiles(true); + } + }); + + filefield.changed(() -> { + button.setChecked(filename.equals(filefield.getText())); + }); + + Image image = new Image(file.isDirectory() ? "icon-folder" : "icon-file-text"); + + button.add(image).padRight(4f).size(14 * 2f); + button.getCells().reverse(); + files.top().left().add(button).align(Align.topLeft).fillX().expandX() + .height(50).pad(2).padTop(0).padBottom(0).colspan(2); + button.getLabel().setAlignment(Align.left); + files.row(); + } + + pane.setScrollY(0f); + updateFileFieldStatus(); + + if(open) filefield.clearText(); + } + + private String shorten(String string){ + int max = 30; + if(string.length() <= max){ + return string; + }else{ + return string.substring(0, max - 3).concat("..."); + } + } + + @Override + public Dialog show(){ + Timers.runTask(2f, () -> { + content().clear(); + setupWidgets(); + super.show(); + Core.scene.setScrollFocus(pane); + }); + return this; + } + + public void fileSelected(Consumer listener){ + this.selectListener = listener; + } + + public interface FileHandleFilter{ + boolean accept(FileHandle file); + } + + public class FileHistory{ + private Array history = new Array<>(); + private int index; + + public FileHistory(){ + + } + + public void push(FileHandle file){ + if(index != history.size) history.truncate(index); + history.add(file); + index++; + } + + public void back(){ + if(!canBack()) return; + index--; + directory = history.get(index - 1); + updateFiles(false); + } + + public void forward(){ + if(!canForward()) return; + directory = history.get(index); + index++; + updateFiles(false); + } + + public boolean canForward(){ + return !(index >= history.size); + } + + public boolean canBack(){ + return !(index == 1) && index > 0; + } + + void print(){ + + System.out.println("\n\n\n\n\n\n"); + int i = 0; + for(FileHandle file : history){ + i++; + if(index == i){ + System.out.println("[[" + file.toString() + "]]"); + }else{ + System.out.println("--" + file.toString() + "--"); + } + } + } + } } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/FloatingDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/FloatingDialog.java index e894da9207..16ba45bcff 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/FloatingDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/FloatingDialog.java @@ -11,43 +11,43 @@ import io.anuke.ucore.scene.ui.Dialog; import io.anuke.ucore.scene.ui.ScrollPane; public class FloatingDialog extends Dialog{ - - public FloatingDialog(String title){ - super(title, "dialog"); - setFillParent(true); - title().setAlignment(Align.center); - getTitleTable().row(); - getTitleTable().addImage("white", Palette.accent) - .growX().height(3f).pad(4f); - boolean[] done = {false}; + public FloatingDialog(String title){ + super(title, "dialog"); + setFillParent(true); + title().setAlignment(Align.center); + getTitleTable().row(); + getTitleTable().addImage("white", Palette.accent) + .growX().height(3f).pad(4f); - shown(() -> Gdx.app.postRunnable(() -> - forEach(child -> { - if (done[0]) return; + boolean[] done = {false}; - if (child instanceof ScrollPane) { - Core.scene.setScrollFocus(child); - done[0] = true; - } - }))); - } + shown(() -> Gdx.app.postRunnable(() -> + forEach(child -> { + if(done[0]) return; - protected void onResize(Runnable run){ - Events.on(ResizeEvent.class, () -> { - if(isShown()){ - run.run(); - } - }); - } - - @Override - public void addCloseButton(){ - buttons().addImageTextButton("$text.back", "icon-arrow-left", 30f, this::hide).size(230f, 64f); - - keyDown(key -> { - if(key == Keys.ESCAPE || key == Keys.BACK) - hide(); - }); - } + if(child instanceof ScrollPane){ + Core.scene.setScrollFocus(child); + done[0] = true; + } + }))); + } + + protected void onResize(Runnable run){ + Events.on(ResizeEvent.class, () -> { + if(isShown()){ + run.run(); + } + }); + } + + @Override + public void addCloseButton(){ + buttons().addImageTextButton("$text.back", "icon-arrow-left", 30f, this::hide).size(230f, 64f); + + keyDown(key -> { + if(key == Keys.ESCAPE || key == Keys.BACK) + hide(); + }); + } } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/HostDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/HostDialog.java index 2864942d46..077cd57de9 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/HostDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/HostDialog.java @@ -55,7 +55,7 @@ public class HostDialog extends FloatingDialog{ try{ Net.host(Vars.port); player.isAdmin = true; - }catch (IOException e){ + }catch(IOException e){ ui.showError(Bundles.format("text.server.error", Strings.parseException(e, false))); } ui.loadfrag.hide(); diff --git a/core/src/io/anuke/mindustry/ui/dialogs/JoinDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/JoinDialog.java index 7777935928..8d48aa37ca 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/JoinDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/JoinDialog.java @@ -25,7 +25,7 @@ import io.anuke.ucore.util.Strings; import static io.anuke.mindustry.Vars.*; -public class JoinDialog extends FloatingDialog { +public class JoinDialog extends FloatingDialog{ Array servers = new Array<>(); Dialog add; Server renaming; @@ -49,7 +49,7 @@ public class JoinDialog extends FloatingDialog { add = new FloatingDialog("$text.joingame.title"); add.content().add("$text.joingame.ip").padRight(5f).left(); - Platform.instance.addDialog(add.content().addField(Settings.getString("ip"), text ->{ + Platform.instance.addDialog(add.content().addField(Settings.getString("ip"), text -> { Settings.putString("ip", text); Settings.save(); }).size(320f, 54f).get(), 100); @@ -58,7 +58,7 @@ public class JoinDialog extends FloatingDialog { add.buttons().defaults().size(140f, 60f).pad(4f); add.buttons().addButton("$text.cancel", add::hide); add.buttons().addButton("$text.ok", () -> { - if(renaming == null) { + if(renaming == null){ Server server = new Server(Settings.getString("ip"), Strings.parseInt(Settings.getString("port"))); servers.add(server); saveServers(); @@ -88,11 +88,11 @@ public class JoinDialog extends FloatingDialog { void setupRemote(){ remote.clear(); - for (Server server : servers) { + for(Server server : servers){ //why are java lambdas this bad TextButton[] buttons = {null}; - TextButton button = buttons[0] = remote.addButton("[accent]"+server.ip, "clear", () -> { + TextButton button = buttons[0] = remote.addButton("[accent]" + server.ip, "clear", () -> { if(!buttons[0].childrenPressed()) connect(server.ip, Vars.port); }).width(targetWidth()).height(150f).pad(4f).get(); @@ -104,16 +104,16 @@ public class JoinDialog extends FloatingDialog { inner.add(button.getLabel()).growX(); - inner.addImageButton("icon-loading", "empty", 16*2, () -> { + inner.addImageButton("icon-loading", "empty", 16 * 2, () -> { refreshServer(server); }).margin(3f).padTop(6f).top().right(); - inner.addImageButton("icon-pencil", "empty", 16*2, () -> { + inner.addImageButton("icon-pencil", "empty", 16 * 2, () -> { renaming = server; add.show(); }).margin(3f).padTop(6f).top().right(); - inner.addImageButton("icon-trash-16", "empty", 16*2, () -> { + inner.addImageButton("icon-trash-16", "empty", 16 * 2, () -> { ui.showConfirm("$text.confirm", "$text.server.delete", () -> { servers.removeValue(server, true); saveServers(); @@ -124,7 +124,8 @@ public class JoinDialog extends FloatingDialog { button.row(); - server.content = button.table(t -> {}).grow().get(); + server.content = button.table(t -> { + }).grow().get(); remote.row(); } @@ -143,7 +144,7 @@ public class JoinDialog extends FloatingDialog { Net.pingHost(server.ip, server.port, host -> { String versionString; - if(host.version == -1) { + if(host.version == -1){ versionString = Bundles.format("text.server.version", Bundles.get("text.server.custombuild")); }else if(host.version == 0){ versionString = Bundles.get("text.server.outdated"); @@ -179,7 +180,7 @@ public class JoinDialog extends FloatingDialog { } void refreshLocal(){ - if(!Vars.gwt) { + if(!Vars.gwt){ local.clear(); local.background("button"); local.label(() -> "[accent]" + Bundles.get("text.hosts.discovering") + Strings.animated(4, 10f, ".")).pad(10f); @@ -227,7 +228,7 @@ public class JoinDialog extends FloatingDialog { content().row(); content().add(pane).width(w + 34).pad(0); content().row(); - content().addCenteredImageTextButton("$text.server.add", "icon-add", "clear", 14*3, () -> { + content().addCenteredImageTextButton("$text.server.add", "icon-add", "clear", 14 * 3, () -> { renaming = null; add.show(); }).marginLeft(6).width(w).height(80f).update(button -> { @@ -238,7 +239,7 @@ public class JoinDialog extends FloatingDialog { pad = 6; } - Cell cell = ((Table)pane.getParent()).getCell(button); + Cell cell = ((Table) pane.getParent()).getCell(button); if(!MathUtils.isEqual(cell.getMinWidth(), pw)){ cell.width(pw); @@ -256,10 +257,10 @@ public class JoinDialog extends FloatingDialog { if(array.size == 0){ local.add("$text.hosts.none").pad(10f); local.add().growX(); - local.addImageButton("icon-loading", 16*2f, this::refreshLocal).pad(-10f).padLeft(0).padTop(-6).size(70f, 74f); - }else { - for (Host a : array) { - TextButton button = local.addButton("[accent]"+a.name, "clear", () -> { + local.addImageButton("icon-loading", 16 * 2f, this::refreshLocal).pad(-10f).padLeft(0).padTop(-6).size(70f, 74f); + }else{ + for(Host a : array){ + TextButton button = local.addButton("[accent]" + a.name, "clear", () -> { connect(a.address, Vars.port); }).width(w).height(80f).pad(4f).get(); button.left(); @@ -289,18 +290,18 @@ public class JoinDialog extends FloatingDialog { Net.connect(ip, port); hide(); add.hide(); - }catch (Exception e) { + }catch(Exception e){ Throwable t = e; while(t.getCause() != null){ t = t.getCause(); } //TODO localize String error = t.getMessage() == null ? "" : t.getMessage().toLowerCase(); - if(error.contains("connection refused")) { + if(error.contains("connection refused")){ error = "connection refused"; }else if(error.contains("port out of range")){ error = "invalid port!"; - }else if(error.contains("invalid argument")) { + }else if(error.contains("invalid argument")){ error = "invalid IP or port!"; }else if(t.getClass().toString().toLowerCase().contains("sockettimeout")){ error = "timed out!\nmake sure the host has port forwarding set up,\nand that the address is correct!"; @@ -340,6 +341,7 @@ public class JoinDialog extends FloatingDialog { this.port = port; } - Server(){} + Server(){ + } } } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/LevelDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/LevelDialog.java index 457856f17c..4df4316cd7 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/LevelDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/LevelDialog.java @@ -24,142 +24,142 @@ import io.anuke.ucore.util.Mathf; import static io.anuke.mindustry.Vars.*; public class LevelDialog extends FloatingDialog{ - - public LevelDialog(){ - super("$text.level.select"); - addCloseButton(); - shown(this::setup); - onResize(this::setup); - } - - void setup(){ - content().clear(); + public LevelDialog(){ + super("$text.level.select"); + addCloseButton(); + shown(this::setup); - Table maps = new Table(); - maps.marginRight(14); - ScrollPane pane = new ScrollPane(maps, "clear-black"); - pane.setFadeScrollBars(false); - - int maxwidth = (Gdx.graphics.getHeight() > Gdx.graphics.getHeight() ? 2 : 4); - - Table selmode = new Table(); - ButtonGroup group = new ButtonGroup<>(); - selmode.add("$text.level.mode").padRight(15f); - - for(GameMode mode : GameMode.values()){ - TextButton[] b = {null}; - b[0] = Elements.newButton("$mode." + mode.name() + ".name", "toggle", () -> state.mode = mode); - b[0].update(() -> b[0].setChecked(state.mode == mode)); - group.add(b[0]); - selmode.add(b[0]).size(130f, 54f); - } - selmode.addButton("?", this::displayGameModeHelp).size(50f, 54f).padLeft(18f); - - content().add(selmode); - content().row(); + onResize(this::setup); + } - Difficulty[] ds = Difficulty.values(); + void setup(){ + content().clear(); - float s = 50f; + Table maps = new Table(); + maps.marginRight(14); + ScrollPane pane = new ScrollPane(maps, "clear-black"); + pane.setFadeScrollBars(false); - Table sdif = new Table(); + int maxwidth = (Gdx.graphics.getHeight() > Gdx.graphics.getHeight() ? 2 : 4); - sdif.add("$setting.difficulty.name").padRight(15f); + Table selmode = new Table(); + ButtonGroup group = new ButtonGroup<>(); + selmode.add("$text.level.mode").padRight(15f); - sdif.defaults().height(s+4); - sdif.addImageButton("icon-arrow-left", 10*3, () -> { - state.difficulty = (ds[Mathf.mod(state.difficulty.ordinal() - 1, ds.length)]); - }).width(s); + for(GameMode mode : GameMode.values()){ + TextButton[] b = {null}; + b[0] = Elements.newButton("$mode." + mode.name() + ".name", "toggle", () -> state.mode = mode); + b[0].update(() -> b[0].setChecked(state.mode == mode)); + group.add(b[0]); + selmode.add(b[0]).size(130f, 54f); + } + selmode.addButton("?", this::displayGameModeHelp).size(50f, 54f).padLeft(18f); - sdif.addButton("", () -> { + content().add(selmode); + content().row(); - }).update(t -> { - t.setText(state.difficulty.toString()); - t.setTouchable(Touchable.disabled); - }).width(180f); + Difficulty[] ds = Difficulty.values(); - sdif.addImageButton("icon-arrow-right", 10*3, () -> { - state.difficulty = (ds[Mathf.mod(state.difficulty.ordinal() + 1, ds.length)]); - }).width(s); + float s = 50f; - content().add(sdif); - content().row(); + Table sdif = new Table(); - float images = 146f; + sdif.add("$setting.difficulty.name").padRight(15f); - int i = 0; - for(Map map : world.maps().all()){ + sdif.defaults().height(s + 4); + sdif.addImageButton("icon-arrow-left", 10 * 3, () -> { + state.difficulty = (ds[Mathf.mod(state.difficulty.ordinal() - 1, ds.length)]); + }).width(s); - if(i % maxwidth == 0){ - maps.row(); - } - - ImageButton image = new ImageButton(new TextureRegion(map.texture), "clear"); - image.margin(5); - image.getImageCell().size(images); - image.top(); - image.row(); - image.add("[accent]" + Bundles.get("map."+map.name+".name", map.name)).pad(3f).growX().wrap().get().setAlignment(Align.center, Align.center); - image.row(); - image.label((() -> Bundles.format("text.level.highscore", Settings.getInt("hiscore" + map.name, 0)))).pad(3f); + sdif.addButton("", () -> { - BorderImage border = new BorderImage(map.texture, 3f); - image.replaceImage(border); + }).update(t -> { + t.setText(state.difficulty.toString()); + t.setTouchable(Touchable.disabled); + }).width(180f); - image.clicked(() -> { - hide(); - control.playMap(map); - }); - - maps.add(image).width(170).fillY().top().pad(4f); - - i ++; - } + sdif.addImageButton("icon-arrow-right", 10 * 3, () -> { + state.difficulty = (ds[Mathf.mod(state.difficulty.ordinal() + 1, ds.length)]); + }).width(s); - ImageButton genb = maps.addImageButton("icon-editor", "clear", 16*3, () -> { - hide(); + content().add(sdif); + content().row(); - ui.loadfrag.show(); + float images = 146f; - Timers.run(5f, () -> { - Cursors.restoreCursor(); - threads.run(() -> { - world.loadProceduralMap(); - logic.play(); - Gdx.app.postRunnable(ui.loadfrag::hide); - }); - }); - }).width(170).fillY().pad(4f).get(); + int i = 0; + for(Map map : world.maps().all()){ - genb.top(); - genb.margin(5); - genb.clearChildren(); - genb.add(new BorderImage(Draw.region("icon-generated"), 3f)).size(images); - genb.row(); - genb.add("$text.map.random").growX().wrap().pad(3f).get().setAlignment(Align.center, Align.center); - genb.row(); - genb.add("").pad(3f); - - content().add(pane).uniformX(); - } + if(i % maxwidth == 0){ + maps.row(); + } - private void displayGameModeHelp() { - FloatingDialog d = new FloatingDialog(Bundles.get("mode.text.help.title")); - d.setFillParent(false); - Table table = new Table(); - table.defaults().pad(1f); - ScrollPane pane = new ScrollPane(table, "clear"); - pane.setFadeScrollBars(false); - table.row(); - for(GameMode mode : GameMode.values()){ - table.labelWrap("[accent]" + mode.toString() + ":[] [lightgray]" + mode.description()).width(600f); - table.row(); - } + ImageButton image = new ImageButton(new TextureRegion(map.texture), "clear"); + image.margin(5); + image.getImageCell().size(images); + image.top(); + image.row(); + image.add("[accent]" + Bundles.get("map." + map.name + ".name", map.name)).pad(3f).growX().wrap().get().setAlignment(Align.center, Align.center); + image.row(); + image.label((() -> Bundles.format("text.level.highscore", Settings.getInt("hiscore" + map.name, 0)))).pad(3f); - d.content().add(pane); - d.buttons().addButton("$text.ok", d::hide).size(110, 50).pad(10f); - d.show(); - } + BorderImage border = new BorderImage(map.texture, 3f); + image.replaceImage(border); + + image.clicked(() -> { + hide(); + control.playMap(map); + }); + + maps.add(image).width(170).fillY().top().pad(4f); + + i++; + } + + ImageButton genb = maps.addImageButton("icon-editor", "clear", 16 * 3, () -> { + hide(); + + ui.loadfrag.show(); + + Timers.run(5f, () -> { + Cursors.restoreCursor(); + threads.run(() -> { + world.loadProceduralMap(); + logic.play(); + Gdx.app.postRunnable(ui.loadfrag::hide); + }); + }); + }).width(170).fillY().pad(4f).get(); + + genb.top(); + genb.margin(5); + genb.clearChildren(); + genb.add(new BorderImage(Draw.region("icon-generated"), 3f)).size(images); + genb.row(); + genb.add("$text.map.random").growX().wrap().pad(3f).get().setAlignment(Align.center, Align.center); + genb.row(); + genb.add("").pad(3f); + + content().add(pane).uniformX(); + } + + private void displayGameModeHelp(){ + FloatingDialog d = new FloatingDialog(Bundles.get("mode.text.help.title")); + d.setFillParent(false); + Table table = new Table(); + table.defaults().pad(1f); + ScrollPane pane = new ScrollPane(table, "clear"); + pane.setFadeScrollBars(false); + table.row(); + for(GameMode mode : GameMode.values()){ + table.labelWrap("[accent]" + mode.toString() + ":[] [lightgray]" + mode.description()).width(600f); + table.row(); + } + + d.content().add(pane); + d.buttons().addButton("$text.ok", d::hide).size(110, 50).pad(10f); + d.show(); + } } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/LoadDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/LoadDialog.java index 7285338725..7730850513 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/LoadDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/LoadDialog.java @@ -6,11 +6,9 @@ import com.badlogic.gdx.utils.Array; import io.anuke.mindustry.Vars; import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.core.Platform; -import io.anuke.mindustry.game.EventType.ResizeEvent; import io.anuke.mindustry.io.SaveIO; import io.anuke.mindustry.io.Saves.SaveSlot; import io.anuke.ucore.core.Core; -import io.anuke.ucore.core.Events; import io.anuke.ucore.core.Timers; import io.anuke.ucore.scene.ui.ScrollPane; import io.anuke.ucore.scene.ui.TextButton; @@ -24,150 +22,150 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.*; public class LoadDialog extends FloatingDialog{ - ScrollPane pane; - Table slots; + ScrollPane pane; + Table slots; - public LoadDialog() { - this("$text.loadgame"); - } + public LoadDialog(){ + this("$text.loadgame"); + } - public LoadDialog(String title) { - super(title); - setup(); + public LoadDialog(String title){ + super(title); + setup(); - shown(() -> { - setup(); - Timers.runTask(2f, () -> Core.scene.setScrollFocus(pane)); - }); + shown(() -> { + setup(); + Timers.runTask(2f, () -> Core.scene.setScrollFocus(pane)); + }); - addCloseButton(); - } + addCloseButton(); + } - protected void setup(){ - content().clear(); + protected void setup(){ + content().clear(); - slots = new Table(); - pane = new ScrollPane(slots, "clear-black"); - pane.setFadeScrollBars(false); - pane.setScrollingDisabled(true, false); + slots = new Table(); + pane = new ScrollPane(slots, "clear-black"); + pane.setFadeScrollBars(false); + pane.setScrollingDisabled(true, false); - slots.marginRight(24); + slots.marginRight(24); - Timers.runTask(2f, () -> Core.scene.setScrollFocus(pane)); + Timers.runTask(2f, () -> Core.scene.setScrollFocus(pane)); - Array array = control.getSaves().getSaveSlots(); + Array array = control.getSaves().getSaveSlots(); - for(SaveSlot slot : array){ + for(SaveSlot slot : array){ - TextButton button = new TextButton("[accent]" + slot.getName(), "clear"); - button.getLabelCell().growX().left(); - button.getLabelCell().padBottom(8f); - button.getLabelCell().top().left().growX(); + TextButton button = new TextButton("[accent]" + slot.getName(), "clear"); + button.getLabelCell().growX().left(); + button.getLabelCell().padBottom(8f); + button.getLabelCell().top().left().growX(); - button.defaults().left(); + button.defaults().left(); - button.table(t -> { - t.right(); + button.table(t -> { + t.right(); - t.addImageButton("icon-floppy", "emptytoggle", 14*3, () -> { - slot.setAutosave(!slot.isAutosave()); - }).checked(slot.isAutosave()).right(); + t.addImageButton("icon-floppy", "emptytoggle", 14 * 3, () -> { + slot.setAutosave(!slot.isAutosave()); + }).checked(slot.isAutosave()).right(); - t.addImageButton("icon-trash", "empty", 14*3, () -> { - ui.showConfirm("$text.confirm", "$text.save.delete.confirm", () -> { - slot.delete(); - setup(); - }); - }).size(14*3).right(); + t.addImageButton("icon-trash", "empty", 14 * 3, () -> { + ui.showConfirm("$text.confirm", "$text.save.delete.confirm", () -> { + slot.delete(); + setup(); + }); + }).size(14 * 3).right(); - t.addImageButton("icon-pencil-small", "empty", 14*3, () -> { - ui.showTextInput("$text.save.rename", "$text.save.rename.text", slot.getName(), text -> { - slot.setName(text); - setup(); - }); - }).size(14*3).right(); + t.addImageButton("icon-pencil-small", "empty", 14 * 3, () -> { + ui.showTextInput("$text.save.rename", "$text.save.rename.text", slot.getName(), text -> { + slot.setName(text); + setup(); + }); + }).size(14 * 3).right(); - if(!gwt) { - t.addImageButton("icon-save", "empty", 14 * 3, () -> { - if(!ios) { - Platform.instance.showFileChooser(Bundles.get("text.save.export"), "Mindustry Save", file -> { - try { - slot.exportFile(file); - setup(); - } catch (IOException e) { - ui.showError(Bundles.format("text.save.export.fail", Strings.parseException(e, false))); - } - }, false, saveExtension); + if(!gwt){ + t.addImageButton("icon-save", "empty", 14 * 3, () -> { + if(!ios){ + Platform.instance.showFileChooser(Bundles.get("text.save.export"), "Mindustry Save", file -> { + try{ + slot.exportFile(file); + setup(); + }catch(IOException e){ + ui.showError(Bundles.format("text.save.export.fail", Strings.parseException(e, false))); + } + }, false, saveExtension); }else{ - try { + try{ FileHandle file = Gdx.files.local("save-" + slot.getName() + "." + Vars.saveExtension); slot.exportFile(file); Platform.instance.shareFile(file); - }catch (Exception e){ + }catch(Exception e){ ui.showError(Bundles.format("text.save.export.fail", Strings.parseException(e, false))); } } - }).size(14 * 3).right(); - } + }).size(14 * 3).right(); + } - }).padRight(-10).growX(); + }).padRight(-10).growX(); - String color = "[lightgray]"; + String color = "[lightgray]"; - button.defaults().padBottom(3); - button.row(); - button.add(Bundles.format("text.save.map", color+ (slot.getMap() == null ? "Unknown" : slot.getMap().meta.name()))); - button.row(); - button.add(Bundles.get("text.level.mode") + " " +color+ slot.getMode()); - button.row(); - button.add(Bundles.format("text.save.wave", color+slot.getWave())); - button.row(); - button.add(Bundles.format("text.save.difficulty", color+slot.getDifficulty())); - button.row(); - button.label(() -> Bundles.format("text.save.autosave", color + Bundles.get(slot.isAutosave() ? "text.on" : "text.off"))); - button.row(); - button.add(Bundles.format("text.save.date", color+slot.getDate())).colspan(2).padTop(5).right(); - button.row(); - modifyButton(button, slot); + button.defaults().padBottom(3); + button.row(); + button.add(Bundles.format("text.save.map", color + (slot.getMap() == null ? "Unknown" : slot.getMap().meta.name()))); + button.row(); + button.add(Bundles.get("text.level.mode") + " " + color + slot.getMode()); + button.row(); + button.add(Bundles.format("text.save.wave", color + slot.getWave())); + button.row(); + button.add(Bundles.format("text.save.difficulty", color + slot.getDifficulty())); + button.row(); + button.label(() -> Bundles.format("text.save.autosave", color + Bundles.get(slot.isAutosave() ? "text.on" : "text.off"))); + button.row(); + button.add(Bundles.format("text.save.date", color + slot.getDate())).colspan(2).padTop(5).right(); + button.row(); + modifyButton(button, slot); - slots.add(button).uniformX().fillX().pad(4).padRight(-4).margin(10f).marginLeft(20f).marginRight(20f); - slots.row(); - } + slots.add(button).uniformX().fillX().pad(4).padRight(-4).margin(10f).marginLeft(20f).marginRight(20f); + slots.row(); + } - content().add(pane); + content().add(pane); - addSetup(); - } + addSetup(); + } - public void addSetup(){ - if(control.getSaves().getSaveSlots().size == 0) { + public void addSetup(){ + if(control.getSaves().getSaveSlots().size == 0){ - slots.row(); - slots.addButton("$text.save.none", "clear", () -> { - }).disabled(true).fillX().margin(20f).minWidth(340f).height(80f).pad(4f); - } + slots.row(); + slots.addButton("$text.save.none", "clear", () -> { + }).disabled(true).fillX().margin(20f).minWidth(340f).height(80f).pad(4f); + } - slots.row(); + slots.row(); - if(gwt || ios) return; + if(gwt || ios) return; - slots.addImageTextButton("$text.save.import", "icon-add", "clear", 14*3, () -> { - Platform.instance.showFileChooser(Bundles.get("text.save.import"), "Mindustry Save", file -> { - if(SaveIO.isSaveValid(file)){ - try{ - control.getSaves().importSave(file); - setup(); - }catch (IOException e){ - ui.showError(Bundles.format("text.save.import.fail", Strings.parseException(e, false))); - } - }else{ - ui.showError("$text.save.import.invalid"); - } - }, true, saveExtension); - }).fillX().margin(10f).minWidth(300f).height(70f).pad(4f).padRight(-4); - } + slots.addImageTextButton("$text.save.import", "icon-add", "clear", 14 * 3, () -> { + Platform.instance.showFileChooser(Bundles.get("text.save.import"), "Mindustry Save", file -> { + if(SaveIO.isSaveValid(file)){ + try{ + control.getSaves().importSave(file); + setup(); + }catch(IOException e){ + ui.showError(Bundles.format("text.save.import.fail", Strings.parseException(e, false))); + } + }else{ + ui.showError("$text.save.import.invalid"); + } + }, true, saveExtension); + }).fillX().margin(10f).minWidth(300f).height(70f).pad(4f).padRight(-4); + } - public void runLoadSave(SaveSlot slot){ + public void runLoadSave(SaveSlot slot){ ui.loadfrag.show(); Timers.runTask(3f, () -> { @@ -187,11 +185,11 @@ public class LoadDialog extends FloatingDialog{ }); } - public void modifyButton(TextButton button, SaveSlot slot){ - button.clicked(() -> { - if(!button.childrenPressed()){ - runLoadSave(slot); - } - }); - } + public void modifyButton(TextButton button, SaveSlot slot){ + button.clicked(() -> { + if(!button.childrenPressed()){ + runLoadSave(slot); + } + }); + } } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/LocalPlayerDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/LocalPlayerDialog.java index 29927af5b4..7ef5786ee3 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/LocalPlayerDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/LocalPlayerDialog.java @@ -1,16 +1,18 @@ package io.anuke.mindustry.ui.dialogs; import com.badlogic.gdx.utils.Scaling; -import static io.anuke.mindustry.Vars.*; import io.anuke.mindustry.entities.Player; import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.scene.ui.Image; import io.anuke.ucore.scene.ui.layout.Stack; import io.anuke.ucore.scene.ui.layout.Table; +import static io.anuke.mindustry.Vars.control; +import static io.anuke.mindustry.Vars.players; + public class LocalPlayerDialog extends FloatingDialog{ - public LocalPlayerDialog() { + public LocalPlayerDialog(){ super("$text.addplayers"); addCloseButton(); @@ -22,7 +24,7 @@ public class LocalPlayerDialog extends FloatingDialog{ content().clear(); - if(players.length > 1) { + if(players.length > 1){ content().addImageButton("icon-cancel", 14 * 2, () -> { control.removePlayer(); rebuild(); @@ -49,7 +51,7 @@ public class LocalPlayerDialog extends FloatingDialog{ content().add(table).pad(5); } - if(players.length < 4) { + if(players.length < 4){ content().addImageButton("icon-add", 14 * 2, () -> { control.addPlayer(players.length); rebuild(); diff --git a/core/src/io/anuke/mindustry/ui/dialogs/MapsDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/MapsDialog.java index f1abf8086c..b60603fa82 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/MapsDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/MapsDialog.java @@ -22,13 +22,13 @@ import java.io.DataInputStream; import static io.anuke.mindustry.Vars.*; -public class MapsDialog extends FloatingDialog { +public class MapsDialog extends FloatingDialog{ - public MapsDialog() { + public MapsDialog(){ super("$text.maps"); addCloseButton(); - buttons().addImageTextButton("$text.editor.importmap", "icon-add", 14*2, () -> { + buttons().addImageTextButton("$text.editor.importmap", "icon-add", 14 * 2, () -> { Platform.instance.showFileChooser("$text.editor.importmap", "Map File", file -> { try{ DataInputStream stream = new DataInputStream(file.read()); @@ -50,7 +50,7 @@ public class MapsDialog extends FloatingDialog { setup(); } - }catch (Exception e){ + }catch(Exception e){ ui.showError(Bundles.format("text.editor.errorimageload", Strings.parseException(e, false))); Log.err(e); } @@ -86,11 +86,11 @@ public class MapsDialog extends FloatingDialog { button.row(); button.addImage("white").growX().pad(4).color(Color.GRAY); button.row(); - ((Image)button.stack(new Image(map.texture), new BorderImage(map.texture)).size(mapsize-20f).get().getChildren().first()).setScaling(Scaling.fit); + ((Image) button.stack(new Image(map.texture), new BorderImage(map.texture)).size(mapsize - 20f).get().getChildren().first()).setScaling(Scaling.fit); button.row(); button.add(map.custom ? "$text.custom" : "$text.builtin").color(Color.GRAY).padTop(3); - i ++; + i++; } if(world.maps().all().size == 0){ @@ -139,13 +139,13 @@ public class MapsDialog extends FloatingDialog { table.row(); - table.addImageTextButton("$text.editor.openin", "icon-load-map", "clear", 16*2, () -> { + table.addImageTextButton("$text.editor.openin", "icon-load-map", "clear", 16 * 2, () -> { Vars.ui.editor.beginEditMap(map.stream.get()); dialog.hide(); hide(); }).fillX().height(50f).marginLeft(6); - table.addImageTextButton("$text.delete", "icon-trash-16", "clear", 16*2, () -> { + table.addImageTextButton("$text.delete", "icon-trash-16", "clear", 16 * 2, () -> { ui.showConfirm("$text.confirm", Bundles.format("text.map.delete", map.name), () -> { world.maps().removeMap(map); dialog.hide(); diff --git a/core/src/io/anuke/mindustry/ui/dialogs/PausedDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/PausedDialog.java index 5caf0f519b..d2be114720 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/PausedDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/PausedDialog.java @@ -11,136 +11,136 @@ import io.anuke.ucore.util.Bundles; import static io.anuke.mindustry.Vars.*; public class PausedDialog extends FloatingDialog{ - private SaveDialog save = new SaveDialog(); - private LoadDialog load = new LoadDialog(); - public boolean wasPaused = false; + public boolean wasPaused = false; + private SaveDialog save = new SaveDialog(); + private LoadDialog load = new LoadDialog(); - public PausedDialog() { - super("$text.menu"); - setup(); - } + public PausedDialog(){ + super("$text.menu"); + setup(); + } - void setup(){ - update(() -> { - if(state.is(State.menu) && isShown()){ - hide(); - } - }); + void setup(){ + update(() -> { + if(state.is(State.menu) && isShown()){ + hide(); + } + }); - shown(() -> { - wasPaused = state.is(State.paused); - if(!Net.active()) state.set(State.paused); - }); - - if(!mobile){ - content().defaults().width(220).height(50); + shown(() -> { + wasPaused = state.is(State.paused); + if(!Net.active()) state.set(State.paused); + }); - content().addButton("$text.back", () -> { - hide(); - if((!wasPaused || Net.active()) && !state.is(State.menu)) - state.set(State.playing); - }); + if(!mobile){ + content().defaults().width(220).height(50); - content().row(); - content().addButton("$text.settings", ui.settings::show); + content().addButton("$text.back", () -> { + hide(); + if((!wasPaused || Net.active()) && !state.is(State.menu)) + state.set(State.playing); + }); - content().row(); - content().addButton("$text.savegame", () -> { - save.show(); - }); + content().row(); + content().addButton("$text.settings", ui.settings::show); - content().row(); - content().addButton("$text.loadgame", () -> { - load.show(); - }).disabled(b -> Net.active()); + content().row(); + content().addButton("$text.savegame", () -> { + save.show(); + }); - //Local multiplayer is currently functional, but disabled. + content().row(); + content().addButton("$text.loadgame", () -> { + load.show(); + }).disabled(b -> Net.active()); + + //Local multiplayer is currently functional, but disabled. /* content().row(); content().addButton("$text.addplayers", () -> { ui.localplayers.show(); }).disabled(b -> Net.active());*/ - content().row(); + content().row(); - content().addButton("$text.hostserver", () -> { - if(!gwt){ - ui.host.show(); - }else{ - ui.showInfo("$text.host.web"); - } - }).disabled(b -> Net.active()); + content().addButton("$text.hostserver", () -> { + if(!gwt){ + ui.host.show(); + }else{ + ui.showInfo("$text.host.web"); + } + }).disabled(b -> Net.active()); content().row(); - content().addButton("$text.quit", () -> { + content().addButton("$text.quit", () -> { ui.showConfirm("$text.confirm", "$text.quit.confirm", () -> { - if(Net.client()) netClient.disconnectQuietly(); - runExitSave(); - hide(); - }); - }); + if(Net.client()) netClient.disconnectQuietly(); + runExitSave(); + hide(); + }); + }); - }else{ - build.begin(content()); - - content().defaults().size(120f).pad(5); - float isize = 14f*4; - - new imagebutton("icon-play-2", isize, () -> { - hide(); - if(!wasPaused && !state.is(State.menu)) - state.set(State.playing); - }).text("$text.back").padTop(4f); - - new imagebutton("icon-tools", isize, ui.settings::show).text("$text.settings").padTop(4f); - - imagebutton sa = new imagebutton("icon-save", isize, save::show); - sa.text("$text.save").padTop(4f); + }else{ + build.begin(content()); - content().row(); - - imagebutton lo = new imagebutton("icon-load", isize, load::show); - lo.text("$text.load").padTop(4f); - lo.cell.disabled(b -> Net.active()); + content().defaults().size(120f).pad(5); + float isize = 14f * 4; - imagebutton ho = new imagebutton("icon-host", isize, () -> { - ui.host.show(); - }); - ho.text("$text.host").padTop(4f); - ho.cell.disabled(b -> Net.active()); - - new imagebutton("icon-quit", isize, () -> { - ui.showConfirm("$text.confirm", "$text.quit.confirm", () -> { - if(Net.client()) netClient.disconnectQuietly(); - runExitSave(); - hide(); - }); - }).text("Quit").padTop(4f); - - build.end(); - } - } + new imagebutton("icon-play-2", isize, () -> { + hide(); + if(!wasPaused && !state.is(State.menu)) + state.set(State.playing); + }).text("$text.back").padTop(4f); - private void runExitSave(){ - if(control.getSaves().getCurrent() == null || - !control.getSaves().getCurrent().isAutosave()){ - state.set(State.menu); - return; - } + new imagebutton("icon-tools", isize, ui.settings::show).text("$text.settings").padTop(4f); - ui.loadfrag.show("$text.saveload"); + imagebutton sa = new imagebutton("icon-save", isize, save::show); + sa.text("$text.save").padTop(4f); - Timers.runTask(5f, () -> { - ui.loadfrag.hide(); - try{ - control.getSaves().getCurrent().save(); - }catch(Throwable e){ - e = (e.getCause() == null ? e : e.getCause()); - ui.showError("[orange]"+ Bundles.get("text.savefail")+"\n[white]" + ClassReflection.getSimpleName(e.getClass()) + ": " + e.getMessage() + "\n" + "at " + e.getStackTrace()[0].getFileName() + ":" + e.getStackTrace()[0].getLineNumber()); - } - state.set(State.menu); - }); - } + content().row(); + + imagebutton lo = new imagebutton("icon-load", isize, load::show); + lo.text("$text.load").padTop(4f); + lo.cell.disabled(b -> Net.active()); + + imagebutton ho = new imagebutton("icon-host", isize, () -> { + ui.host.show(); + }); + ho.text("$text.host").padTop(4f); + ho.cell.disabled(b -> Net.active()); + + new imagebutton("icon-quit", isize, () -> { + ui.showConfirm("$text.confirm", "$text.quit.confirm", () -> { + if(Net.client()) netClient.disconnectQuietly(); + runExitSave(); + hide(); + }); + }).text("Quit").padTop(4f); + + build.end(); + } + } + + private void runExitSave(){ + if(control.getSaves().getCurrent() == null || + !control.getSaves().getCurrent().isAutosave()){ + state.set(State.menu); + return; + } + + ui.loadfrag.show("$text.saveload"); + + Timers.runTask(5f, () -> { + ui.loadfrag.hide(); + try{ + control.getSaves().getCurrent().save(); + }catch(Throwable e){ + e = (e.getCause() == null ? e : e.getCause()); + ui.showError("[orange]" + Bundles.get("text.savefail") + "\n[white]" + ClassReflection.getSimpleName(e.getClass()) + ": " + e.getMessage() + "\n" + "at " + e.getStackTrace()[0].getFileName() + ":" + e.getStackTrace()[0].getLineNumber()); + } + state.set(State.menu); + }); + } } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/RestartDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/RestartDialog.java index 855f53d6ff..38554f889f 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/RestartDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/RestartDialog.java @@ -5,8 +5,8 @@ import io.anuke.ucore.scene.ui.Dialog; import static io.anuke.mindustry.Vars.*; -public class RestartDialog extends Dialog { - +public class RestartDialog extends Dialog{ + public RestartDialog(){ super("$text.gameover", "dialog"); @@ -21,7 +21,7 @@ public class RestartDialog extends Dialog { pack(); }); - getButtonTable().addButton("$text.menu", ()-> { + getButtonTable().addButton("$text.menu", () -> { hide(); state.set(State.menu); logic.reset(); diff --git a/core/src/io/anuke/mindustry/ui/dialogs/RollbackDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/RollbackDialog.java index 9675e64a9c..2a991e30dd 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/RollbackDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/RollbackDialog.java @@ -5,34 +5,35 @@ import io.anuke.ucore.util.Strings; import static io.anuke.mindustry.Vars.gwt; -public class RollbackDialog extends FloatingDialog { - - public RollbackDialog(){ - super("$text.server.rollback"); - - setup(); - shown(this::setup); - } - - private void setup(){ - content().clear(); - buttons().clear(); - - if(gwt) return; - - content().row(); - content().add("$text.server.rollback.numberfield"); - - TextField field = content().addField("", t->{}).size(200f, 48f).get(); - field.setTextFieldFilter((f, c) -> field.getText().length() < 4); - - content().row(); - buttons().defaults().size(200f, 50f).left().pad(2f); - buttons().addButton("$text.cancel", this::hide); - - buttons().addButton("$text.ok", () -> { - //NetEvents.handleRollbackRequest(Integer.valueOf(field.getText())); - hide(); - }).disabled(b -> field.getText().isEmpty() || !Strings.canParsePostiveInt(field.getText())); - } +public class RollbackDialog extends FloatingDialog{ + + public RollbackDialog(){ + super("$text.server.rollback"); + + setup(); + shown(this::setup); + } + + private void setup(){ + content().clear(); + buttons().clear(); + + if(gwt) return; + + content().row(); + content().add("$text.server.rollback.numberfield"); + + TextField field = content().addField("", t -> { + }).size(200f, 48f).get(); + field.setTextFieldFilter((f, c) -> field.getText().length() < 4); + + content().row(); + buttons().defaults().size(200f, 50f).left().pad(2f); + buttons().addButton("$text.cancel", this::hide); + + buttons().addButton("$text.ok", () -> { + //NetEvents.handleRollbackRequest(Integer.valueOf(field.getText())); + hide(); + }).disabled(b -> field.getText().isEmpty() || !Strings.canParsePostiveInt(field.getText())); + } } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/SaveDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/SaveDialog.java index f56b6f3137..26a5e6b148 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/SaveDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/SaveDialog.java @@ -11,57 +11,57 @@ import static io.anuke.mindustry.Vars.*; public class SaveDialog extends LoadDialog{ - public SaveDialog() { - super("$text.savegame"); + public SaveDialog(){ + super("$text.savegame"); - update(() -> { - if(state.is(State.menu) && isShown()){ - hide(); - } - }); - } + update(() -> { + if(state.is(State.menu) && isShown()){ + hide(); + } + }); + } - public void addSetup(){ - if(!control.getSaves().canAddSave()){ - return; - } + public void addSetup(){ + if(!control.getSaves().canAddSave()){ + return; + } - slots.row(); - slots.addImageTextButton("$text.save.new", "icon-add", "clear", 14*3, () -> - ui.showTextInput("$text.save", "$text.save.newslot", "", text -> { - ui.loadAnd("$text.saving", () -> { - control.getSaves().addSave(text); - setup(); - }); - }) - ).fillX().margin(10f).minWidth(300f).height(70f).pad(4f).padRight(-4); - } + slots.row(); + slots.addImageTextButton("$text.save.new", "icon-add", "clear", 14 * 3, () -> + ui.showTextInput("$text.save", "$text.save.newslot", "", text -> { + ui.loadAnd("$text.saving", () -> { + control.getSaves().addSave(text); + setup(); + }); + }) + ).fillX().margin(10f).minWidth(300f).height(70f).pad(4f).padRight(-4); + } - @Override - public void modifyButton(TextButton button, SaveSlot slot){ - button.clicked(() -> { - if(button.childrenPressed()) return; + @Override + public void modifyButton(TextButton button, SaveSlot slot){ + button.clicked(() -> { + if(button.childrenPressed()) return; - ui.showConfirm("$text.overwrite", "$text.save.overwrite", () -> save(slot)); - }); - } + ui.showConfirm("$text.overwrite", "$text.save.overwrite", () -> save(slot)); + }); + } - void save(SaveSlot slot){ + void save(SaveSlot slot){ - ui.loadfrag.show("$text.saveload"); + ui.loadfrag.show("$text.saveload"); - Timers.runTask(5f, () -> { - hide(); - ui.loadfrag.hide(); - try{ - slot.save(); - }catch(Throwable e){ - e.printStackTrace(); - e = (e.getCause() == null ? e : e.getCause()); + Timers.runTask(5f, () -> { + hide(); + ui.loadfrag.hide(); + try{ + slot.save(); + }catch(Throwable e){ + e.printStackTrace(); + e = (e.getCause() == null ? e : e.getCause()); - ui.showError("[orange]"+Bundles.get("text.savefail")+"\n[white]" + ClassReflection.getSimpleName(e.getClass()) + ": " + e.getMessage() + "\n" + "at " + e.getStackTrace()[0].getFileName() + ":" + e.getStackTrace()[0].getLineNumber()); - } - }); - } + ui.showError("[orange]" + Bundles.get("text.savefail") + "\n[white]" + ClassReflection.getSimpleName(e.getClass()) + ": " + e.getMessage() + "\n" + "at " + e.getStackTrace()[0].getFileName() + ":" + e.getStackTrace()[0].getLineNumber()); + } + }); + } } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/SettingsMenuDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/SettingsMenuDialog.java index 72ebfe9ccb..a1fc7bbc3f 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/SettingsMenuDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/SettingsMenuDialog.java @@ -24,164 +24,164 @@ import io.anuke.ucore.util.Mathf; import static io.anuke.mindustry.Vars.*; public class SettingsMenuDialog extends SettingsDialog{ - public SettingsTable graphics; - public SettingsTable game; - public SettingsTable sound; + public SettingsTable graphics; + public SettingsTable game; + public SettingsTable sound; - private Table prefs; - private Table menu; - private boolean wasPaused; - - public SettingsMenuDialog(){ - setStyle(Core.skin.get("dialog", WindowStyle.class)); + private Table prefs; + private Table menu; + private boolean wasPaused; - hidden(() -> { - if(!state.is(State.menu)){ - if(!wasPaused || Net.active()) - state.set(State.playing); - } - }); + public SettingsMenuDialog(){ + setStyle(Core.skin.get("dialog", WindowStyle.class)); - shown(() -> { - if(!state.is(State.menu)){ - wasPaused = state.is(State.paused); - if(ui.paused.getScene() != null){ - wasPaused = ui.paused.wasPaused; - } - if(!Net.active()) state.set(State.paused); - ui.paused.hide(); - } - }); + hidden(() -> { + if(!state.is(State.menu)){ + if(!wasPaused || Net.active()) + state.set(State.playing); + } + }); - setFillParent(true); - title().setAlignment(Align.center); - getTitleTable().row(); - getTitleTable().add(new Image("white")) - .growX().height(3f).pad(4f).get().setColor(Palette.accent); + shown(() -> { + if(!state.is(State.menu)){ + wasPaused = state.is(State.paused); + if(ui.paused.getScene() != null){ + wasPaused = ui.paused.wasPaused; + } + if(!Net.active()) state.set(State.paused); + ui.paused.hide(); + } + }); - content().clearChildren(); - content().remove(); - buttons().remove(); + setFillParent(true); + title().setAlignment(Align.center); + getTitleTable().row(); + getTitleTable().add(new Image("white")) + .growX().height(3f).pad(4f).get().setColor(Palette.accent); - menu = new Table(); + content().clearChildren(); + content().remove(); + buttons().remove(); - Consumer s = table -> { - table.row(); - table.addImageTextButton("$text.back", "icon-arrow-left", 10*3, this::back).size(240f, 60f).colspan(2).padTop(15f); - }; + menu = new Table(); - game = new SettingsTable(s); - graphics = new SettingsTable(s); - sound = new SettingsTable(s); + Consumer s = table -> { + table.row(); + table.addImageTextButton("$text.back", "icon-arrow-left", 10 * 3, this::back).size(240f, 60f).colspan(2).padTop(15f); + }; - prefs = new Table(); - prefs.top(); - prefs.margin(14f); + game = new SettingsTable(s); + graphics = new SettingsTable(s); + sound = new SettingsTable(s); - menu.defaults().size(300f, 60f).pad(3f); - menu.addButton("$text.settings.game", () -> visible(0)); - menu.row(); - menu.addButton("$text.settings.graphics", () -> visible(1)); - menu.row(); - menu.addButton("$text.settings.sound", () -> visible(2)); - if(!Vars.mobile) { - menu.row(); - menu.addButton("$text.settings.controls", ui.controls::show); - } - menu.row(); - menu.addButton("$text.settings.language", ui.language::show); + prefs = new Table(); + prefs.top(); + prefs.margin(14f); - prefs.clearChildren(); - prefs.add(menu); + menu.defaults().size(300f, 60f).pad(3f); + menu.addButton("$text.settings.game", () -> visible(0)); + menu.row(); + menu.addButton("$text.settings.graphics", () -> visible(1)); + menu.row(); + menu.addButton("$text.settings.sound", () -> visible(2)); + if(!Vars.mobile){ + menu.row(); + menu.addButton("$text.settings.controls", ui.controls::show); + } + menu.row(); + menu.addButton("$text.settings.language", ui.language::show); - ScrollPane pane = new ScrollPane(prefs, "clear"); - pane.addCaptureListener(new InputListener() { - @Override - public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) { - Element actor = pane.hit(x, y, true); - if (actor instanceof Slider) { - pane.setFlickScroll(false); - return true; - } + prefs.clearChildren(); + prefs.add(menu); - return super.touchDown(event, x, y, pointer, button); - } + ScrollPane pane = new ScrollPane(prefs, "clear"); + pane.addCaptureListener(new InputListener(){ + @Override + public boolean touchDown(InputEvent event, float x, float y, int pointer, int button){ + Element actor = pane.hit(x, y, true); + if(actor instanceof Slider){ + pane.setFlickScroll(false); + return true; + } - @Override - public void touchUp(InputEvent event, float x, float y, int pointer, int button) { - pane.setFlickScroll(true); - super.touchUp(event, x, y, pointer, button); - } - }); - pane.setFadeScrollBars(false); + return super.touchDown(event, x, y, pointer, button); + } - row(); - add(pane).grow().top(); - row(); - add(buttons()).fillX(); + @Override + public void touchUp(InputEvent event, float x, float y, int pointer, int button){ + pane.setFlickScroll(true); + super.touchUp(event, x, y, pointer, button); + } + }); + pane.setFadeScrollBars(false); - hidden(this::back); + row(); + add(pane).grow().top(); + row(); + add(buttons()).fillX(); - addSettings(); - } + hidden(this::back); - void addSettings(){ - sound.volumePrefs(); + addSettings(); + } - game.screenshakePref(); - //game.checkPref("smoothcam", true); - game.checkPref("effects", true); - //game.sliderPref("sensitivity", 100, 10, 300, i -> i + "%"); - game.sliderPref("saveinterval", 90, 10, 5*120, i -> Bundles.format("setting.seconds", i)); + void addSettings(){ + sound.volumePrefs(); - if(!gwt){ - graphics.checkPref("multithread", true, threads::setEnabled); + game.screenshakePref(); + //game.checkPref("smoothcam", true); + game.checkPref("effects", true); + //game.sliderPref("sensitivity", 100, 10, 300, i -> i + "%"); + game.sliderPref("saveinterval", 90, 10, 5 * 120, i -> Bundles.format("setting.seconds", i)); - if(Settings.getBool("multithread")){ - threads.setEnabled(true); - } - } + if(!gwt){ + graphics.checkPref("multithread", true, threads::setEnabled); - if(!mobile && !gwt) { - graphics.checkPref("vsync", true, b -> Gdx.graphics.setVSync(b)); - graphics.checkPref("fullscreen", false, b -> { - if (b) { - Gdx.graphics.setFullscreenMode(Gdx.graphics.getDisplayMode()); - } else { - Gdx.graphics.setWindowedMode(600, 480); - } - }); + if(Settings.getBool("multithread")){ + threads.setEnabled(true); + } + } - Gdx.graphics.setVSync(Settings.getBool("vsync")); - if(Settings.getBool("fullscreen")){ - Gdx.graphics.setFullscreenMode(Gdx.graphics.getDisplayMode()); - } - } + if(!mobile && !gwt){ + graphics.checkPref("vsync", true, b -> Gdx.graphics.setVSync(b)); + graphics.checkPref("fullscreen", false, b -> { + if(b){ + Gdx.graphics.setFullscreenMode(Gdx.graphics.getDisplayMode()); + }else{ + Gdx.graphics.setWindowedMode(600, 480); + } + }); - graphics.checkPref("fps", false); - graphics.checkPref("lasers", true); - graphics.checkPref("healthbars", true); - graphics.checkPref("minimap", !mobile); //minimap is disabled by default on mobile devices - } + Gdx.graphics.setVSync(Settings.getBool("vsync")); + if(Settings.getBool("fullscreen")){ + Gdx.graphics.setFullscreenMode(Gdx.graphics.getDisplayMode()); + } + } - private void back(){ - prefs.clearChildren(); - prefs.add(menu); - } + graphics.checkPref("fps", false); + graphics.checkPref("lasers", true); + graphics.checkPref("healthbars", true); + graphics.checkPref("minimap", !mobile); //minimap is disabled by default on mobile devices + } - private void visible(int index){ - prefs.clearChildren(); - Table table = Mathf.select(index, game, graphics, sound); + private void back(){ + prefs.clearChildren(); + prefs.add(menu); + } + + private void visible(int index){ + prefs.clearChildren(); + Table table = Mathf.select(index, game, graphics, sound); prefs.add(table); - } - - @Override - public void addCloseButton(){ - buttons().addImageTextButton("$text.menu", "icon-arrow-left", 30f, this::hide).size(230f, 64f); - - keyDown(key->{ - if(key == Keys.ESCAPE || key == Keys.BACK) - hide(); - }); - } + } + + @Override + public void addCloseButton(){ + buttons().addImageTextButton("$text.menu", "icon-arrow-left", 30f, this::hide).size(230f, 64f); + + keyDown(key -> { + if(key == Keys.ESCAPE || key == Keys.BACK) + hide(); + }); + } } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/TraceDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/TraceDialog.java index 1874ec157c..39eece4f47 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/TraceDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/TraceDialog.java @@ -5,7 +5,7 @@ import io.anuke.mindustry.net.TraceInfo; import io.anuke.ucore.scene.ui.layout.Table; import io.anuke.ucore.util.Bundles; -public class TraceDialog extends FloatingDialog { +public class TraceDialog extends FloatingDialog{ public TraceDialog(){ super("$text.trace"); diff --git a/core/src/io/anuke/mindustry/ui/dialogs/UnlocksDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/UnlocksDialog.java index 87f667e628..7bc8d5e445 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/UnlocksDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/UnlocksDialog.java @@ -15,9 +15,9 @@ import io.anuke.ucore.scene.utils.UIUtils; import static io.anuke.mindustry.Vars.control; -public class UnlocksDialog extends FloatingDialog { +public class UnlocksDialog extends FloatingDialog{ - public UnlocksDialog() { + public UnlocksDialog(){ super("$text.unlocks"); addCloseButton(); @@ -38,7 +38,7 @@ public class UnlocksDialog extends FloatingDialog { Array array = allContent.get(key); if(array.size == 0 || !(array.first() instanceof UnlockableContent)) continue; - table.add("$content." +key + ".name").growX().left().color(Palette.accent); + table.add("$content." + key + ".name").growX().left().color(Palette.accent); table.row(); table.addImage("white").growX().pad(5).padLeft(0).padRight(0).height(3).color(Palette.accent); table.row(); @@ -46,19 +46,19 @@ public class UnlocksDialog extends FloatingDialog { list.left(); int maxWidth = UIUtils.portrait() ? 7 : 13; - int size = 8*6; + int size = 8 * 6; int count = 0; - for (int i = 0; i < array.size; i++) { - UnlockableContent unlock = (UnlockableContent)array.get(i); + for(int i = 0; i < array.size; i++){ + UnlockableContent unlock = (UnlockableContent) array.get(i); if(unlock.isHidden()) continue; Image image = control.database().isUnlocked(unlock) ? new Image(unlock.getContentIcon()) : new Image("icon-locked"); list.add(image).size(size).pad(3); - if(control.database().isUnlocked(unlock)) { + if(control.database().isUnlocked(unlock)){ image.clicked(() -> Vars.ui.content.show(unlock)); image.addListener(new Tooltip<>(new Table("clear"){{ add(unlock.localizedName()); diff --git a/core/src/io/anuke/mindustry/ui/fragments/BackgroundFragment.java b/core/src/io/anuke/mindustry/ui/fragments/BackgroundFragment.java index 793da24841..55c96f856b 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/BackgroundFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/BackgroundFragment.java @@ -10,29 +10,29 @@ import io.anuke.ucore.scene.ui.layout.Unit; import static io.anuke.mindustry.Vars.state; -public class BackgroundFragment extends Fragment { +public class BackgroundFragment extends Fragment{ @Override - public void build(Group parent) { + public void build(Group parent){ Core.scene.table().addRect((a, b, w, h) -> { Draw.color(); TextureRegion back = Draw.region("background"); - float backscl = (int)Math.max(Gdx.graphics.getWidth() / (float)back.getRegionWidth() * 1.5f, Unit.dp.scl(5f)); + float backscl = (int) Math.max(Gdx.graphics.getWidth() / (float) back.getRegionWidth() * 1.5f, Unit.dp.scl(5f)); Draw.alpha(0.5f); - Core.batch.draw(back, w/2 - back.getRegionWidth()*backscl/2, h/2 - back.getRegionHeight()*backscl/2, - back.getRegionWidth()*backscl, back.getRegionHeight()*backscl); + Core.batch.draw(back, w / 2 - back.getRegionWidth() * backscl / 2, h / 2 - back.getRegionHeight() * backscl / 2, + back.getRegionWidth() * backscl, back.getRegionHeight() * backscl); boolean portrait = Gdx.graphics.getWidth() < Gdx.graphics.getHeight(); - float logoscl = (int)Unit.dp.scl(7) * (portrait ? 5f/7f : 1f); + float logoscl = (int) Unit.dp.scl(7) * (portrait ? 5f / 7f : 1f); TextureRegion logo = Core.skin.getRegion("logotext"); - float logow = logo.getRegionWidth()*logoscl; - float logoh = logo.getRegionHeight()*logoscl; + float logow = logo.getRegionWidth() * logoscl; + float logoh = logo.getRegionHeight() * logoscl; Draw.color(); - Core.batch.draw(logo, (int)(w/2 - logow/2), (int)(h - logoh + 15 - Unit.dp.scl(portrait ? 30f : 0)), logow, logoh); + Core.batch.draw(logo, (int) (w / 2 - logow / 2), (int) (h - logoh + 15 - Unit.dp.scl(portrait ? 30f : 0)), logow, logoh); }).visible(() -> state.is(State.menu)).grow(); } } diff --git a/core/src/io/anuke/mindustry/ui/fragments/BlockConfigFragment.java b/core/src/io/anuke/mindustry/ui/fragments/BlockConfigFragment.java index 2ad8787086..b122811e16 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/BlockConfigFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/BlockConfigFragment.java @@ -7,6 +7,7 @@ import com.badlogic.gdx.utils.Align; import io.anuke.mindustry.content.blocks.Blocks; import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.input.InputHandler; +import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; import io.anuke.ucore.core.Core; import io.anuke.ucore.core.Graphics; @@ -18,17 +19,18 @@ import io.anuke.ucore.scene.ui.layout.Table; import static io.anuke.mindustry.Vars.state; import static io.anuke.mindustry.Vars.tilesize; -public class BlockConfigFragment extends Fragment { +public class BlockConfigFragment extends Fragment{ private Table table = new Table(); private InputHandler input; private Tile configTile; + private Block configBlock; public BlockConfigFragment(InputHandler input){ this.input = input; } @Override - public void build(Group parent) { + public void build(Group parent){ parent.addChild(table); } @@ -42,6 +44,7 @@ public class BlockConfigFragment extends Fragment { public void showConfig(Tile tile){ configTile = tile; + configBlock = tile.block(); table.setVisible(true); table.clear(); @@ -63,9 +66,9 @@ public class BlockConfigFragment extends Fragment { } table.setOrigin(Align.center); - Vector2 pos = Graphics.screen(tile.drawx(), tile.drawy() - tile.block().size * tilesize/2f - 1); + Vector2 pos = Graphics.screen(tile.drawx(), tile.drawy() - tile.block().size * tilesize / 2f - 1); table.setPosition(pos.x, pos.y, Align.top); - if(configTile == null || configTile.block() == Blocks.air){ + if(configTile == null || configTile.block() == Blocks.air || configTile.block() != configBlock){ hideConfig(); } }); diff --git a/core/src/io/anuke/mindustry/ui/fragments/BlockConsumeFragment.java b/core/src/io/anuke/mindustry/ui/fragments/BlockConsumeFragment.java index 0934f8fb4c..eed5e343aa 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/BlockConsumeFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/BlockConsumeFragment.java @@ -17,12 +17,12 @@ import io.anuke.ucore.scene.ui.layout.Table; import static io.anuke.mindustry.Vars.*; -public class BlockConsumeFragment extends Fragment { +public class BlockConsumeFragment extends Fragment{ private Table table; private boolean visible; @Override - public void build(Group parent) { + public void build(Group parent){ table = new Table(); table.setVisible(() -> !state.is(State.menu) && visible); table.setTransform(true); @@ -68,7 +68,7 @@ public class BlockConsumeFragment extends Fragment { rebuild(block, entity); } - Vector2 v = Graphics.screen(tile.drawx() - tile.block().size * tilesize/2f, tile.drawy() + tile.block().size * tilesize/2f); + Vector2 v = Graphics.screen(tile.drawx() - tile.block().size * tilesize / 2f, tile.drawy() + tile.block().size * tilesize / 2f); table.pack(); table.setPosition(v.x, v.y, Align.topRight); }); @@ -78,7 +78,8 @@ public class BlockConsumeFragment extends Fragment { public void hide(){ table.clear(); - table.update(() -> {}); + table.update(() -> { + }); visible = false; } @@ -97,10 +98,10 @@ public class BlockConsumeFragment extends Fragment { }).get().act(0); Table result = table.table(out -> { - out.addImage(c.getIcon()).size(10*scale).color(Color.DARK_GRAY).padRight(-10*scale).padBottom(-scale*2); - out.addImage(c.getIcon()).size(10*scale).color(Palette.accent); - out.addImage("icon-missing").size(10*scale).color(Palette.remove).padLeft(-10*scale); - }).size(10*scale).get(); + out.addImage(c.getIcon()).size(10 * scale).color(Color.DARK_GRAY).padRight(-10 * scale).padBottom(-scale * 2); + out.addImage(c.getIcon()).size(10 * scale).color(Palette.accent); + out.addImage("icon-missing").size(10 * scale).color(Palette.remove).padLeft(-10 * scale); + }).size(10 * scale).get(); result.hovered(() -> hovered[0] = true); if(!mobile){ diff --git a/core/src/io/anuke/mindustry/ui/fragments/BlockInventoryFragment.java b/core/src/io/anuke/mindustry/ui/fragments/BlockInventoryFragment.java index 2720ad7096..bfc30db2df 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/BlockInventoryFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/BlockInventoryFragment.java @@ -30,7 +30,7 @@ import io.anuke.ucore.util.Strings; import static io.anuke.mindustry.Vars.*; -public class BlockInventoryFragment extends Fragment { +public class BlockInventoryFragment extends Fragment{ private final static float holdWithdraw = 40f; private Table table; @@ -44,8 +44,20 @@ public class BlockInventoryFragment extends Fragment { this.input = input; } + @Remote(called = Loc.server, targets = Loc.both, in = In.blocks, forward = true) + public static void requestItem(Player player, Tile tile, Item item, int amount){ + if(player == null) return; + + int removed = tile.block().removeStack(tile, item, amount); + + player.inventory.addItem(item, removed); + for(int j = 0; j < Mathf.clamp(removed / 3, 1, 8); j++){ + Timers.run(j * 3f, () -> CallEntity.transferItemEffect(item, tile.drawx(), tile.drawy(), player)); + } + } + @Override - public void build(Group parent) { + public void build(Group parent){ table = new Table(); table.setVisible(() -> !state.is(State.menu)); table.setTransform(true); @@ -55,7 +67,8 @@ public class BlockInventoryFragment extends Fragment { public void showFor(Tile t){ this.tile = t.target(); - if(tile == null || tile.entity == null || !tile.block().isAccessible() || tile.entity.items.total() == 0) return; + if(tile == null || tile.entity == null || !tile.block().isAccessible() || tile.entity.items.total() == 0) + return; rebuild(true); } @@ -93,10 +106,10 @@ public class BlockInventoryFragment extends Fragment { } updateTablePosition(); - if(tile.block().hasItems) { - for (int i = 0; i < Item.all().size; i++) { + if(tile.block().hasItems){ + for(int i = 0; i < Item.all().size; i++){ boolean has = tile.entity.items.has(Item.getByID(i)); - if (has != container.contains(i)) { + if(has != container.contains(i)){ rebuild(false); } } @@ -108,13 +121,13 @@ public class BlockInventoryFragment extends Fragment { int row = 0; table.margin(6f); - table.defaults().size(mobile ? 16*3 : 16*2).space(6f); + table.defaults().size(mobile ? 16 * 3 : 16 * 2).space(6f); - if(tile.block().hasItems) { + if(tile.block().hasItems){ - for (int i = 0; i < Item.all().size; i++) { + for(int i = 0; i < Item.all().size; i++){ Item item = Item.getByID(i); - if (!tile.entity.items.has(item)) continue; + if(!tile.entity.items.has(item)) continue; container.add(i); @@ -133,7 +146,7 @@ public class BlockInventoryFragment extends Fragment { image.addListener(new InputListener(){ @Override - public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) { + public boolean touchDown(InputEvent event, float x, float y, int pointer, int button){ if(!canPick.get() || !tile.entity.items.has(item)) return false; int amount = Math.min(1, player.inventory.itemCapacityUsed(item)); CallBlocks.requestItem(player, tile, item, amount); @@ -144,14 +157,14 @@ public class BlockInventoryFragment extends Fragment { } @Override - public void touchUp(InputEvent event, float x, float y, int pointer, int button) { + public void touchUp(InputEvent event, float x, float y, int pointer, int button){ holding = false; lastItem = null; } }); table.add(image); - if (row++ % cols == cols - 1) table.row(); + if(row++ % cols == cols - 1) table.row(); } } @@ -168,29 +181,17 @@ public class BlockInventoryFragment extends Fragment { } private String round(float f){ - f = (int)f; + f = (int) f; if(f >= 1000){ - return Strings.toFixed(f/1000, 1) + "k"; + return Strings.toFixed(f / 1000, 1) + "k"; }else{ - return (int)f+""; + return (int) f + ""; } } private void updateTablePosition(){ - Vector2 v = Graphics.screen(tile.drawx() + tile.block().size * tilesize/2f, tile.drawy() + tile.block().size * tilesize/2f); + Vector2 v = Graphics.screen(tile.drawx() + tile.block().size * tilesize / 2f, tile.drawy() + tile.block().size * tilesize / 2f); table.pack(); table.setPosition(v.x, v.y, Align.topLeft); } - - @Remote(called = Loc.server, targets = Loc.both, in = In.blocks, forward = true) - public static void requestItem(Player player, Tile tile, Item item, int amount){ - if(player == null) return; - - int removed = tile.block().removeStack(tile, item, amount); - - player.inventory.addItem(item, removed); - for(int j = 0; j < Mathf.clamp(removed/3, 1, 8); j ++){ - Timers.run(j*3f, () -> CallEntity.transferItemEffect(item, tile.drawx(), tile.drawy(), player)); - } - } } diff --git a/core/src/io/anuke/mindustry/ui/fragments/BlocksFragment.java b/core/src/io/anuke/mindustry/ui/fragments/BlocksFragment.java index 673ac7c2aa..a6949bcdf6 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/BlocksFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/BlocksFragment.java @@ -29,327 +29,344 @@ import io.anuke.ucore.util.Strings; import static io.anuke.mindustry.Vars.*; public class BlocksFragment extends Fragment{ - /**Table containing description that is shown on top.*/ - private Table descTable; - /**Main table containing the whole menu.*/ - private Table mainTable; - /**Table for all section buttons and blocks.*/ - private Table selectTable; - /**Whether the whole thing is shown or hidden by the popup button.*/ - private boolean shown = true; - /**Recipe currently hovering over.*/ - private Recipe hoverRecipe; - /**Last category selected.*/ - private Category lastCategory; - /**Last block pane scroll Y position.*/ - private float lastScroll; - /**Temporary recipe array for storage*/ - private Array recipes = new Array<>(); - - //number of block icon rows - private static final int rows = 4; - //number of category button rows - private static final int secrows = 4; - //size of each block icon - private static final float size = 48; - //maximum recipe rows - private static final int maxrow = 3; - - public void build(Group parent){ - InputHandler input = control.input(0); - - //create container table - new table(){{ - abottom(); - aright(); - - //make it only be shown when needed. - visible(() -> !state.is(State.menu)); - - //create the main blocks table - mainTable = new table(){{ - - //add top description table - descTable = new Table("button"); - descTable.setVisible(() -> hoverRecipe != null || input.recipe != null); //make sure it's visible when necessary - descTable.update(() -> { - // note: This is required because there is no direct connection between - // input.recipe and the description ui. If input.recipe gets set to null - // a proper cleanup of the ui elements is required. - boolean anyRecipeShown = input.recipe != null || hoverRecipe != null; - boolean descriptionTableClean = descTable.getChildren().size == 0; - boolean cleanupRequired = !anyRecipeShown && !descriptionTableClean; - if(cleanupRequired){ - descTable.clear(); - } - }); - - add(descTable).fillX().uniformX(); - - row(); - - //now add the block selection menu - selectTable = new table("pane") {{ - touchable(Touchable.enabled); - - margin(10f); - marginLeft(0f); - marginRight(0f); - marginTop(-5); - - }}.right().bottom().end().get(); - - visible(() -> !state.is(State.menu)); - - }}.end().get(); - - }}.end(); - - rebuild(); - } - - /**Rebuilds the whole placement menu, attempting to preserve previous state.*/ - void rebuild(){ - selectTable.clear(); - - InputHandler input = control.input(0); - Stack stack = new Stack(); - ButtonGroup group = new ButtonGroup<>(); - Table catTable = selectTable; - - int cati = 0; - int checkedi = 0; - int rowsUsed = 0; - - //add categories - for (Category cat : Category.values()) { - //get recipes out by category - Recipe.getUnlockedByCategory(cat, recipes); - - //empty section, nothing to see here - if(recipes.size == 0){ - continue; - } - - //table where actual recipes go - Table recipeTable = new Table(); - recipeTable.margin(4).top().left().marginRight(15); - - //add a new row here when needed - if (cati == secrows) { - catTable = new Table(); - selectTable.row(); - selectTable.add(catTable).colspan(secrows).padTop(-5).growX(); - } - - //add category button - ImageButton catb = catTable.addImageButton( "icon-" + cat.name(), "toggle", 40, () -> { - if (!recipeTable.isVisible() && input.recipe != null) { - input.recipe = null; - } - lastCategory = cat; - stack.act(Gdx.graphics.getDeltaTime()); - stack.act(Gdx.graphics.getDeltaTime()); - }).growX().height(54).group(group) - .name("sectionbutton" + cat.name()).get(); - - if(lastCategory == cat || lastCategory == null){ - checkedi = cati; - lastCategory = cat; - } - - //scrollpane for recipes - ScrollPane pane = new ScrollPane(recipeTable, "clear-black"); - pane.setOverscroll(false, false); - pane.setVisible(catb::isChecked); - pane.setScrollYForce(lastScroll); - pane.update(() -> { - Element e = Core.scene.hit(Graphics.mouse().x, Graphics.mouse().y, true); - if(e != null && e.isDescendantOf(pane)){ - Core.scene.setScrollFocus(pane); - }else if(Core.scene.getScrollFocus() == pane){ - Core.scene.setScrollFocus(null); - } - - if(lastCategory == cat){ - lastScroll = pane.getVisualScrollY(); - } - }); - stack.add(pane); - - int i = 0; - - //add actual recipes - for (Recipe r : recipes) { - if((r.debugOnly && !debug) || (r.desktopOnly && mobile)) continue; - - ImageButton image = new ImageButton(new TextureRegion(), "select"); - - TextureRegion[] regions = r.result.getCompactIcon(); - Stack istack = new Stack(); - for(TextureRegion region : regions){ - Image u = new Image(region); - u.update(() -> u.setColor(istack.getColor())); - istack.add(u); - } - - image.getImageCell().setActor(istack).size(size); - image.addChild(istack); - image.setTouchable(Touchable.enabled); - image.getImage().remove(); - - image.addListener(new ClickListener(){ - @Override - public void enter(InputEvent event, float x, float y, int pointer, Element fromActor) { - super.enter(event, x, y, pointer, fromActor); - if (hoverRecipe != r) { - hoverRecipe = r; - updateRecipe(r); - } - } - - @Override - public void exit(InputEvent event, float x, float y, int pointer, Element toActor) { - super.exit(event, x, y, pointer, toActor); - hoverRecipe = null; - updateRecipe(input.recipe); - } - }); - - image.clicked(() -> { - // note: input.recipe only gets set here during a click. - // during a hover only the visual description will be updated. - InputHandler handler = mobile ? input : control.input(0); - - boolean nothingSelectedYet = handler.recipe == null; - boolean selectedSomethingElse = !nothingSelectedYet && handler.recipe != r; - boolean shouldMakeSelection = nothingSelectedYet || selectedSomethingElse; - if (shouldMakeSelection) { - handler.recipe = r; - hoverRecipe = r; - updateRecipe(r); - } else { - handler.recipe = null; - hoverRecipe = null; - updateRecipe(null); - } - }); - - recipeTable.add(image).size(size + 8); - - image.update(() -> { - image.setChecked(r == control.input(0).recipe); - TileEntity entity = players[0].getClosestCore(); - - if(entity == null) return; - - for(ItemStack s : r.requirements){ - if(!entity.items.has(s.item, Mathf.ceil(s.amount))){ - istack.setColor(Color.GRAY); - return; - } - } - istack.setColor(Color.WHITE); - }); - - if (i % rows == rows - 1) { - rowsUsed = Math.max((i+1)/rows, rowsUsed); - recipeTable.row(); - } - - i++; - } - - cati ++; - } - - if(group.getButtons().size > 0){ - group.getButtons().get(checkedi).setChecked(true); - } - - selectTable.row(); - selectTable.add(stack).growX().left().top().colspan(Category.values().length).padBottom(-5).height((size + 12)*rowsUsed); - } - - void toggle(boolean show, float t, Interpolation ip){ - if(shown){ - shown = false; - mainTable.actions(Actions.translateBy(0, mainTable.getTranslation().y + (-mainTable.getHeight() - descTable.getHeight()), t, ip)); - }else{ - shown = true; - mainTable.actions(Actions.translateBy(0, -mainTable.getTranslation().y, t, ip)); - } - } - - private void updateRecipe(Recipe recipe){ - if (recipe == null) { - descTable.clear(); - return; - } - - descTable.clear(); - descTable.setTouchable(Touchable.enabled); - - descTable.defaults().left(); - descTable.left(); - descTable.margin(12); - - Table header = new Table(); - - descTable.add(header).left(); - - descTable.row(); - - TextureRegion[] regions = recipe.result.getCompactIcon(); - - Stack istack = new Stack(); - - for(TextureRegion region : regions) istack.add(new Image(region)); - - header.add(istack).size(8*5).padTop(4); - Label nameLabel = new Label(recipe.result.formalName); - nameLabel.setWrap(true); - header.add(nameLabel).padLeft(2).width(120f); - - header.addButton("?", () -> ui.content.show(recipe)).expandX().padLeft(3).top().right().size(40f, 44f).padTop(-2); - - descTable.add().pad(2); - - Table requirements = new Table(); - - descTable.row(); - - descTable.add(requirements); - descTable.left(); - - for(ItemStack stack : recipe.requirements){ - requirements.addImage(stack.item.region).size(8*3); - Label reqlabel = new Label(() ->{ - TileEntity core = players[0].getClosestCore(); - if(core == null) return "*/*"; + //number of block icon rows + private static final int rows = 4; + //number of category button rows + private static final int secrows = 4; + //size of each block icon + private static final float size = 48; + //maximum recipe rows + private static final int maxrow = 3; + /** + * Table containing description that is shown on top. + */ + private Table descTable; + /** + * Main table containing the whole menu. + */ + private Table mainTable; + /** + * Table for all section buttons and blocks. + */ + private Table selectTable; + /** + * Whether the whole thing is shown or hidden by the popup button. + */ + private boolean shown = true; + /** + * Recipe currently hovering over. + */ + private Recipe hoverRecipe; + /** + * Last category selected. + */ + private Category lastCategory; + /** + * Last block pane scroll Y position. + */ + private float lastScroll; + /** + * Temporary recipe array for storage + */ + private Array recipes = new Array<>(); + + public void build(Group parent){ + InputHandler input = control.input(0); + + //create container table + new table(){{ + abottom(); + aright(); + + //make it only be shown when needed. + visible(() -> !state.is(State.menu)); + + //create the main blocks table + mainTable = new table(){{ + + //add top description table + descTable = new Table("button"); + descTable.setVisible(() -> hoverRecipe != null || input.recipe != null); //make sure it's visible when necessary + descTable.update(() -> { + // note: This is required because there is no direct connection between + // input.recipe and the description ui. If input.recipe gets set to null + // a proper cleanup of the ui elements is required. + boolean anyRecipeShown = input.recipe != null || hoverRecipe != null; + boolean descriptionTableClean = descTable.getChildren().size == 0; + boolean cleanupRequired = !anyRecipeShown && !descriptionTableClean; + if(cleanupRequired){ + descTable.clear(); + } + }); + + add(descTable).fillX().uniformX(); + + row(); + + //now add the block selection menu + selectTable = new table("pane"){{ + touchable(Touchable.enabled); + + margin(10f); + marginLeft(0f); + marginRight(0f); + marginTop(-5); + + }}.right().bottom().end().get(); + + visible(() -> !state.is(State.menu)); + + }}.end().get(); + + }}.end(); + + rebuild(); + } + + /** + * Rebuilds the whole placement menu, attempting to preserve previous state. + */ + void rebuild(){ + selectTable.clear(); + + InputHandler input = control.input(0); + Stack stack = new Stack(); + ButtonGroup group = new ButtonGroup<>(); + Table catTable = selectTable; + + int cati = 0; + int checkedi = 0; + int rowsUsed = 0; + + //add categories + for(Category cat : Category.values()){ + //get recipes out by category + Recipe.getUnlockedByCategory(cat, recipes); + + //empty section, nothing to see here + if(recipes.size == 0){ + continue; + } + + //table where actual recipes go + Table recipeTable = new Table(); + recipeTable.margin(4).top().left().marginRight(15); + + //add a new row here when needed + if(cati == secrows){ + catTable = new Table(); + selectTable.row(); + selectTable.add(catTable).colspan(secrows).padTop(-5).growX(); + } + + //add category button + ImageButton catb = catTable.addImageButton("icon-" + cat.name(), "toggle", 40, () -> { + if(!recipeTable.isVisible() && input.recipe != null){ + input.recipe = null; + } + lastCategory = cat; + stack.act(Gdx.graphics.getDeltaTime()); + stack.act(Gdx.graphics.getDeltaTime()); + }).growX().height(54).group(group) + .name("sectionbutton" + cat.name()).get(); + + if(lastCategory == cat || lastCategory == null){ + checkedi = cati; + lastCategory = cat; + } + + //scrollpane for recipes + ScrollPane pane = new ScrollPane(recipeTable, "clear-black"); + pane.setOverscroll(false, false); + pane.setVisible(catb::isChecked); + pane.setScrollYForce(lastScroll); + pane.update(() -> { + Element e = Core.scene.hit(Graphics.mouse().x, Graphics.mouse().y, true); + if(e != null && e.isDescendantOf(pane)){ + Core.scene.setScrollFocus(pane); + }else if(Core.scene.getScrollFocus() == pane){ + Core.scene.setScrollFocus(null); + } + + if(lastCategory == cat){ + lastScroll = pane.getVisualScrollY(); + } + }); + stack.add(pane); + + int i = 0; + + //add actual recipes + for(Recipe r : recipes){ + if((r.debugOnly && !debug) || (r.desktopOnly && mobile)) continue; + + ImageButton image = new ImageButton(new TextureRegion(), "select"); + + TextureRegion[] regions = r.result.getCompactIcon(); + Stack istack = new Stack(); + for(TextureRegion region : regions){ + Image u = new Image(region); + u.update(() -> u.setColor(istack.getColor())); + istack.add(u); + } + + image.getImageCell().setActor(istack).size(size); + image.addChild(istack); + image.setTouchable(Touchable.enabled); + image.getImage().remove(); + + image.addListener(new ClickListener(){ + @Override + public void enter(InputEvent event, float x, float y, int pointer, Element fromActor){ + super.enter(event, x, y, pointer, fromActor); + if(hoverRecipe != r){ + hoverRecipe = r; + updateRecipe(r); + } + } + + @Override + public void exit(InputEvent event, float x, float y, int pointer, Element toActor){ + super.exit(event, x, y, pointer, toActor); + hoverRecipe = null; + updateRecipe(input.recipe); + } + }); + + image.clicked(() -> { + // note: input.recipe only gets set here during a click. + // during a hover only the visual description will be updated. + InputHandler handler = mobile ? input : control.input(0); + + boolean nothingSelectedYet = handler.recipe == null; + boolean selectedSomethingElse = !nothingSelectedYet && handler.recipe != r; + boolean shouldMakeSelection = nothingSelectedYet || selectedSomethingElse; + if(shouldMakeSelection){ + handler.recipe = r; + hoverRecipe = r; + updateRecipe(r); + }else{ + handler.recipe = null; + hoverRecipe = null; + updateRecipe(null); + } + }); + + recipeTable.add(image).size(size + 8); + + image.update(() -> { + image.setChecked(r == control.input(0).recipe); + TileEntity entity = players[0].getClosestCore(); + + if(entity == null) return; + + for(ItemStack s : r.requirements){ + if(!entity.items.has(s.item, Mathf.ceil(s.amount))){ + istack.setColor(Color.GRAY); + return; + } + } + istack.setColor(Color.WHITE); + }); + + if(i % rows == rows - 1){ + rowsUsed = Math.max((i + 1) / rows, rowsUsed); + recipeTable.row(); + } + + i++; + } + + cati++; + } + + if(group.getButtons().size > 0){ + group.getButtons().get(checkedi).setChecked(true); + } + + selectTable.row(); + selectTable.add(stack).growX().left().top().colspan(Category.values().length).padBottom(-5).height((size + 12) * rowsUsed); + } + + void toggle(boolean show, float t, Interpolation ip){ + if(shown){ + shown = false; + mainTable.actions(Actions.translateBy(0, mainTable.getTranslation().y + (-mainTable.getHeight() - descTable.getHeight()), t, ip)); + }else{ + shown = true; + mainTable.actions(Actions.translateBy(0, -mainTable.getTranslation().y, t, ip)); + } + } + + private void updateRecipe(Recipe recipe){ + if(recipe == null){ + descTable.clear(); + return; + } + + descTable.clear(); + descTable.setTouchable(Touchable.enabled); + + descTable.defaults().left(); + descTable.left(); + descTable.margin(12); + + Table header = new Table(); + + descTable.add(header).left(); + + descTable.row(); + + TextureRegion[] regions = recipe.result.getCompactIcon(); + + Stack istack = new Stack(); + + for(TextureRegion region : regions) istack.add(new Image(region)); + + header.add(istack).size(8 * 5).padTop(4); + Label nameLabel = new Label(recipe.result.formalName); + nameLabel.setWrap(true); + header.add(nameLabel).padLeft(2).width(120f); + + header.addButton("?", () -> ui.content.show(recipe)).expandX().padLeft(3).top().right().size(40f, 44f).padTop(-2); + + descTable.add().pad(2); + + Table requirements = new Table(); + + descTable.row(); + + descTable.add(requirements); + descTable.left(); + + for(ItemStack stack : recipe.requirements){ + requirements.addImage(stack.item.region).size(8 * 3); + Label reqlabel = new Label(() -> { + TileEntity core = players[0].getClosestCore(); + if(core == null) return "*/*"; - int amount = core.items.get(stack.item); - String color = (amount < stack.amount/2f ? "[red]" : amount < stack.amount ? "[orange]" : "[white]"); + int amount = core.items.get(stack.item); + String color = (amount < stack.amount / 2f ? "[red]" : amount < stack.amount ? "[orange]" : "[white]"); - return color + format(amount) + "[white]/" + stack.amount; - }); + return color + format(amount) + "[white]/" + stack.amount; + }); - requirements.add(reqlabel).left(); - requirements.row(); - } + requirements.add(reqlabel).left(); + requirements.row(); + } - descTable.row(); - } + descTable.row(); + } - String format(int number){ - if(number >= 1000000) { - return Strings.toFixed(number/1000000f, 1) + "[gray]mil[]"; - }else if(number >= 10000){ - return number/1000 + "[gray]k[]"; - }else if(number >= 1000){ - return Strings.toFixed(number/1000f, 1) + "[gray]k[]"; - }else{ - return number + ""; - } - } + String format(int number){ + if(number >= 1000000){ + return Strings.toFixed(number / 1000000f, 1) + "[gray]mil[]"; + }else if(number >= 10000){ + return number / 1000 + "[gray]k[]"; + }else if(number >= 1000){ + return Strings.toFixed(number / 1000f, 1) + "[gray]k[]"; + }else{ + return number + ""; + } + } } \ No newline at end of file diff --git a/core/src/io/anuke/mindustry/ui/fragments/ChatFragment.java b/core/src/io/anuke/mindustry/ui/fragments/ChatFragment.java index 153ed15ea5..82d67556ed 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/ChatFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/ChatFragment.java @@ -43,9 +43,9 @@ public class ChatFragment extends Table{ private Array history = new Array<>(); private int historyPos = 0; private int scrollPos = 0; - private Fragment container = new Fragment() { + private Fragment container = new Fragment(){ @Override - public void build(Group parent) { + public void build(Group parent){ scene.add(ChatFragment.this); } }; @@ -67,17 +67,17 @@ public class ChatFragment extends Table{ toggle(); } - if (chatOpen) { - if (Inputs.keyTap("chat_history_prev") && historyPos < history.size - 1) { - if (historyPos == 0) history.set(0, chatfield.getText()); + if(chatOpen){ + if(Inputs.keyTap("chat_history_prev") && historyPos < history.size - 1){ + if(historyPos == 0) history.set(0, chatfield.getText()); historyPos++; updateChat(); } - if (Inputs.keyTap("chat_history_next") && historyPos > 0) { + if(Inputs.keyTap("chat_history_next") && historyPos > 0){ historyPos--; updateChat(); } - scrollPos = (int)Mathf.clamp(scrollPos + Inputs.getAxis("chat_scroll"), 0, Math.max(0, messages.size - messagesShown)); + scrollPos = (int) Mathf.clamp(scrollPos + Inputs.getAxis("chat_scroll"), 0, Math.max(0, messages.size - messagesShown)); } }); @@ -85,7 +85,7 @@ public class ChatFragment extends Table{ setup(); } - public Fragment container() { + public Fragment container(){ return container; } @@ -108,16 +108,16 @@ public class ChatFragment extends Table{ chatfield.setStyle(chatfield.getStyle()); Platform.instance.addDialog(chatfield, Vars.maxTextLength); - bottom().left().marginBottom(offsety).marginLeft(offsetx*2).add(fieldlabel).padBottom(4f); + bottom().left().marginBottom(offsety).marginLeft(offsetx * 2).add(fieldlabel).padBottom(4f); add(chatfield).padBottom(offsety).padLeft(offsetx).growX().padRight(offsetx).height(28); - if(Vars.mobile) { + if(Vars.mobile){ marginBottom(105f); marginRight(240f); } - if(Vars.mobile) { + if(Vars.mobile){ addImageButton("icon-arrow-right", 14 * 2, this::toggle).size(46f, 51f).visible(() -> chatOpen).pad(2f); } } @@ -128,7 +128,7 @@ public class ChatFragment extends Table{ batch.setColor(shadowColor); if(chatOpen) - batch.draw(skin.getRegion("white"), offsetx, chatfield.getY(), chatfield.getWidth() + 15f, chatfield.getHeight()-1); + batch.draw(skin.getRegion("white"), offsetx, chatfield.getY(), chatfield.getWidth() + 15f, chatfield.getHeight() - 1); super.draw(batch, alpha); @@ -143,18 +143,18 @@ public class ChatFragment extends Table{ for(int i = scrollPos; i < messages.size && i < messagesShown + scrollPos && (i < fadetime || chatOpen); i++){ layout.setText(font, messages.get(i).formattedMessage, Color.WHITE, textWidth, Align.bottomLeft, true); - theight += layout.height+textspacing; - if(i - scrollPos == 0) theight -= textspacing+1; + theight += layout.height + textspacing; + if(i - scrollPos == 0) theight -= textspacing + 1; font.getCache().clear(); font.getCache().addText(messages.get(i).formattedMessage, fontoffsetx + offsetx, offsety + theight, textWidth, Align.bottomLeft, true); - if(!chatOpen && fadetime-i < 1f && fadetime-i >= 0f){ - font.getCache().setAlphas(fadetime-i); - batch.setColor(0, 0, 0, shadowColor.a*(fadetime-i)); + if(!chatOpen && fadetime - i < 1f && fadetime - i >= 0f){ + font.getCache().setAlphas(fadetime - i); + batch.setColor(0, 0, 0, shadowColor.a * (fadetime - i)); } - batch.draw(skin.getRegion("white"), offsetx, theight-layout.height-2, textWidth + Unit.dp.scl(4f), layout.height+textspacing); + batch.draw(skin.getRegion("white"), offsetx, theight - layout.height - 2, textWidth + Unit.dp.scl(4f), layout.height + textspacing); batch.setColor(shadowColor); font.getCache().draw(batch); @@ -163,7 +163,7 @@ public class ChatFragment extends Table{ batch.setColor(Color.WHITE); if(fadetime > 0 && !chatOpen) - fadetime -= Timers.delta()/180f; + fadetime -= Timers.delta() / 180f; } private void sendMessage(){ @@ -197,12 +197,12 @@ public class ChatFragment extends Table{ clearChatInput(); } - public void updateChat() { + public void updateChat(){ chatfield.setText(history.get(historyPos)); chatfield.setCursorPosition(chatfield.getText().length()); } - public void clearChatInput() { + public void clearChatInput(){ historyPos = 0; history.set(0, ""); chatfield.setText(""); @@ -234,7 +234,7 @@ public class ChatFragment extends Table{ if(sender == null){ //no sender, this is a server message? formattedMessage = message; }else{ - formattedMessage = "[CORAL][["+sender+"[CORAL]]:[WHITE] "+message; + formattedMessage = "[CORAL][[" + sender + "[CORAL]]:[WHITE] " + message; } } } diff --git a/core/src/io/anuke/mindustry/ui/fragments/DebugFragment.java b/core/src/io/anuke/mindustry/ui/fragments/DebugFragment.java index 8b2b6338ca..0ec1779737 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/DebugFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/DebugFragment.java @@ -27,7 +27,7 @@ import io.anuke.ucore.util.Mathf; import static io.anuke.mindustry.Vars.*; -public class DebugFragment extends Fragment { +public class DebugFragment extends Fragment{ private static StringBuilder log = new StringBuilder(); static{ @@ -35,7 +35,7 @@ public class DebugFragment extends Fragment { @Override public void print(String text, Object... args){ super.print(text, args); - if(log.length() < 1000) { + if(log.length() < 1000){ log.append(Log.format(text, args)); log.append("\n"); } @@ -43,109 +43,6 @@ public class DebugFragment extends Fragment { }); } - @Override - public void build(Group parent){ - - Player player = players[0]; - new table(){{ - visible(() -> debug); - - abottom().aleft(); - - new table("pane"){{ - defaults().fillX().width(100f); - - new label(() -> Gdx.app.getJavaHeap() / 1024 / 1024 + "MB"); - row(); - - new label("Debug"); - row(); - new button("noclip", "toggle", () -> noclip = !noclip); - row(); - new button("items", () -> { - for (int i = 0; i < 10; i++) { - ItemDrop.create(Item.all().random(), 5, player.x, player.y, Mathf.random(360f)); - } - }); - row(); - new button("team", "toggle", player::toggleTeam); - row(); - new button("blocks", "toggle", () -> showBlockDebug = !showBlockDebug); - row(); - new button("fog", () -> showFog = !showFog); - row(); - new button("gameover", () ->{ - state.teams.get(Team.blue).cores.get(0).entity.health = 0; - state.teams.get(Team.blue).cores.get(0).entity.damage(1); - }); - row(); - new button("wave", () -> state.wavetime = 0f); - row(); - new button("death", () -> player.damage(99999, true)); - row(); - new button("spawn", () -> { - FloatingDialog dialog = new FloatingDialog("debug spawn"); - for(UnitType type : UnitType.all()){ - dialog.content().addImageButton("white", 40, () -> { - BaseUnit unit = type.create(player.getTeam()); - unit.inventory.addAmmo(type.weapon.getAmmoType(type.weapon.getAcceptedItems().iterator().next())); - unit.setWave(); - unit.set(player.x, player.y); - unit.add(); - }).get().getStyle().imageUp = new TextureRegionDrawable(type.iconRegion); - } - dialog.addCloseButton(); - dialog.setFillParent(false); - dialog.show(); - }); - row(); - }}.end(); - - row(); - - }}.end(); - - - new table(){{ - visible(() -> console); - - atop().aleft(); - - new table("pane") {{ - defaults().fillX(); - - ScrollPane pane = new ScrollPane(new Label(DebugFragment::debugInfo), "clear"); - - add(pane); - row(); - new button("dump", () -> { - try{ - FileHandle file = Gdx.files.local("packet-dump.txt"); - file.writeString("--INFO--\n", false); - file.writeString(debugInfo(), true); - file.writeString("--LOG--\n\n", true); - file.writeString(log.toString(), true); - }catch (Exception e){ - ui.showError("Error dumping log."); - } - }); - }}.end(); - }}.end(); - - new table(){{ - visible(() -> console); - - atop(); - - Table table = new Table("pane"); - table.label(() -> log.toString()); - - ScrollPane pane = new ScrollPane(table, "clear"); - - get().add(pane); - }}.end(); - } - public static void printDebugInfo(){ Gdx.app.error("Minudstry Info Dump", debugInfo()); } @@ -166,9 +63,9 @@ public class DebugFragment extends Fragment { "units: " + totalUnits, "bullets: " + bulletGroup.size(), Net.client() ? - "chat.open: " + ui.chatfrag.chatOpen() + "\n" + - "chat.messages: " + ui.chatfrag.getMessagesSize() + "\n" + - "client.connecting: " + netClient.isConnecting() + "\n" : "", + "chat.open: " + ui.chatfrag.chatOpen() + "\n" + + "chat.messages: " + ui.chatfrag.getMessagesSize() + "\n" + + "client.connecting: " + netClient.isConnecting() + "\n" : "", "players: " + playerGroup.size(), "tiles: " + tileGroup.size(), "tiles.sleeping: " + TileEntity.sleepingEntities, @@ -212,10 +109,113 @@ public class DebugFragment extends Fragment { private static StringBuilder join(String... strings){ StringBuilder builder = new StringBuilder(); - for (String string : strings) { + for(String string : strings){ builder.append(string); builder.append("\n"); } return builder; } + + @Override + public void build(Group parent){ + + Player player = players[0]; + new table(){{ + visible(() -> debug); + + abottom().aleft(); + + new table("pane"){{ + defaults().fillX().width(100f); + + new label(() -> Gdx.app.getJavaHeap() / 1024 / 1024 + "MB"); + row(); + + new label("Debug"); + row(); + new button("noclip", "toggle", () -> noclip = !noclip); + row(); + new button("items", () -> { + for(int i = 0; i < 10; i++){ + ItemDrop.create(Item.all().random(), 5, player.x, player.y, Mathf.random(360f)); + } + }); + row(); + new button("team", "toggle", player::toggleTeam); + row(); + new button("blocks", "toggle", () -> showBlockDebug = !showBlockDebug); + row(); + new button("fog", () -> showFog = !showFog); + row(); + new button("gameover", () -> { + state.teams.get(Team.blue).cores.get(0).entity.health = 0; + state.teams.get(Team.blue).cores.get(0).entity.damage(1); + }); + row(); + new button("wave", () -> state.wavetime = 0f); + row(); + new button("death", () -> player.damage(99999, true)); + row(); + new button("spawn", () -> { + FloatingDialog dialog = new FloatingDialog("debug spawn"); + for(UnitType type : UnitType.all()){ + dialog.content().addImageButton("white", 40, () -> { + BaseUnit unit = type.create(player.getTeam()); + unit.inventory.addAmmo(type.weapon.getAmmoType(type.weapon.getAcceptedItems().iterator().next())); + unit.setWave(); + unit.set(player.x, player.y); + unit.add(); + }).get().getStyle().imageUp = new TextureRegionDrawable(type.iconRegion); + } + dialog.addCloseButton(); + dialog.setFillParent(false); + dialog.show(); + }); + row(); + }}.end(); + + row(); + + }}.end(); + + + new table(){{ + visible(() -> console); + + atop().aleft(); + + new table("pane"){{ + defaults().fillX(); + + ScrollPane pane = new ScrollPane(new Label(DebugFragment::debugInfo), "clear"); + + add(pane); + row(); + new button("dump", () -> { + try{ + FileHandle file = Gdx.files.local("packet-dump.txt"); + file.writeString("--INFO--\n", false); + file.writeString(debugInfo(), true); + file.writeString("--LOG--\n\n", true); + file.writeString(log.toString(), true); + }catch(Exception e){ + ui.showError("Error dumping log."); + } + }); + }}.end(); + }}.end(); + + new table(){{ + visible(() -> console); + + atop(); + + Table table = new Table("pane"); + table.label(() -> log.toString()); + + ScrollPane pane = new ScrollPane(table, "clear"); + + get().add(pane); + }}.end(); + } } diff --git a/core/src/io/anuke/mindustry/ui/fragments/Fragment.java b/core/src/io/anuke/mindustry/ui/fragments/Fragment.java index f28b87f8dc..080c2c4a05 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/Fragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/Fragment.java @@ -3,5 +3,5 @@ package io.anuke.mindustry.ui.fragments; import io.anuke.ucore.scene.Group; public abstract class Fragment{ - public abstract void build(Group parent); + public abstract void build(Group parent); } diff --git a/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java b/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java index 8fb5334a82..f0980dac14 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java @@ -34,316 +34,318 @@ import io.anuke.ucore.util.Bundles; import static io.anuke.mindustry.Vars.*; public class HudFragment extends Fragment{ - public final BlocksFragment blockfrag = new BlocksFragment(); + public final BlocksFragment blockfrag = new BlocksFragment(); - private ImageButton menu, flip; - private Table respawntable; - private Table wavetable; - private Table infolabel; - private Table lastUnlockTable; - private Table lastUnlockLayout; - private boolean shown = true; - private float dsize = 58; - private float isize = 40; + private ImageButton menu, flip; + private Table respawntable; + private Table wavetable; + private Table infolabel; + private Table lastUnlockTable; + private Table lastUnlockLayout; + private boolean shown = true; + private float dsize = 58; + private float isize = 40; - public void build(Group parent){ + public void build(Group parent){ - //menu at top left - new table(){{ - atop(); - aleft(); + //menu at top left + new table(){{ + atop(); + aleft(); - new table(){{ + new table(){{ - new table() {{ - left(); - defaults().size(dsize).left(); + new table(){{ + left(); + defaults().size(dsize).left(); - menu = new imagebutton("icon-menu", isize, ui.paused::show).get(); - flip = new imagebutton("icon-arrow-up", isize, () -> toggleMenus()).get(); + menu = new imagebutton("icon-menu", isize, ui.paused::show).get(); + flip = new imagebutton("icon-arrow-up", isize, () -> toggleMenus()).get(); - update(t -> { - if(Inputs.keyTap("toggle_menus") && !ui.chatfrag.chatOpen()){ - toggleMenus(); - } - }); + update(t -> { + if(Inputs.keyTap("toggle_menus") && !ui.chatfrag.chatOpen()){ + toggleMenus(); + } + }); - new imagebutton("icon-pause", isize, () -> { - if (Net.active()) { - ui.listfrag.toggle(); - } else { - state.set(state.is(State.paused) ? State.playing : State.paused); - } - }).update(i -> { - if (Net.active()) { - i.getStyle().imageUp = Core.skin.getDrawable("icon-players"); - } else { - i.setDisabled(Net.active()); - i.getStyle().imageUp = Core.skin.getDrawable(state.is(State.paused) ? "icon-play" : "icon-pause"); - } - }).get(); + new imagebutton("icon-pause", isize, () -> { + if(Net.active()){ + ui.listfrag.toggle(); + }else{ + state.set(state.is(State.paused) ? State.playing : State.paused); + } + }).update(i -> { + if(Net.active()){ + i.getStyle().imageUp = Core.skin.getDrawable("icon-players"); + }else{ + i.setDisabled(Net.active()); + i.getStyle().imageUp = Core.skin.getDrawable(state.is(State.paused) ? "icon-play" : "icon-pause"); + } + }).get(); - new imagebutton("icon-settings", isize, () -> { - if (Net.active() && mobile) { - if (ui.chatfrag.chatOpen()) { - ui.chatfrag.hide(); - } else { - ui.chatfrag.toggle(); - } - } else { - ui.settings.show(); - } - }).update(i -> { - if (Net.active() && mobile) { - i.getStyle().imageUp = Core.skin.getDrawable("icon-chat"); - } else { - i.getStyle().imageUp = Core.skin.getDrawable("icon-settings"); - } - }).get(); + new imagebutton("icon-settings", isize, () -> { + if(Net.active() && mobile){ + if(ui.chatfrag.chatOpen()){ + ui.chatfrag.hide(); + }else{ + ui.chatfrag.toggle(); + } + }else{ + ui.settings.show(); + } + }).update(i -> { + if(Net.active() && mobile){ + i.getStyle().imageUp = Core.skin.getDrawable("icon-chat"); + }else{ + i.getStyle().imageUp = Core.skin.getDrawable("icon-settings"); + } + }).get(); - }}.end(); + }}.end(); - row(); + row(); - new table() {{ - touchable(Touchable.enabled); - addWaveTable(); - }}.fillX().end(); + new table(){{ + touchable(Touchable.enabled); + addWaveTable(); + }}.fillX().end(); - row(); + row(); - visible(() -> !state.is(State.menu)); - row(); - new table(){{ - IntFormat fps = new IntFormat("text.fps"); - IntFormat tps = new IntFormat("text.tps"); - IntFormat ping = new IntFormat("text.ping"); - new label(() -> fps.get(Gdx.graphics.getFramesPerSecond())).padRight(10); - new label(() -> tps.get(threads.getTPS())).visible(() -> threads.isEnabled()); - row(); - new label(() -> ping.get(Net.getPing())).visible(() -> Net.client() && !gwt).colspan(2); + visible(() -> !state.is(State.menu)); + row(); + new table(){{ + IntFormat fps = new IntFormat("text.fps"); + IntFormat tps = new IntFormat("text.tps"); + IntFormat ping = new IntFormat("text.ping"); + new label(() -> fps.get(Gdx.graphics.getFramesPerSecond())).padRight(10); + new label(() -> tps.get(threads.getTPS())).visible(() -> threads.isEnabled()); + row(); + new label(() -> ping.get(Net.getPing())).visible(() -> Net.client() && !gwt).colspan(2); - infolabel = get(); - }}.size(-1).end().visible(() -> Settings.getBool("fps")); + infolabel = get(); + }}.size(-1).end().visible(() -> Settings.getBool("fps")); - }}.end(); - }}.end(); + }}.end(); + }}.end(); - new table(){{ - visible(() -> !state.is(State.menu)); - atop(); - aright(); + new table(){{ + visible(() -> !state.is(State.menu)); + atop(); + aright(); - Minimap minimap = new Minimap(); + Minimap minimap = new Minimap(); - add(minimap).visible(() -> Settings.getBool("minimap")); - }}.end(); + add(minimap).visible(() -> Settings.getBool("minimap")); + }}.end(); - //paused table - new table(){{ - visible(() -> state.is(State.paused) && !Net.active()); - atop(); + //paused table + new table(){{ + visible(() -> state.is(State.paused) && !Net.active()); + atop(); - new table("pane"){{ - new label("[orange]< "+ Bundles.get("text.paused") + " >").scale(0.75f).pad(6); - }}.end(); - }}.end(); + new table("pane"){{ + new label("[orange]< " + Bundles.get("text.paused") + " >").scale(0.75f).pad(6); + }}.end(); + }}.end(); - //respawn background table - new table("white"){{ - respawntable = get(); - respawntable.setColor(Color.CLEAR); - update(t -> { - if(state.is(State.menu)){ - respawntable.setColor(Color.CLEAR); - } - }); - }}.end(); + //respawn background table + new table("white"){{ + respawntable = get(); + respawntable.setColor(Color.CLEAR); + update(t -> { + if(state.is(State.menu)){ + respawntable.setColor(Color.CLEAR); + } + }); + }}.end(); - new table(){{ - abottom(); - visible(() -> !state.is(State.menu) && control.getSaves().isSaving()); + new table(){{ + abottom(); + visible(() -> !state.is(State.menu) && control.getSaves().isSaving()); - new label("$text.saveload"); + new label("$text.saveload"); - }}.end(); + }}.end(); - blockfrag.build(Core.scene.getRoot()); - } + blockfrag.build(Core.scene.getRoot()); + } - /**Show unlock notification for a new recipe.*/ - public void showUnlock(Recipe recipe){ - blockfrag.rebuild(); + /** + * Show unlock notification for a new recipe. + */ + public void showUnlock(Recipe recipe){ + blockfrag.rebuild(); - //if there's currently no unlock notification... - if(lastUnlockTable == null) { - Table table = new Table("button"); - table.update(() -> { - if(state.is(State.menu)){ - table.remove(); - lastUnlockLayout = null; - lastUnlockTable = null; - } - }); - table.margin(12); + //if there's currently no unlock notification... + if(lastUnlockTable == null){ + Table table = new Table("button"); + table.update(() -> { + if(state.is(State.menu)){ + table.remove(); + lastUnlockLayout = null; + lastUnlockTable = null; + } + }); + table.margin(12); - Table in = new Table(); + Table in = new Table(); - //create texture stack for displaying - Stack stack = new Stack(); - for (TextureRegion region : recipe.result.getCompactIcon()) { - Image image = new Image(region); - image.setScaling(Scaling.fit); - stack.add(image); - } + //create texture stack for displaying + Stack stack = new Stack(); + for(TextureRegion region : recipe.result.getCompactIcon()){ + Image image = new Image(region); + image.setScaling(Scaling.fit); + stack.add(image); + } - in.add(stack).size(48f).pad(2); + in.add(stack).size(48f).pad(2); - //add to table - table.add(in).padRight(8); - table.add("$text.unlocked"); - table.pack(); + //add to table + table.add(in).padRight(8); + table.add("$text.unlocked"); + table.pack(); - //create container table which will align and move - Table container = Core.scene.table(); - container.top().add(table); - container.setTranslation(0, table.getPrefHeight()); - container.actions(Actions.translateBy(0, -table.getPrefHeight(), 1f, Interpolation.fade), Actions.delay(4f), - //nesting actions() calls is necessary so the right prefHeight() is used - Actions.run(() -> container.actions(Actions.translateBy(0, table.getPrefHeight(), 1f, Interpolation.fade), Actions.run(() ->{ - lastUnlockTable = null; - lastUnlockLayout = null; - }), Actions.removeActor()))); + //create container table which will align and move + Table container = Core.scene.table(); + container.top().add(table); + container.setTranslation(0, table.getPrefHeight()); + container.actions(Actions.translateBy(0, -table.getPrefHeight(), 1f, Interpolation.fade), Actions.delay(4f), + //nesting actions() calls is necessary so the right prefHeight() is used + Actions.run(() -> container.actions(Actions.translateBy(0, table.getPrefHeight(), 1f, Interpolation.fade), Actions.run(() -> { + lastUnlockTable = null; + lastUnlockLayout = null; + }), Actions.removeActor()))); - lastUnlockTable = container; - lastUnlockLayout = in; - }else{ - //max column size - int col = 3; - //max amount of elements minus extra 'plus' - int cap = col*col-1; + lastUnlockTable = container; + lastUnlockLayout = in; + }else{ + //max column size + int col = 3; + //max amount of elements minus extra 'plus' + int cap = col * col - 1; - //get old elements - Array elements = new Array<>(lastUnlockLayout.getChildren()); - int esize = elements.size; + //get old elements + Array elements = new Array<>(lastUnlockLayout.getChildren()); + int esize = elements.size; - //...if it's already reached the cap, ignore everything - if(esize > cap) return; + //...if it's already reached the cap, ignore everything + if(esize > cap) return; - //get size of each element - float size = 48f / Math.min(elements.size + 1, col); + //get size of each element + float size = 48f / Math.min(elements.size + 1, col); - //correct plurals if needed - if(esize == 1){ - ((Label)lastUnlockLayout.getParent().find(e -> e instanceof Label)).setText("$text.unlocked.plural"); - } + //correct plurals if needed + if(esize == 1){ + ((Label) lastUnlockLayout.getParent().find(e -> e instanceof Label)).setText("$text.unlocked.plural"); + } - lastUnlockLayout.clearChildren(); - lastUnlockLayout.defaults().size(size).pad(2); + lastUnlockLayout.clearChildren(); + lastUnlockLayout.defaults().size(size).pad(2); - for(int i = 0; i < esize && i <= cap; i ++){ - lastUnlockLayout.add(elements.get(i)); + for(int i = 0; i < esize && i <= cap; i++){ + lastUnlockLayout.add(elements.get(i)); - if(i % col == col - 1){ - lastUnlockLayout.row(); - } - } + if(i % col == col - 1){ + lastUnlockLayout.row(); + } + } - //if there's space, add it - if(esize < cap) { + //if there's space, add it + if(esize < cap){ - Stack stack = new Stack(); - for (TextureRegion region : recipe.result.getCompactIcon()) { - Image image = new Image(region); - image.setScaling(Scaling.fit); - stack.add(image); - } + Stack stack = new Stack(); + for(TextureRegion region : recipe.result.getCompactIcon()){ + Image image = new Image(region); + image.setScaling(Scaling.fit); + stack.add(image); + } - lastUnlockLayout.add(stack); - }else{ //else, add a specific icon to denote no more space - lastUnlockLayout.addImage("icon-add"); - } + lastUnlockLayout.add(stack); + }else{ //else, add a specific icon to denote no more space + lastUnlockLayout.addImage("icon-add"); + } - lastUnlockLayout.pack(); - } - } + lastUnlockLayout.pack(); + } + } - private void toggleMenus(){ - wavetable.clearActions(); - infolabel.clearActions(); + private void toggleMenus(){ + wavetable.clearActions(); + infolabel.clearActions(); - float dur = 0.3f; - Interpolation in = Interpolation.pow3Out; + float dur = 0.3f; + Interpolation in = Interpolation.pow3Out; - flip.getStyle().imageUp = Core.skin.getDrawable(shown ? "icon-arrow-down" : "icon-arrow-up"); + flip.getStyle().imageUp = Core.skin.getDrawable(shown ? "icon-arrow-down" : "icon-arrow-up"); - if (shown) { - shown = false; - blockfrag.toggle(false, dur, in); - wavetable.actions(Actions.translateBy(0, (wavetable.getHeight() + dsize) - wavetable.getTranslation().y, dur, in)); - infolabel.actions(Actions.translateBy(0, (wavetable.getHeight()) - wavetable.getTranslation().y, dur, in)); - } else { - shown = true; - blockfrag.toggle(true, dur, in); - wavetable.actions(Actions.translateBy(0, -wavetable.getTranslation().y, dur, in)); - infolabel.actions(Actions.translateBy(0, -infolabel.getTranslation().y, dur, in)); - } - } + if(shown){ + shown = false; + blockfrag.toggle(false, dur, in); + wavetable.actions(Actions.translateBy(0, (wavetable.getHeight() + dsize) - wavetable.getTranslation().y, dur, in)); + infolabel.actions(Actions.translateBy(0, (wavetable.getHeight()) - wavetable.getTranslation().y, dur, in)); + }else{ + shown = true; + blockfrag.toggle(true, dur, in); + wavetable.actions(Actions.translateBy(0, -wavetable.getTranslation().y, dur, in)); + infolabel.actions(Actions.translateBy(0, -infolabel.getTranslation().y, dur, in)); + } + } - private String getEnemiesRemaining() { - int enemies = unitGroups[Team.red.ordinal()].size(); - if(enemies == 1) { - return Bundles.format("text.enemies.single", enemies); - } else { - return Bundles.format("text.enemies", enemies); - } - } + private String getEnemiesRemaining(){ + int enemies = unitGroups[Team.red.ordinal()].size(); + if(enemies == 1){ + return Bundles.format("text.enemies.single", enemies); + }else{ + return Bundles.format("text.enemies", enemies); + } + } - private void addWaveTable(){ - float uheight = 66f; + private void addWaveTable(){ + float uheight = 66f; - IntFormat wavef = new IntFormat("text.wave"); - IntFormat timef = new IntFormat("text.wave.waiting"); + IntFormat wavef = new IntFormat("text.wave"); + IntFormat timef = new IntFormat("text.wave.waiting"); - wavetable = new table("button"){{ - aleft(); - new table(){{ - aleft(); + wavetable = new table("button"){{ + aleft(); + new table(){{ + aleft(); - new label(() -> wavef.get(state.wave)).scale(fontScale *1.5f).left().padLeft(-6); + new label(() -> wavef.get(state.wave)).scale(fontScale * 1.5f).left().padLeft(-6); - row(); + row(); - new label(() -> unitGroups[Team.red.ordinal()].size() > 0 && state.mode.disableWaveTimer ? - getEnemiesRemaining() : - (state.mode.disableWaveTimer) ? "$text.waiting" - : timef.get((int) (state.wavetime / 60f))) - .minWidth(126).padLeft(-6).left(); + new label(() -> unitGroups[Team.red.ordinal()].size() > 0 && state.mode.disableWaveTimer ? + getEnemiesRemaining() : + (state.mode.disableWaveTimer) ? "$text.waiting" + : timef.get((int) (state.wavetime / 60f))) + .minWidth(126).padLeft(-6).left(); - margin(10f); - get().marginLeft(6); - }}.left().end(); + margin(10f); + get().marginLeft(6); + }}.left().end(); - add().growX(); + add().growX(); - playButton(uheight); - }}.height(uheight).fillX().expandX().end().get(); - wavetable.getParent().getParent().swapActor(wavetable.getParent(), menu.getParent()); - } + playButton(uheight); + }}.height(uheight).fillX().expandX().end().get(); + wavetable.getParent().getParent().swapActor(wavetable.getParent(), menu.getParent()); + } - private void playButton(float uheight){ - new imagebutton("icon-play", 30f, () -> { - if(Net.client() && players[0].isAdmin){ - Call.onAdminRequest(players[0], AdminAction.wave); - }else { - state.wavetime = 0f; - } - }).height(uheight).fillX().right().padTop(-8f).padBottom(-12f).padLeft(-15).padRight(-10).width(40f).update(l->{ - boolean vis = state.mode.disableWaveTimer && ((Net.server() || players[0].isAdmin) || !Net.active()); - boolean paused = state.is(State.paused) || !vis; + private void playButton(float uheight){ + new imagebutton("icon-play", 30f, () -> { + if(Net.client() && players[0].isAdmin){ + Call.onAdminRequest(players[0], AdminAction.wave); + }else{ + state.wavetime = 0f; + } + }).height(uheight).fillX().right().padTop(-8f).padBottom(-12f).padLeft(-15).padRight(-10).width(40f).update(l -> { + boolean vis = state.mode.disableWaveTimer && ((Net.server() || players[0].isAdmin) || !Net.active()); + boolean paused = state.is(State.paused) || !vis; - l.getStyle().imageUp = Core.skin.getDrawable(vis ? "icon-play" : "clear"); - l.setTouchable(!paused ? Touchable.enabled : Touchable.disabled); - }).visible(() -> state.mode.disableWaveTimer && ((Net.server() || players[0].isAdmin) || !Net.active()) && unitGroups[Team.red.ordinal()].size() == 0); - } + l.getStyle().imageUp = Core.skin.getDrawable(vis ? "icon-play" : "clear"); + l.setTouchable(!paused ? Touchable.enabled : Touchable.disabled); + }).visible(() -> state.mode.disableWaveTimer && ((Net.server() || players[0].isAdmin) || !Net.active()) && unitGroups[Team.red.ordinal()].size() == 0); + } } diff --git a/core/src/io/anuke/mindustry/ui/fragments/LoadingFragment.java b/core/src/io/anuke/mindustry/ui/fragments/LoadingFragment.java index 3e41e10d54..4104939c22 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/LoadingFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/LoadingFragment.java @@ -10,14 +10,14 @@ import io.anuke.ucore.scene.ui.Label; import io.anuke.ucore.scene.ui.TextButton; import io.anuke.ucore.scene.ui.layout.Table; -public class LoadingFragment extends Fragment { +public class LoadingFragment extends Fragment{ private Table table; private TextButton button; @Override - public void build(Group parent) { + public void build(Group parent){ - table = new table("loadDim"){{ + table = new table("loadDim"){{ add().height(70f).row(); touchable(Touchable.enabled); @@ -33,7 +33,8 @@ public class LoadingFragment extends Fragment { row(); - button = get().addButton("$text.cancel", () -> {}).pad(20).size(250f, 70f).get(); + button = get().addButton("$text.cancel", () -> { + }).pad(20).size(250f, 70f).get(); button.setVisible(false); }}.end().get(); diff --git a/core/src/io/anuke/mindustry/ui/fragments/MenuFragment.java b/core/src/io/anuke/mindustry/ui/fragments/MenuFragment.java index 2a9834943f..228015ea52 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/MenuFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/MenuFragment.java @@ -18,175 +18,175 @@ import io.anuke.ucore.scene.ui.layout.Table; import static io.anuke.mindustry.Vars.*; public class MenuFragment extends Fragment{ - private Table mobileContainer; + private Table mobileContainer; - @Override - public void build(Group parent){ - new table(){{ - visible(() -> state.is(State.menu)); + @Override + public void build(Group parent){ + new table(){{ + visible(() -> state.is(State.menu)); - if(!mobile){ - buildDesktop(); - }else{ - buildMobile(); + if(!mobile){ + buildDesktop(); + }else{ + buildMobile(); - Events.on(ResizeEvent.class, () -> buildMobile()); - } - }}.end(); + Events.on(ResizeEvent.class, () -> buildMobile()); + } + }}.end(); - //discord icon in top right - if(Platform.instance.hasDiscord()) { - new table() {{ - abottom().atop().aright(); - get().addButton("", "discord", ui.discord::show).size(81, 42); - }}.end().visible(() -> state.is(State.menu)); - } + //discord icon in top right + if(Platform.instance.hasDiscord()){ + new table(){{ + abottom().atop().aright(); + get().addButton("", "discord", ui.discord::show).size(81, 42); + }}.end().visible(() -> state.is(State.menu)); + } - //info icon - if(mobile) { - new table() {{ - abottom().atop().aleft(); - get().addButton("", "info", ui.about::show).size(81, 42); - }}.end().visible(() -> state.is(State.menu)); - } + //info icon + if(mobile){ + new table(){{ + abottom().atop().aleft(); + get().addButton("", "info", ui.about::show).size(81, 42); + }}.end().visible(() -> state.is(State.menu)); + } - //version info - new table(){{ - visible(() -> state.is(State.menu)); - abottom().aleft(); - new label("Mindustry " + Version.code + " " + Version.type + " / " + Version.buildName); - }}.end(); - } + //version info + new table(){{ + visible(() -> state.is(State.menu)); + abottom().aleft(); + new label("Mindustry " + Version.code + " " + Version.type + " / " + Version.buildName); + }}.end(); + } - private void buildMobile(){ - if(mobileContainer == null){ - mobileContainer = build.getTable(); - } + private void buildMobile(){ + if(mobileContainer == null){ + mobileContainer = build.getTable(); + } - mobileContainer.clear(); - mobileContainer.setSize(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); + mobileContainer.clear(); + mobileContainer.setSize(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); - float size = 120f; - float isize = 14f * 4; - mobileContainer.defaults().size(size).pad(5).padTop(4f); + float size = 120f; + float isize = 14f * 4; + mobileContainer.defaults().size(size).pad(5).padTop(4f); - MobileButton - play = new MobileButton("icon-play-2", isize, "$text.play", ui.levels::show), - maps = new MobileButton("icon-map", isize, "$text.maps", ui.maps::show), - load = new MobileButton("icon-load", isize, "$text.load", ui.load::show), - join = new MobileButton("icon-add", isize, "$text.joingame", ui.join::show), - editor = new MobileButton("icon-editor", isize, "$text.editor", () -> ui.loadAnd(ui.editor::show)), - tools = new MobileButton("icon-tools", isize, "$text.settings", ui.settings::show), - unlocks = new MobileButton("icon-unlocks", isize, "$text.unlocks", ui.unlocks::show), - donate = new MobileButton("icon-donate", isize, "$text.donate", Platform.instance::openDonations); + MobileButton + play = new MobileButton("icon-play-2", isize, "$text.play", ui.levels::show), + maps = new MobileButton("icon-map", isize, "$text.maps", ui.maps::show), + load = new MobileButton("icon-load", isize, "$text.load", ui.load::show), + join = new MobileButton("icon-add", isize, "$text.joingame", ui.join::show), + editor = new MobileButton("icon-editor", isize, "$text.editor", () -> ui.loadAnd(ui.editor::show)), + tools = new MobileButton("icon-tools", isize, "$text.settings", ui.settings::show), + unlocks = new MobileButton("icon-unlocks", isize, "$text.unlocks", ui.unlocks::show), + donate = new MobileButton("icon-donate", isize, "$text.donate", Platform.instance::openDonations); - if(Gdx.graphics.getWidth() > Gdx.graphics.getHeight()){ - mobileContainer.add(play); - mobileContainer.add(join); - mobileContainer.add(load); - mobileContainer.add(maps); - mobileContainer.row(); + if(Gdx.graphics.getWidth() > Gdx.graphics.getHeight()){ + mobileContainer.add(play); + mobileContainer.add(join); + mobileContainer.add(load); + mobileContainer.add(maps); + mobileContainer.row(); - mobileContainer.table(table -> { - table.defaults().set(mobileContainer.defaults()); + mobileContainer.table(table -> { + table.defaults().set(mobileContainer.defaults()); - table.add(editor); - table.add(tools); - table.add(unlocks); + table.add(editor); + table.add(tools); + table.add(unlocks); - if(Platform.instance.canDonate()) table.add(donate); - }).colspan(4); - }else{ - mobileContainer.add(play); - mobileContainer.add(maps); - mobileContainer.row(); - mobileContainer.add(load); - mobileContainer.add(join); - mobileContainer.row(); - mobileContainer.add(editor); - mobileContainer.add(tools); - mobileContainer.row(); + if(Platform.instance.canDonate()) table.add(donate); + }).colspan(4); + }else{ + mobileContainer.add(play); + mobileContainer.add(maps); + mobileContainer.row(); + mobileContainer.add(load); + mobileContainer.add(join); + mobileContainer.row(); + mobileContainer.add(editor); + mobileContainer.add(tools); + mobileContainer.row(); - mobileContainer.table(table -> { - table.defaults().set(mobileContainer.defaults()); + mobileContainer.table(table -> { + table.defaults().set(mobileContainer.defaults()); - table.add(unlocks); + table.add(unlocks); - if(Platform.instance.canDonate()) table.add(donate); - }).colspan(2); - } - } + if(Platform.instance.canDonate()) table.add(donate); + }).colspan(2); + } + } - private void buildDesktop(){ - new table(){{ + private void buildDesktop(){ + new table(){{ - float w = 200f; - float bw = w * 2f + 10f; + float w = 200f; + float bw = w * 2f + 10f; - defaults().size(w, 66f).padTop(5).padRight(5); + defaults().size(w, 66f).padTop(5).padRight(5); - add(new MenuButton("icon-play-2", "$text.play", MenuFragment.this::showPlaySelect)).width(bw).colspan(2); + add(new MenuButton("icon-play-2", "$text.play", MenuFragment.this::showPlaySelect)).width(bw).colspan(2); - row(); + row(); - add(new MenuButton("icon-editor", "$text.editor", () -> ui.loadAnd(ui.editor::show))); + add(new MenuButton("icon-editor", "$text.editor", () -> ui.loadAnd(ui.editor::show))); - add(new MenuButton("icon-map", "$text.maps", ui.maps::show)); + add(new MenuButton("icon-map", "$text.maps", ui.maps::show)); - row(); + row(); - add(new MenuButton("icon-info", "$text.about.button", ui.about::show)); + add(new MenuButton("icon-info", "$text.about.button", ui.about::show)); - add(new MenuButton("icon-tools", "$text.settings", ui.settings::show)); + add(new MenuButton("icon-tools", "$text.settings", ui.settings::show)); - row(); + row(); - add(new MenuButton("icon-menu", "$text.changelog.title", ui.changelog::show)); + add(new MenuButton("icon-menu", "$text.changelog.title", ui.changelog::show)); - add(new MenuButton("icon-unlocks", "$text.unlocks", ui.unlocks::show)); + add(new MenuButton("icon-unlocks", "$text.unlocks", ui.unlocks::show)); - row(); + row(); - if(!gwt){ - add(new MenuButton("icon-exit", "$text.quit", Gdx.app::exit)).width(bw).colspan(2); - } + if(!gwt){ + add(new MenuButton("icon-exit", "$text.quit", Gdx.app::exit)).width(bw).colspan(2); + } - get().margin(16); - }}.end(); - } + get().margin(16); + }}.end(); + } - private void showPlaySelect(){ - float w = 200f; - float bw = w * 2f + 10f; + private void showPlaySelect(){ + float w = 200f; + float bw = w * 2f + 10f; - FloatingDialog dialog = new FloatingDialog("$text.play"); - dialog.addCloseButton(); - dialog.content().defaults().height(66f).width(w).padRight(5f); + FloatingDialog dialog = new FloatingDialog("$text.play"); + dialog.addCloseButton(); + dialog.content().defaults().height(66f).width(w).padRight(5f); - dialog.content().add(new MenuButton("icon-play-2", "$text.newgame", () -> { - dialog.hide(); - ui.levels.show(); - })).width(bw).colspan(2); - dialog.content().row(); + dialog.content().add(new MenuButton("icon-play-2", "$text.newgame", () -> { + dialog.hide(); + ui.levels.show(); + })).width(bw).colspan(2); + dialog.content().row(); - dialog.content().add(new MenuButton("icon-add", "$text.joingame", () -> { - if(Platform.instance.canJoinGame()){ - ui.join.show(); - dialog.hide(); - }else{ - ui.showInfo("$text.multiplayer.web"); - } - })); + dialog.content().add(new MenuButton("icon-add", "$text.joingame", () -> { + if(Platform.instance.canJoinGame()){ + ui.join.show(); + dialog.hide(); + }else{ + ui.showInfo("$text.multiplayer.web"); + } + })); - dialog.content().add(new MenuButton("icon-tutorial", "$text.tutorial", ()-> ui.showInfo("The tutorial is currently not yet implemented."))); + dialog.content().add(new MenuButton("icon-tutorial", "$text.tutorial", () -> ui.showInfo("The tutorial is currently not yet implemented."))); - dialog.content().row(); + dialog.content().row(); - dialog.content().add(new MenuButton("icon-load", "$text.loadgame", () -> { - ui.load.show(); - dialog.hide(); - })).width(bw).colspan(2); + dialog.content().add(new MenuButton("icon-load", "$text.loadgame", () -> { + ui.load.show(); + dialog.hide(); + })).width(bw).colspan(2); - dialog.show(); - } + dialog.show(); + } } diff --git a/core/src/io/anuke/mindustry/ui/fragments/OverlayFragment.java b/core/src/io/anuke/mindustry/ui/fragments/OverlayFragment.java index 060467f109..fd861518ad 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/OverlayFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/OverlayFragment.java @@ -3,7 +3,9 @@ package io.anuke.mindustry.ui.fragments; import io.anuke.mindustry.input.InputHandler; import io.anuke.ucore.scene.Group; -/**Fragment for displaying overlays such as block inventories. One is created for each input handler.*/ +/** + * Fragment for displaying overlays such as block inventories. One is created for each input handler. + */ public class OverlayFragment extends Fragment{ public final BlockInventoryFragment inv; public final BlockConfigFragment config; diff --git a/core/src/io/anuke/mindustry/ui/fragments/PlayerListFragment.java b/core/src/io/anuke/mindustry/ui/fragments/PlayerListFragment.java index 08f8780a47..584fef1b94 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/PlayerListFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/PlayerListFragment.java @@ -116,26 +116,26 @@ public class PlayerListFragment extends Fragment{ button.labelWrap("[#" + player.color.toString().toUpperCase() + "]" + player.name).width(170f).pad(10); button.add().grow(); - button.addImage("icon-admin").size(14*2).visible(() -> player.isAdmin && !(!player.isLocal && Net.server())).padRight(5); + button.addImage("icon-admin").size(14 * 2).visible(() -> player.isAdmin && !(!player.isLocal && Net.server())).padRight(5); if((Net.server() || players[0].isAdmin) && !player.isLocal && (!player.isAdmin || Net.server())){ button.add().growY(); - float bs = (h + 14)/2f; + float bs = (h + 14) / 2f; button.table(t -> { t.defaults().size(bs - 1, bs + 3); //TODO requests. - t.addImageButton("icon-ban", 14*2, () -> { + t.addImageButton("icon-ban", 14 * 2, () -> { ui.showConfirm("$text.confirm", "$text.confirmban", () -> Call.onAdminRequest(player, AdminAction.ban)); }).padBottom(-5.1f); - t.addImageButton("icon-cancel", 14*2, () -> Call.onAdminRequest(player, AdminAction.kick)).padBottom(-5.1f); + t.addImageButton("icon-cancel", 14 * 2, () -> Call.onAdminRequest(player, AdminAction.kick)).padBottom(-5.1f); t.row(); - t.addImageButton("icon-admin", "toggle", 14*2, () -> { + t.addImageButton("icon-admin", "toggle", 14 * 2, () -> { if(Net.client()) return; String id = netServer.admins.getTraceByID(player.uuid).uuid; @@ -154,7 +154,7 @@ public class PlayerListFragment extends Fragment{ b.setDisabled(Net.client()); }).get().setTouchable(() -> Net.client() ? Touchable.disabled : Touchable.enabled); - t.addImageButton("icon-zoom-small", 14*2, () -> Call.onAdminRequest(player, AdminAction.trace)); + t.addImageButton("icon-zoom-small", 14 * 2, () -> Call.onAdminRequest(player, AdminAction.trace)); }).padRight(12).padTop(-5).padLeft(0).padBottom(-10).size(bs + 10f, bs); diff --git a/core/src/io/anuke/mindustry/world/BarType.java b/core/src/io/anuke/mindustry/world/BarType.java index 332ed5c7a1..e5ed5a6d3b 100644 --- a/core/src/io/anuke/mindustry/world/BarType.java +++ b/core/src/io/anuke/mindustry/world/BarType.java @@ -2,7 +2,7 @@ package io.anuke.mindustry.world; import com.badlogic.gdx.graphics.Color; -public enum BarType { +public enum BarType{ health(Color.SCARLET), inventory(Color.GREEN), power(Color.valueOf("fbeb67")), diff --git a/core/src/io/anuke/mindustry/world/BaseBlock.java b/core/src/io/anuke/mindustry/world/BaseBlock.java index 875de327d1..466996ddae 100644 --- a/core/src/io/anuke/mindustry/world/BaseBlock.java +++ b/core/src/io/anuke/mindustry/world/BaseBlock.java @@ -15,7 +15,7 @@ import io.anuke.ucore.core.Timers; import io.anuke.ucore.util.Mathf; import io.anuke.ucore.util.Translator; -public abstract class BaseBlock { +public abstract class BaseBlock{ public boolean hasItems; public boolean hasLiquids; public boolean hasPower; @@ -34,7 +34,9 @@ public abstract class BaseBlock { return true; } - /**Returns the amount of items this block can accept.*/ + /** + * Returns the amount of items this block can accept. + */ public int acceptStack(Item item, int amount, Tile tile, Unit source){ if(acceptItem(item, tile, tile) && hasItems && source.getTeam() == tile.getTeam()){ return Math.min(getMaximumAccepted(tile, item), amount); @@ -47,25 +49,32 @@ public abstract class BaseBlock { return itemCapacity - tile.entity.items.total(); } - /**Remove a stack from this inventory, and return the amount removed.*/ + /** + * Remove a stack from this inventory, and return the amount removed. + */ public int removeStack(Tile tile, Item item, int amount){ tile.entity.wakeUp(); tile.entity.items.remove(item, amount); return amount; } - /**Handle a stack input.*/ + /** + * Handle a stack input. + */ public void handleStack(Item item, int amount, Tile tile, Unit source){ tile.entity.wakeUp(); tile.entity.items.add(item, amount); } - /**Returns offset for stack placement.*/ + /** + * Returns offset for stack placement. + */ public void getStackOffset(Item item, Tile tile, Translator trns){ } - public void onProximityUpdate(Tile tile){} + public void onProximityUpdate(Tile tile){ + } public void handleItem(Item item, Tile tile, Tile source){ tile.entity.items.add(item, 1); @@ -89,7 +98,9 @@ public abstract class BaseBlock { return true; } - /**Returns how much power is accepted.*/ + /** + * Returns how much power is accepted. + */ public float addPower(Tile tile, float amount){ float canAccept = Math.min(powerCapacity - tile.entity.power.amount, amount); @@ -102,16 +113,16 @@ public abstract class BaseBlock { Array proximity = tile.entity.proximity(); int dump = tile.getDump(); - for (int i = 0; i < proximity.size; i ++) { + for(int i = 0; i < proximity.size; i++){ incrementDump(tile, proximity.size); Tile other = proximity.get((i + dump) % proximity.size); Tile in = Edges.getFacingEdge(tile, other); - if (other.block().hasLiquids) { + if(other.block().hasLiquids){ float ofract = other.entity.liquids.get(liquid) / other.block().liquidCapacity; float fract = tile.entity.liquids.get(liquid) / liquidCapacity; - if (ofract < fract) tryMoveLiquid(tile, in, other, (fract - ofract) * liquidCapacity / 2f, liquid); + if(ofract < fract) tryMoveLiquid(tile, in, other, (fract - ofract) * liquidCapacity / 2f, liquid); } } @@ -133,36 +144,36 @@ public abstract class BaseBlock { if(next.block().hasLiquids && tile.entity.liquids.get(liquid) > 0f){ - if(next.block().acceptLiquid(next, tile, liquid, 0f)) { + if(next.block().acceptLiquid(next, tile, liquid, 0f)){ float ofract = next.entity.liquids.get(liquid) / next.block().liquidCapacity; float fract = tile.entity.liquids.get(liquid) / liquidCapacity; float flow = Math.min(Mathf.clamp((fract - ofract) * (1f)) * (liquidCapacity), tile.entity.liquids.get(liquid)); flow = Math.min(flow, next.block().liquidCapacity - next.entity.liquids.get(liquid) - 0.001f); - if (flow > 0f && ofract <= fract && next.block().acceptLiquid(next, tile, liquid, flow)) { + if(flow > 0f && ofract <= fract && next.block().acceptLiquid(next, tile, liquid, flow)){ next.block().handleLiquid(next, tile, liquid, flow); tile.entity.liquids.remove(liquid, flow); return flow; - } else if (ofract > 0.1f && fract > 0.1f) { + }else if(ofract > 0.1f && fract > 0.1f){ Liquid other = next.entity.liquids.current(); - if ((other.flammability > 0.3f && liquid.temperature > 0.7f) || - (liquid.flammability > 0.3f && other.temperature > 0.7f)) { + if((other.flammability > 0.3f && liquid.temperature > 0.7f) || + (liquid.flammability > 0.3f && other.temperature > 0.7f)){ tile.entity.damage(1 * Timers.delta()); next.entity.damage(1 * Timers.delta()); - if (Mathf.chance(0.1 * Timers.delta())) { + if(Mathf.chance(0.1 * Timers.delta())){ Effects.effect(EnvironmentFx.fire, (tile.worldx() + next.worldx()) / 2f, (tile.worldy() + next.worldy()) / 2f); } - } else if ((liquid.temperature > 0.7f && other.temperature < 0.55f) || - (other.temperature > 0.7f && liquid.temperature < 0.55f)) { + }else if((liquid.temperature > 0.7f && other.temperature < 0.55f) || + (other.temperature > 0.7f && liquid.temperature < 0.55f)){ tile.entity.liquids.remove(liquid, Math.min(tile.entity.liquids.get(liquid), 0.7f * Timers.delta())); - if (Mathf.chance(0.2f * Timers.delta())) { + if(Mathf.chance(0.2f * Timers.delta())){ Effects.effect(EnvironmentFx.steam, (tile.worldx() + next.worldx()) / 2f, (tile.worldy() + next.worldy()) / 2f); } } } } }else if(leak && !next.block().solid && !next.block().hasLiquids){ - float leakAmount = tile.entity.liquids.get(liquid)/1.5f; + float leakAmount = tile.entity.liquids.get(liquid) / 1.5f; Puddle.deposit(next, tile, liquid, leakAmount); tile.entity.liquids.remove(liquid, leakAmount); } @@ -171,12 +182,13 @@ public abstract class BaseBlock { /** * Tries to put this item into a nearby container, if there are no available - * containers, it gets added to the block's inventory.*/ + * containers, it gets added to the block's inventory. + */ public void offloadNear(Tile tile, Item item){ Array proximity = tile.entity.proximity(); int dump = tile.getDump(); - for(int i = 0; i < proximity.size; i ++){ + for(int i = 0; i < proximity.size; i++){ incrementDump(tile, proximity.size); Tile other = proximity.get((i + dump) % proximity.size); Tile in = Edges.getFacingEdge(tile, other); @@ -189,32 +201,38 @@ public abstract class BaseBlock { handleItem(item, tile, tile); } - /**Try dumping any item near the tile.*/ + /** + * Try dumping any item near the tile. + */ public boolean tryDump(Tile tile){ return tryDump(tile, null); } - /**Try dumping a specific item near the tile. - * @param todump Item to dump. Can be null to dump anything.*/ + /** + * Try dumping a specific item near the tile. + * + * @param todump Item to dump. Can be null to dump anything. + */ public boolean tryDump(Tile tile, Item todump){ TileEntity entity = tile.entity; - if(entity == null || !hasItems || tile.entity.items.total() == 0 || (todump != null && !entity.items.has(todump))) return false; + if(entity == null || !hasItems || tile.entity.items.total() == 0 || (todump != null && !entity.items.has(todump))) + return false; Array proximity = entity.proximity(); int dump = tile.getDump(); if(proximity.size == 0) return false; - for(int i = 0; i < proximity.size; i ++){ + for(int i = 0; i < proximity.size; i++){ Tile other = proximity.get((i + dump) % proximity.size); Tile in = Edges.getFacingEdge(tile, other); - if(todump == null) { + if(todump == null){ - for (int ii = 0; ii < Item.all().size; ii++) { + for(int ii = 0; ii < Item.all().size; ii++){ Item item = Item.getByID(ii); - if (entity.items.has(item) && other.block().acceptItem(item, other, in) && canDump(tile, other, item)) { + if(entity.items.has(item) && other.block().acceptItem(item, other, in) && canDump(tile, other, item)){ other.block().handleItem(item, other, in); tile.entity.items.remove(item, 1); incrementDump(tile, proximity.size); @@ -223,7 +241,7 @@ public abstract class BaseBlock { } }else{ - if (other.block().acceptItem(todump, other, in) && canDump(tile, other, todump)) { + if(other.block().acceptItem(todump, other, in) && canDump(tile, other, todump)){ other.block().handleItem(todump, other, in); tile.entity.items.remove(todump, 1); incrementDump(tile, proximity.size); @@ -238,10 +256,12 @@ public abstract class BaseBlock { } private void incrementDump(Tile tile, int prox){ - tile.setDump((byte)((tile.getDump() + 1) % prox)); + tile.setDump((byte) ((tile.getDump() + 1) % prox)); } - /**Used for dumping items.*/ + /** + * Used for dumping items. + */ public boolean canDump(Tile tile, Tile to, Item item){ return true; } diff --git a/core/src/io/anuke/mindustry/world/Block.java b/core/src/io/anuke/mindustry/world/Block.java index 5e2b2631a5..a8961072a4 100644 --- a/core/src/io/anuke/mindustry/world/Block.java +++ b/core/src/io/anuke/mindustry/world/Block.java @@ -33,352 +33,391 @@ import static io.anuke.mindustry.Vars.tilesize; import static io.anuke.mindustry.Vars.world; public class Block extends BaseBlock implements Content{ - private static int lastid; - private static Array blocks = new Array<>(140); - private static ObjectMap map = new ObjectMap<>(); + private static int lastid; + private static Array blocks = new Array<>(140); + private static ObjectMap map = new ObjectMap<>(); + /** internal name */ + public final String name; + /** internal ID */ + public final int id; + /** display name */ + public final String formalName; + /** Detailed description of the block. Can be as long as necesary. */ + public final String fullDescription; + /** whether this block has a tile entity that updates */ + public boolean update; + /** whether this block has health and can be destroyed */ + public boolean destructible; + /** if true, this block cannot be broken by normal means. */ + public boolean unbreakable; + /** whether this is solid */ + public boolean solid; + /** whether this block CAN be solid. */ + public boolean solidifes; + /** whether this is rotateable */ + public boolean rotate; + /** whether you can break this with rightclick */ + public boolean breakable; + /** whether this floor can be placed on. */ + public boolean placeableOn = true; + /** tile entity health */ + public int health = -1; + /** base block explosiveness */ + public float baseExplosiveness = 0f; + /** whether this block can be placed on liquids. */ + public boolean floating = true; + /** stuff that drops when broken */ + public ItemStack drops = null; + /** multiblock size */ + public int size = 1; + /** Whether to draw this block in the expanded draw range. */ + public boolean expanded = false; + /** Max of timers used. */ + public int timers = 0; + /** Cache layer. Only used for 'cached' rendering. */ + public CacheLayer cacheLayer = CacheLayer.normal; + /** Layer to draw extra stuff on. */ + public Layer layer = null; + /** Extra layer to draw extra extra stuff on. */ + public Layer layer2 = null; + /** whether this block can be replaced in all cases */ + public boolean alwaysReplace = false; + /** whether this block has instant transfer checking. used for calculations to prevent infinite loops. */ + public boolean instantTransfer = false; + /** The block group. Unless {@link #canReplace} is overriden, blocks in the same group can replace each other. */ + public BlockGroup group = BlockGroup.none; + /** list of displayed block status bars. Defaults to health bar. */ + public BlockBars bars = new BlockBars(); + /** List of block stats. */ + public BlockStats stats = new BlockStats(this); + /** List of block flags. Used for AI indexing. */ + public EnumSet flags; + /** Whether to automatically set the entity to 'sleeping' when created. */ + public boolean autoSleep; + /** Name of shadow region to load. Null to indicate normal shadow. */ + public String shadow = null; + /** Whether the block can be tapped and selected to configure. */ + public boolean configurable; + /** Whether this block consumes touchDown events when tapped. */ + public boolean consumesTap; + /** The color of this block when displayed on the minimap or map preview. */ + public Color minimapColor = Color.CLEAR; + /** View range of this block type. Use a value < 0 to disable. */ + public float viewRange = 10; + protected Array tempTiles = new Array<>(); + protected Color tempColor = new Color(); + protected TextureRegion[] blockIcon; + protected TextureRegion[] icon; + protected TextureRegion[] compactIcon; + protected TextureRegion editorIcon; + protected TextureRegion shadowRegion; + protected TextureRegion region; - protected Array tempTiles = new Array<>(); - protected Color tempColor = new Color(); + public Block(String name){ + this.name = name; + this.formalName = Bundles.get("block." + name + ".name", name); + this.fullDescription = Bundles.getOrNull("block." + name + ".description"); + this.solid = false; + this.id = lastid++; - protected TextureRegion[] blockIcon; - protected TextureRegion[] icon; - protected TextureRegion[] compactIcon; - protected TextureRegion editorIcon; - - protected TextureRegion shadowRegion; - protected TextureRegion region; - - /**internal name*/ - public final String name; - /**internal ID*/ - public final int id; - /**display name*/ - public final String formalName; - /**whether this block has a tile entity that updates*/ - public boolean update; - /**whether this block has health and can be destroyed*/ - public boolean destructible; - /**if true, this block cannot be broken by normal means.*/ - public boolean unbreakable; - /**whether this is solid*/ - public boolean solid; - /**whether this block CAN be solid.*/ - public boolean solidifes; - /**whether this is rotateable*/ - public boolean rotate; - /**whether you can break this with rightclick*/ - public boolean breakable; - /**whether this floor can be placed on.*/ - public boolean placeableOn = true; - /**tile entity health*/ - public int health = -1; - /**base block explosiveness*/ - public float baseExplosiveness = 0f; - /**whether this block can be placed on liquids.*/ - public boolean floating = true; - /**stuff that drops when broken*/ - public ItemStack drops = null; - /**multiblock size*/ - public int size = 1; - /**Detailed description of the block. Can be as long as necesary.*/ - public final String fullDescription; - /**Whether to draw this block in the expanded draw range.*/ - public boolean expanded = false; - /**Max of timers used.*/ - public int timers = 0; - /**Cache layer. Only used for 'cached' rendering.*/ - public CacheLayer cacheLayer = CacheLayer.normal; - /**Layer to draw extra stuff on.*/ - public Layer layer = null; - /**Extra layer to draw extra extra stuff on.*/ - public Layer layer2 = null; - /**whether this block can be replaced in all cases*/ - public boolean alwaysReplace = false; - /**whether this block has instant transfer checking. used for calculations to prevent infinite loops.*/ - public boolean instantTransfer = false; - /**The block group. Unless {@link #canReplace} is overriden, blocks in the same group can replace each other.*/ - public BlockGroup group = BlockGroup.none; - /**list of displayed block status bars. Defaults to health bar.*/ - public BlockBars bars = new BlockBars(); - /**List of block stats.*/ - public BlockStats stats = new BlockStats(this); - /**List of block flags. Used for AI indexing.*/ - public EnumSet flags; - /**Whether to automatically set the entity to 'sleeping' when created.*/ - public boolean autoSleep; - /**Name of shadow region to load. Null to indicate normal shadow.*/ - public String shadow = null; - /**Whether the block can be tapped and selected to configure.*/ - public boolean configurable; - /**Whether this block consumes touchDown events when tapped.*/ - public boolean consumesTap; - /**The color of this block when displayed on the minimap or map preview.*/ - public Color minimapColor = Color.CLEAR; - /**View range of this block type. Use a value < 0 to disable.*/ - public float viewRange = 10; - - public Block(String name) { - this.name = name; - this.formalName = Bundles.get("block." + name + ".name", name); - this.fullDescription = Bundles.getOrNull("block." + name + ".description"); - this.solid = false; - this.id = lastid++; - - if(map.containsKey(name)){ - throw new RuntimeException("Two blocks cannot have the same names! Problematic block: " + name); - } - - map.put(name, this); - blocks.add(this); - } - - public boolean isLayer(Tile tile){return true;} - public boolean isLayer2(Tile tile){return true;} - public void drawLayer(Tile tile){} - public void drawLayer2(Tile tile){} - - /**Draw the block overlay that is shown when a cursor is over the block.*/ - public void drawSelect(Tile tile){} - - /**Drawn when you are placing a block.*/ - public void drawPlace(int x, int y, int rotation, boolean valid){} - - /**Called after the block is placed.*/ - public void placed(Tile tile){} - - /**Called every frame a unit is on this tile.*/ - public void unitOn(Tile tile, Unit unit){} - - /**Returns whether ot not this block can be place on the specified tile.*/ - public boolean canPlaceOn(Tile tile){ return true; } - - /**Called after all blocks are created.*/ - @Override - public void init(){ - //initialize default health based on size - if(health == -1){ - health = size*size*40; - } - - setStats(); - setBars(); - - consumes.checkRequired(this); - } - - @Override - public void load() { - shadowRegion = Draw.region(shadow == null ? "shadow-" + size : shadow); - region = Draw.region(name); - } - - /**Called when the block is tapped.*/ - public void tapped(Tile tile, Player player){ - - } - - /**Returns whether or not a hand cursor should be shown over this block.*/ - public CursorType getCursor(Tile tile){ - return configurable ? CursorType.hand : CursorType.normal; - } - - /**Called when this block is tapped to build a UI on the table. - * {@link #configurable} able} must return true for this to be called.*/ - public void buildTable(Tile tile, Table table) {} - - /**Called when another tile is tapped while this block is selected. - * Returns whether or not this block should be deselected.*/ - public boolean onConfigureTileTapped(Tile tile, Tile other){ - return tile != other; - } - - /**Returns whether this config menu should show when the specified player taps it.*/ - public boolean shouldShowConfigure(Tile tile, Player player){ - return true; - } - - /**Whether this configuration should be hidden now. Called every frame the config is open.*/ - public boolean shouldHideConfigure(Tile tile, Player player){ - return false; - } - - public boolean synthetic(){ - return update || destructible || solid; - } - - public void drawConfigure(Tile tile){ - Draw.color(Palette.accent); - Lines.stroke(1f); - Lines.square(tile.drawx(), tile.drawy(), - tile.block().size * tilesize / 2f + 1f); - Draw.reset(); - } - - public void setStats(){ - stats.add(BlockStat.size, "{0}x{0}", size); - stats.add(BlockStat.health, health, StatUnit.none); - - consumes.forEach(cons -> cons.display(stats)); - - if(hasPower) stats.add(BlockStat.powerCapacity, powerCapacity, StatUnit.powerUnits); - if(hasLiquids) stats.add(BlockStat.liquidCapacity, liquidCapacity, StatUnit.liquidUnits); - if(hasItems) stats.add(BlockStat.itemCapacity, itemCapacity, StatUnit.items); - } - - //TODO make this easier to config. - public void setBars(){ - if(hasPower) bars.add(new BlockBar(BarType.power, true, tile -> tile.entity.power.amount / powerCapacity)); - if(hasLiquids) bars.add(new BlockBar(BarType.liquid, true, tile -> tile.entity.liquids.total() / liquidCapacity)); - if(hasItems) bars.add(new BlockBar(BarType.inventory, true, tile -> (float)tile.entity.items.total() / itemCapacity)); - } - - public String name(){ - return name; - } - - public boolean isSolidFor(Tile tile){ - return false; - } - - public boolean canReplace(Block other){ - return (other != this || rotate) && this.group != BlockGroup.none && other.group == this.group; - } - - public float handleDamage(Tile tile, float amount){ - return amount; - } - - public void handleBulletHit(TileEntity entity, Bullet bullet){ - entity.damage(bullet.getDamage()); - } - - public void update(Tile tile){} - - public boolean isAccessible(){ - return (hasItems && itemCapacity > 0); - } - - /**Called after the block is destroyed and removed.*/ - public void afterDestroyed(Tile tile, TileEntity entity){ - - } - - /**Called when the block is destroyed.*/ - public void onDestroyed(Tile tile){ - float x = tile.worldx(), y = tile.worldy(); - float explosiveness = baseExplosiveness; - float flammability = 0f; - float power = 0f; - int units = 1; - tempColor.set(Palette.darkFlame); - - if(hasItems){ - for(Item item : Item.all()){ - int amount = tile.entity.items.get(item); - explosiveness += item.explosiveness*amount; - flammability += item.flammability*amount; - - if(item.flammability*amount > 0.5){ - units ++; - Hue.addu(tempColor, item.flameColor); - } - } - } - - if(hasLiquids){ - flammability += tile.entity.liquids.sum((liquid, amount) -> liquid.explosiveness * amount/2f); - explosiveness += tile.entity.liquids.sum((liquid, amount) -> liquid.flammability * amount/2f); - } - - if(hasPower){ - power += tile.entity.power.amount; - } - - tempColor.mul(1f/units); - - if(hasLiquids) { - - tile.entity.liquids.forEach((liquid, amount) -> { - float splash = Mathf.clamp(amount / 4f, 0f, 10f); - - for (int i = 0; i < Mathf.clamp(amount / 5, 0, 30); i++) { - Timers.run(i / 2, () -> { - Tile other = world.tile(tile.x + Mathf.range(size / 2), tile.y + Mathf.range(size / 2)); - if (other != null) { - Puddle.deposit(other, liquid, splash); - } - }); - } - }); - } - - Damage.dynamicExplosion(x, y, flammability, explosiveness, power, tilesize * size/2f, tempColor); - if(!tile.floor().solid && !tile.floor().isLiquid){ - RubbleDecal.create(tile.drawx(), tile.drawy(), size); - } - } - - /**Returns the flammability of the tile. Used for fire calculations. - * Takes flammability of floor liquid into account.*/ - public float getFlammability(Tile tile){ - if(!hasItems || tile.entity == null){ - if(tile.floor().isLiquid && !solid){ - return tile.floor().liquidDrop.flammability; - } - return 0; - }else{ - float result = tile.entity.items.sum((item, amount) -> item.flammability * amount); - - if(hasLiquids){ - result += tile.entity.liquids.sum((liquid, amount) -> liquid.flammability * amount/3f); - } - - return result; - } - } - - public TextureRegion getEditorIcon(){ - if(editorIcon == null){ - editorIcon = Draw.region("block-icon-" + name, Draw.region("clear")); - } - return editorIcon; - } - - /**Returns the icon used for displaying this block in the place menu*/ - public TextureRegion[] getIcon(){ - if(icon == null) { - if (Draw.hasRegion(name + "-icon")) { - icon = new TextureRegion[]{Draw.region(name + "-icon")}; - } else if (Draw.hasRegion(name)){ - icon = new TextureRegion[]{Draw.region(name)}; - } else if (Draw.hasRegion(name + "1")) { - icon = new TextureRegion[]{Draw.region(name + "1")}; - }else{ - icon = new TextureRegion[]{}; - } + if(map.containsKey(name)){ + throw new RuntimeException("Two blocks cannot have the same names! Problematic block: " + name); } - return icon; - } - - /**Returns a list of regions that represent this block in the world*/ - public TextureRegion[] getBlockIcon(){ - return getIcon(); + map.put(name, this); + blocks.add(this); } - /**Returns a list of icon regions that have been cropped to 8x8*/ - public TextureRegion[] getCompactIcon(){ - if(compactIcon == null) { - compactIcon = new TextureRegion[getIcon().length]; - for (int i = 0; i < compactIcon.length; i++) { - compactIcon[i] = iconRegion(getIcon()[i]); - } - } - return compactIcon; - } + public static Array all(){ + return blocks; + } - /**Crops a regionto 8x8*/ - protected TextureRegion iconRegion(TextureRegion src){ + public static Block getByName(String name){ + return map.get(name); + } + + public static Block getByID(int id){ + if(id < 0){ //offset negative values by 256, as they are a product of byte overflow + id += 256; + } + if(id >= blocks.size || id < 0){ + throw new RuntimeException("No block with ID '" + id + "' found!"); + } + return blocks.get(id); + } + + public boolean isLayer(Tile tile){ + return true; + } + + public boolean isLayer2(Tile tile){ + return true; + } + + public void drawLayer(Tile tile){ + } + + public void drawLayer2(Tile tile){ + } + + /** Draw the block overlay that is shown when a cursor is over the block. */ + public void drawSelect(Tile tile){ + } + + /** Drawn when you are placing a block. */ + public void drawPlace(int x, int y, int rotation, boolean valid){ + } + + /** Called after the block is placed. */ + public void placed(Tile tile){ + } + + /** Called every frame a unit is on this tile. */ + public void unitOn(Tile tile, Unit unit){ + } + + /** Returns whether ot not this block can be place on the specified tile. */ + public boolean canPlaceOn(Tile tile){ + return true; + } + + /** Called after all blocks are created. */ + @Override + public void init(){ + //initialize default health based on size + if(health == -1){ + health = size * size * 40; + } + + setStats(); + setBars(); + + consumes.checkRequired(this); + } + + @Override + public void load(){ + shadowRegion = Draw.region(shadow == null ? "shadow-" + size : shadow); + region = Draw.region(name); + } + + /** Called when the block is tapped. */ + public void tapped(Tile tile, Player player){ + + } + + /** Returns whether or not a hand cursor should be shown over this block. */ + public CursorType getCursor(Tile tile){ + return configurable ? CursorType.hand : CursorType.normal; + } + + /** + * Called when this block is tapped to build a UI on the table. + * {@link #configurable} able} must return true for this to be called. + */ + public void buildTable(Tile tile, Table table){ + } + + /** + * Called when another tile is tapped while this block is selected. + * Returns whether or not this block should be deselected. + */ + public boolean onConfigureTileTapped(Tile tile, Tile other){ + return tile != other; + } + + /** Returns whether this config menu should show when the specified player taps it. */ + public boolean shouldShowConfigure(Tile tile, Player player){ + return true; + } + + /** Whether this configuration should be hidden now. Called every frame the config is open. */ + public boolean shouldHideConfigure(Tile tile, Player player){ + return false; + } + + public boolean synthetic(){ + return update || destructible || solid; + } + + public void drawConfigure(Tile tile){ + Draw.color(Palette.accent); + Lines.stroke(1f); + Lines.square(tile.drawx(), tile.drawy(), + tile.block().size * tilesize / 2f + 1f); + Draw.reset(); + } + + public void setStats(){ + stats.add(BlockStat.size, "{0}x{0}", size); + stats.add(BlockStat.health, health, StatUnit.none); + + consumes.forEach(cons -> cons.display(stats)); + + if(hasPower) stats.add(BlockStat.powerCapacity, powerCapacity, StatUnit.powerUnits); + if(hasLiquids) stats.add(BlockStat.liquidCapacity, liquidCapacity, StatUnit.liquidUnits); + if(hasItems) stats.add(BlockStat.itemCapacity, itemCapacity, StatUnit.items); + } + + //TODO make this easier to config. + public void setBars(){ + if(hasPower) bars.add(new BlockBar(BarType.power, true, tile -> tile.entity.power.amount / powerCapacity)); + if(hasLiquids) + bars.add(new BlockBar(BarType.liquid, true, tile -> tile.entity.liquids.total() / liquidCapacity)); + if(hasItems) + bars.add(new BlockBar(BarType.inventory, true, tile -> (float) tile.entity.items.total() / itemCapacity)); + } + + public String name(){ + return name; + } + + public boolean isSolidFor(Tile tile){ + return false; + } + + public boolean canReplace(Block other){ + return (other != this || rotate) && this.group != BlockGroup.none && other.group == this.group; + } + + public float handleDamage(Tile tile, float amount){ + return amount; + } + + public void handleBulletHit(TileEntity entity, Bullet bullet){ + entity.damage(bullet.getDamage()); + } + + public void update(Tile tile){ + } + + public boolean isAccessible(){ + return (hasItems && itemCapacity > 0); + } + + /** Called after the block is destroyed and removed. */ + public void afterDestroyed(Tile tile, TileEntity entity){ + + } + + /** Called when the block is destroyed. */ + public void onDestroyed(Tile tile){ + float x = tile.worldx(), y = tile.worldy(); + float explosiveness = baseExplosiveness; + float flammability = 0f; + float power = 0f; + int units = 1; + tempColor.set(Palette.darkFlame); + + if(hasItems){ + for(Item item : Item.all()){ + int amount = tile.entity.items.get(item); + explosiveness += item.explosiveness * amount; + flammability += item.flammability * amount; + + if(item.flammability * amount > 0.5){ + units++; + Hue.addu(tempColor, item.flameColor); + } + } + } + + if(hasLiquids){ + flammability += tile.entity.liquids.sum((liquid, amount) -> liquid.explosiveness * amount / 2f); + explosiveness += tile.entity.liquids.sum((liquid, amount) -> liquid.flammability * amount / 2f); + } + + if(hasPower){ + power += tile.entity.power.amount; + } + + tempColor.mul(1f / units); + + if(hasLiquids){ + + tile.entity.liquids.forEach((liquid, amount) -> { + float splash = Mathf.clamp(amount / 4f, 0f, 10f); + + for(int i = 0; i < Mathf.clamp(amount / 5, 0, 30); i++){ + Timers.run(i / 2, () -> { + Tile other = world.tile(tile.x + Mathf.range(size / 2), tile.y + Mathf.range(size / 2)); + if(other != null){ + Puddle.deposit(other, liquid, splash); + } + }); + } + }); + } + + Damage.dynamicExplosion(x, y, flammability, explosiveness, power, tilesize * size / 2f, tempColor); + if(!tile.floor().solid && !tile.floor().isLiquid){ + RubbleDecal.create(tile.drawx(), tile.drawy(), size); + } + } + + /** + * Returns the flammability of the tile. Used for fire calculations. + * Takes flammability of floor liquid into account. + */ + public float getFlammability(Tile tile){ + if(!hasItems || tile.entity == null){ + if(tile.floor().isLiquid && !solid){ + return tile.floor().liquidDrop.flammability; + } + return 0; + }else{ + float result = tile.entity.items.sum((item, amount) -> item.flammability * amount); + + if(hasLiquids){ + result += tile.entity.liquids.sum((liquid, amount) -> liquid.flammability * amount / 3f); + } + + return result; + } + } + + public TextureRegion getEditorIcon(){ + if(editorIcon == null){ + editorIcon = Draw.region("block-icon-" + name, Draw.region("clear")); + } + return editorIcon; + } + + /** Returns the icon used for displaying this block in the place menu */ + public TextureRegion[] getIcon(){ + if(icon == null){ + if(Draw.hasRegion(name + "-icon")){ + icon = new TextureRegion[]{Draw.region(name + "-icon")}; + }else if(Draw.hasRegion(name)){ + icon = new TextureRegion[]{Draw.region(name)}; + }else if(Draw.hasRegion(name + "1")){ + icon = new TextureRegion[]{Draw.region(name + "1")}; + }else{ + icon = new TextureRegion[]{}; + } + } + + return icon; + } + + /** Returns a list of regions that represent this block in the world */ + public TextureRegion[] getBlockIcon(){ + return getIcon(); + } + + /** Returns a list of icon regions that have been cropped to 8x8 */ + public TextureRegion[] getCompactIcon(){ + if(compactIcon == null){ + compactIcon = new TextureRegion[getIcon().length]; + for(int i = 0; i < compactIcon.length; i++){ + compactIcon[i] = iconRegion(getIcon()[i]); + } + } + return compactIcon; + } + + /** Crops a regionto 8x8 */ + protected TextureRegion iconRegion(TextureRegion src){ TextureRegion region = new TextureRegion(src); region.setRegionWidth(8); region.setRegionHeight(8); @@ -386,76 +425,59 @@ public class Block extends BaseBlock implements Content{ } public boolean hasEntity(){ - return destructible || update; - } - - public TileEntity getEntity(){ - return new TileEntity(); - } - - public void draw(Tile tile){ - Draw.rect(region, tile.drawx(), tile.drawy(), rotate ? tile.getRotation() * 90 : 0); - } + return destructible || update; + } - public void drawNonLayer(Tile tile){} - - public void drawShadow(Tile tile){ - Draw.rect(shadowRegion, tile.drawx(), tile.drawy()); - } - - /**Offset for placing and drawing multiblocks.*/ - public float offset(){ - return ((size + 1) % 2) * tilesize/2; - } - - public boolean isMultiblock(){ - return size > 1; - } + public TileEntity getEntity(){ + return new TileEntity(); + } - public Array getDebugInfo(Tile tile){ - return Array.with( - "block", tile.block().name, - "floor", tile.floor().name, - "x", tile.x, - "y", tile.y, - "entity.name", ClassReflection.getSimpleName(tile.entity.getClass()), - "entity.x", tile.entity.x, - "entity.y", tile.entity.y, - "entity.id", tile.entity.id, - "entity.items.total", hasItems ? tile.entity.items.total() : null - ); - } + public void draw(Tile tile){ + Draw.rect(region, tile.drawx(), tile.drawy(), rotate ? tile.getRotation() * 90 : 0); + } - @Override - public String getContentTypeName() { - return "block"; - } + public void drawNonLayer(Tile tile){ + } - @Override - public Array getAll() { - return all(); - } + public void drawShadow(Tile tile){ + Draw.rect(shadowRegion, tile.drawx(), tile.drawy()); + } - @Override - public String toString(){ - return name; - } + /** Offset for placing and drawing multiblocks. */ + public float offset(){ + return ((size + 1) % 2) * tilesize / 2; + } - public static Array all(){ - return blocks; - } + public boolean isMultiblock(){ + return size > 1; + } - public static Block getByName(String name){ - return map.get(name); - } + public Array getDebugInfo(Tile tile){ + return Array.with( + "block", tile.block().name, + "floor", tile.floor().name, + "x", tile.x, + "y", tile.y, + "entity.name", ClassReflection.getSimpleName(tile.entity.getClass()), + "entity.x", tile.entity.x, + "entity.y", tile.entity.y, + "entity.id", tile.entity.id, + "entity.items.total", hasItems ? tile.entity.items.total() : null + ); + } - public static Block getByID(int id){ - if(id < 0){ //offset negative values by 256, as they are a product of byte overflow - id += 256; - } - if(id >= blocks.size || id < 0){ - throw new RuntimeException("No block with ID '" + id + "' found!"); - } - return blocks.get(id); - } -} + @Override + public String getContentTypeName(){ + return "block"; + } + + @Override + public Array getAll(){ + return all(); + } + + @Override + public String toString(){ + return name; + } +} \ No newline at end of file diff --git a/core/src/io/anuke/mindustry/world/Build.java b/core/src/io/anuke/mindustry/world/Build.java index aa075581ae..d0fab8e84e 100644 --- a/core/src/io/anuke/mindustry/world/Build.java +++ b/core/src/io/anuke/mindustry/world/Build.java @@ -12,11 +12,13 @@ import io.anuke.ucore.entities.Entities; import static io.anuke.mindustry.Vars.*; -public class Build { +public class Build{ private static final Rectangle rect = new Rectangle(); private static final Rectangle hitrect = new Rectangle(); - /**Returns block type that was broken, or null if unsuccesful.*/ + /** + * Returns block type that was broken, or null if unsuccesful. + */ //@Remote(targets = Loc.both, forward = true, called = Loc.server, in = In.blocks) public static void beginBreak(Team team, int x, int y){ if(!validBreak(team, x, y)){ @@ -38,17 +40,17 @@ public class Build { tile.entity().setDeconstruct(previous); tile.setTeam(team); - if (previous.isMultiblock()) { + if(previous.isMultiblock()){ int offsetx = -(previous.size - 1) / 2; int offsety = -(previous.size - 1) / 2; - for (int dx = 0; dx < previous.size; dx++) { - for (int dy = 0; dy < previous.size; dy++) { + for(int dx = 0; dx < previous.size; dx++){ + for(int dy = 0; dy < previous.size; dy++){ int worldx = dx + offsetx + x; int worldy = dy + offsety + y; - if (!(worldx == x && worldy == y)) { + if(!(worldx == x && worldy == y)){ Tile toplace = world.tile(worldx, worldy); - if (toplace != null) { + if(toplace != null){ toplace.setLinked((byte) (dx + offsetx), (byte) (dy + offsety)); toplace.setTeam(team); } @@ -59,7 +61,9 @@ public class Build { } - /**Places a BuildBlock at this location.*/ + /** + * Places a BuildBlock at this location. + */ //@Remote(targets = Loc.both, forward = true, called = Loc.server, in = In.blocks) public static void beginPlace(Team team, int x, int y, Recipe recipe, int rotation){ if(!validPlace(team, x, y, recipe.result, rotation)){ @@ -80,17 +84,17 @@ public class Build { tile.entity().setConstruct(previous, recipe); tile.setTeam(team); - if (result.isMultiblock()) { + if(result.isMultiblock()){ int offsetx = -(result.size - 1) / 2; int offsety = -(result.size - 1) / 2; - for (int dx = 0; dx < result.size; dx++) { - for (int dy = 0; dy < result.size; dy++) { + for(int dx = 0; dx < result.size; dx++){ + for(int dy = 0; dy < result.size; dy++){ int worldx = dx + offsetx + x; int worldy = dy + offsety + y; - if (!(worldx == x && worldy == y)) { + if(!(worldx == x && worldy == y)){ Tile toplace = world.tile(worldx, worldy); - if (toplace != null) { + if(toplace != null){ toplace.setLinked((byte) (dx + offsetx), (byte) (dy + offsety)); toplace.setTeam(team); } @@ -103,35 +107,37 @@ public class Build { threads.runDelay(() -> Events.fire(BlockBuildEvent.class, team, tile)); } - /**Returns whether a tile can be placed at this location by this team.*/ - public static boolean validPlace(Team team, int x, int y, Block type, int rotation) { + /** + * Returns whether a tile can be placed at this location by this team. + */ + public static boolean validPlace(Team team, int x, int y, Block type, int rotation){ Recipe recipe = Recipe.getByResult(type); - if (recipe == null || (recipe.debugOnly && !debug)) { + if(recipe == null || (recipe.debugOnly && !debug)){ return false; } rect.setSize(type.size * tilesize, type.size * tilesize); rect.setCenter(type.offset() + x * tilesize, type.offset() + y * tilesize); - if (type.solid || type.solidifes) { - synchronized (Entities.entityLock) { - try { + if(type.solid || type.solidifes){ + synchronized(Entities.entityLock){ + try{ rect.setSize(tilesize * type.size).setCenter(x * tilesize + type.offset(), y * tilesize + type.offset()); boolean[] result = {false}; Units.getNearby(rect, e -> { - if (e == null) return; //not sure why this happens? + if(e == null) return; //not sure why this happens? e.getHitbox(hitrect); - if (rect.overlaps(hitrect) && !e.isFlying()) { + if(rect.overlaps(hitrect) && !e.isFlying()){ result[0] = true; } }); - if (result[0]) return false; - } catch (Exception e) { + if(result[0]) return false; + }catch(Exception e){ return false; } } @@ -139,10 +145,10 @@ public class Build { Tile tile = world.tile(x, y); - if (tile == null) return false; + if(tile == null) return false; - if (type.isMultiblock()) { - if (type.canReplace(tile.block()) && tile.block().size == type.size) { + if(type.isMultiblock()){ + if(type.canReplace(tile.block()) && tile.block().size == type.size){ return true; } @@ -152,18 +158,18 @@ public class Build { int offsetx = -(type.size - 1) / 2; int offsety = -(type.size - 1) / 2; - for (int dx = 0; dx < type.size; dx++) { - for (int dy = 0; dy < type.size; dy++) { + for(int dx = 0; dx < type.size; dx++){ + for(int dy = 0; dy < type.size; dy++){ Tile other = world.tile(x + dx + offsetx, y + dy + offsety); - if (other == null || (other.block() != Blocks.air && !other.block().alwaysReplace) - || other.cliffs != 0 || !other.floor().placeableOn || - (tile.floor().liquidDrop != null && !type.floating)) { + if(other == null || (other.block() != Blocks.air && !other.block().alwaysReplace) + || other.cliffs != 0 || !other.floor().placeableOn || + (tile.floor().liquidDrop != null && !type.floating)){ return false; } } } return true; - } else { + }else{ return (tile.getTeam() == Team.none || tile.getTeam() == team) && (tile.floor().liquidDrop == null || type.floating) && tile.floor().placeableOn && tile.cliffs == 0 @@ -173,8 +179,10 @@ public class Build { } } - /**Returns whether the tile at this position is breakable by this team*/ - public static boolean validBreak(Team team, int x, int y) { + /** + * Returns whether the tile at this position is breakable by this team + */ + public static boolean validBreak(Team team, int x, int y){ Tile tile = world.tile(x, y); return tile != null && !tile.block().unbreakable diff --git a/core/src/io/anuke/mindustry/world/ColorMapper.java b/core/src/io/anuke/mindustry/world/ColorMapper.java index 74b20c6308..012caefe05 100644 --- a/core/src/io/anuke/mindustry/world/ColorMapper.java +++ b/core/src/io/anuke/mindustry/world/ColorMapper.java @@ -8,30 +8,30 @@ import io.anuke.mindustry.game.Content; import io.anuke.mindustry.type.ContentList; public class ColorMapper implements ContentList{ - private static IntMap blockMap = new IntMap<>(); - private static ObjectIntMap colorMap = new ObjectIntMap<>(); + private static IntMap blockMap = new IntMap<>(); + private static ObjectIntMap colorMap = new ObjectIntMap<>(); - @Override - public void load() { - for(Block block : Block.all()){ - int color = Color.rgba8888(block.minimapColor); - if(color == 0) continue; //skip blocks that are not mapped + public static Block getByColor(int color){ + return blockMap.get(color); + } - blockMap.put(color, block); - colorMap.put(block, color); - } - } + public static int getBlockColor(Block block){ + return colorMap.get(block, 0); + } - @Override - public Array getAll() { - return new Array<>(); - } + @Override + public void load(){ + for(Block block : Block.all()){ + int color = Color.rgba8888(block.minimapColor); + if(color == 0) continue; //skip blocks that are not mapped - public static Block getByColor(int color){ - return blockMap.get(color); - } + blockMap.put(color, block); + colorMap.put(block, color); + } + } - public static int getBlockColor(Block block){ - return colorMap.get(block, 0); - } + @Override + public Array getAll(){ + return new Array<>(); + } } diff --git a/core/src/io/anuke/mindustry/world/Edges.java b/core/src/io/anuke/mindustry/world/Edges.java index f8f0fe045b..6c0bbbdc94 100644 --- a/core/src/io/anuke/mindustry/world/Edges.java +++ b/core/src/io/anuke/mindustry/world/Edges.java @@ -9,57 +9,58 @@ import java.util.Arrays; import static io.anuke.mindustry.Vars.world; -public class Edges { +public class Edges{ private static final int maxSize = 11; private static final int maxRadius = 12; private static GridPoint2[][] edges = new GridPoint2[maxSize][0]; private static GridPoint2[][] edgeInside = new GridPoint2[maxSize][0]; - private static Vector2[][] polygons = new Vector2[maxRadius*2][0]; + private static Vector2[][] polygons = new Vector2[maxRadius * 2][0]; static{ - for(int i = 0; i < maxRadius*2; i ++){ - polygons[i] = Geometry.pixelCircle((i + 1)/2f); + for(int i = 0; i < maxRadius * 2; i++){ + polygons[i] = Geometry.pixelCircle((i + 1) / 2f); } - for(int i = 0; i < maxSize; i ++){ - int bot = -(int)(i/2f) - 1; - int top = (int)(i/2f+0.5f) + 1; + for(int i = 0; i < maxSize; i++){ + int bot = -(int) (i / 2f) - 1; + int top = (int) (i / 2f + 0.5f) + 1; edges[i] = new GridPoint2[(i + 1) * 4]; int idx = 0; - for(int j = 0; j < i + 1; j ++){ + for(int j = 0; j < i + 1; j++){ //bottom - edges[i][idx ++] = new GridPoint2(bot + 1 + j, bot); + edges[i][idx++] = new GridPoint2(bot + 1 + j, bot); //top - edges[i][idx ++] = new GridPoint2(bot + 1 + j, top); + edges[i][idx++] = new GridPoint2(bot + 1 + j, top); //left - edges[i][idx ++] = new GridPoint2(bot, bot + j + 1); + edges[i][idx++] = new GridPoint2(bot, bot + j + 1); //right - edges[i][idx ++] = new GridPoint2(top, bot + j + 1); + edges[i][idx++] = new GridPoint2(top, bot + j + 1); } Arrays.sort(edges[i], (e1, e2) -> Float.compare(Mathf.atan2(e1.x, e1.y), Mathf.atan2(e2.x, e2.y))); edgeInside[i] = new GridPoint2[edges[i].length]; - for(int j = 0; j < edges[i].length; j ++){ + for(int j = 0; j < edges[i].length; j++){ GridPoint2 point = edges[i][j]; - edgeInside[i][j] = new GridPoint2(Mathf.clamp(point.x, -(int)((i)/2f), (int)(i/2f + 0.5f)), - Mathf.clamp(point.y, -(int)((i)/2f), (int)(i/2f + 0.5f))); + edgeInside[i][j] = new GridPoint2(Mathf.clamp(point.x, -(int) ((i) / 2f), (int) (i / 2f + 0.5f)), + Mathf.clamp(point.y, -(int) ((i) / 2f), (int) (i / 2f + 0.5f))); } } } public static Tile getFacingEdge(Tile tile, Tile other){ int size = tile.block().size; - return world.tile(tile.x + Mathf.clamp(other.x - tile.x, -(size-1) / 2, (size / 2)), - tile.y + Mathf.clamp(other.y - tile.y, -(size-1) / 2, (size / 2))); + return world.tile(tile.x + Mathf.clamp(other.x - tile.x, -(size - 1) / 2, (size / 2)), + tile.y + Mathf.clamp(other.y - tile.y, -(size - 1) / 2, (size / 2))); } public static Vector2[] getPixelPolygon(float radius){ - if(radius < 1 || radius > maxRadius) throw new RuntimeException("Polygon size must be between 1 and " + maxRadius); - return polygons[(int)(radius*2) - 1]; + if(radius < 1 || radius > maxRadius) + throw new RuntimeException("Polygon size must be between 1 and " + maxRadius); + return polygons[(int) (radius * 2) - 1]; } public static synchronized GridPoint2[] getEdges(int size){ diff --git a/core/src/io/anuke/mindustry/world/ItemBuffer.java b/core/src/io/anuke/mindustry/world/ItemBuffer.java index d09583d6d1..d3ba23f394 100644 --- a/core/src/io/anuke/mindustry/world/ItemBuffer.java +++ b/core/src/io/anuke/mindustry/world/ItemBuffer.java @@ -5,7 +5,7 @@ import io.anuke.mindustry.type.Item; import io.anuke.ucore.core.Timers; import io.anuke.ucore.util.Bits; -public class ItemBuffer { +public class ItemBuffer{ private final float speed; private long[] buffer; @@ -22,11 +22,11 @@ public class ItemBuffer { public void accept(Item item, short data){ //if(!accepts()) return; - buffer[index ++] = Bits.packLong(NumberUtils.floatToIntBits(Timers.time()), Bits.packInt((short)item.id, data)); + buffer[index++] = Bits.packLong(NumberUtils.floatToIntBits(Timers.time()), Bits.packInt((short) item.id, data)); } public void accept(Item item){ - accept(item, (short)-1); + accept(item, (short) -1); } public Item poll(){ @@ -55,6 +55,6 @@ public class ItemBuffer { public void remove(){ System.arraycopy(buffer, 1, buffer, 0, index - 1); - index --; + index--; } } diff --git a/core/src/io/anuke/mindustry/world/Tile.java b/core/src/io/anuke/mindustry/world/Tile.java index 28b0fd0217..4ea601b896 100644 --- a/core/src/io/anuke/mindustry/world/Tile.java +++ b/core/src/io/anuke/mindustry/world/Tile.java @@ -24,419 +24,426 @@ import static io.anuke.mindustry.Vars.tilesize; import static io.anuke.mindustry.Vars.world; -public class Tile implements PosTrait, TargetTrait { - public static final Object tileSetLock = new Object(); - - /**Block ID data.*/ - private Block wall; - private Floor floor; - /**Rotation, 0-3. Also used to store offload location for routers, in which case it can be any number.*/ - private byte rotation; - /**Team ordinal.*/ - private byte team; - /**The coordinates of the core tile this is linked to, in the form of two bytes packed into one. - * This is relative to the block it is linked to; negate coords to find the link.*/ - public byte link = 0; - public short x, y; - /**Tile traversal cost.*/ - public byte cost = 1; - /**Elevation of tile.*/ - public byte elevation; - /**Position of cliffs around the tile, packed into bits 0-8.*/ - public byte cliffs; - /**Tile entity, usually null.*/ - public TileEntity entity; - - public Tile(int x, int y){ - this.x = (short)x; - this.y = (short)y; - } +public class Tile implements PosTrait, TargetTrait{ + public static final Object tileSetLock = new Object(); + /** + * The coordinates of the core tile this is linked to, in the form of two bytes packed into one. + * This is relative to the block it is linked to; negate coords to find the link. + */ + public byte link = 0; + public short x, y; + /** Tile traversal cost. */ + public byte cost = 1; + /** Elevation of tile. */ + public byte elevation; + /** Position of cliffs around the tile, packed into bits 0-8. */ + public byte cliffs; + /** Tile entity, usually null. */ + public TileEntity entity; + /** Block ID data. */ + private Block wall; + private Floor floor; + /** Rotation, 0-3. Also used to store offload location for routers, in which case it can be any number. */ + private byte rotation; + /** Team ordinal. */ + private byte team; - public Tile(int x, int y, byte floor, byte wall){ - this(x, y); - this.floor = (Floor) Block.getByID(floor); - this.wall = Block.getByID(wall); - changed(); - } - - public Tile(int x, int y, byte floor, byte wall, byte rotation, byte team, byte elevation){ - this(x, y); - this.floor =(Floor) Block.getByID(floor); - this.wall = Block.getByID(wall); - this.rotation = rotation; - this.elevation = elevation; - changed(); - this.team = team; - } + public Tile(int x, int y){ + this.x = (short) x; + this.y = (short) y; + } - public int packedPosition(){ - return x + y * world.width(); - } - - public byte getWallID(){ - return (byte)wall.id; - } - - public byte getFloorID(){ - return (byte)floor.id; - } - - /**Return relative rotation to a coordinate. Returns -1 if the coordinate is not near this tile.*/ - public byte relativeTo(int cx, int cy){ - if(x == cx && y == cy - 1) return 1; - if(x == cx && y == cy + 1) return 3; - if(x == cx - 1 && y == cy) return 0; - if(x == cx + 1 && y == cy) return 2; - return -1; - } + public Tile(int x, int y, byte floor, byte wall){ + this(x, y); + this.floor = (Floor) Block.getByID(floor); + this.wall = Block.getByID(wall); + changed(); + } - public byte absoluteRelativeTo(int cx, int cy){ - if(x == cx && y <= cy - 1) return 1; - if(x == cx && y >= cy + 1) return 3; - if(x <= cx - 1 && y == cy) return 0; - if(x >= cx + 1 && y == cy) return 2; - return -1; - } + public Tile(int x, int y, byte floor, byte wall, byte rotation, byte team, byte elevation){ + this(x, y); + this.floor = (Floor) Block.getByID(floor); + this.wall = Block.getByID(wall); + this.rotation = rotation; + this.elevation = elevation; + changed(); + this.team = team; + } - public byte sizedRelativeTo(int cx, int cy){ - if(x == cx && y == cy - 1 - block().size/2) return 1; - if(x == cx && y == cy + 1 + block().size/2) return 3; - if(x == cx - 1 - block().size/2 && y == cy) return 0; - if(x == cx + 1 + block().size/2 && y == cy) return 2; - return -1; - } - - public T entity(){ - return (T)entity; - } - - public int id(){ - return x + y * world.width(); - } - - public float worldx(){ - return x * tilesize; - } - - public float worldy(){ - return y * tilesize; - } + public int packedPosition(){ + return x + y * world.width(); + } - public float drawx(){ - return block().offset() + worldx(); - } + public byte getWallID(){ + return (byte) wall.id; + } - public float drawy(){ - return block().offset() + worldy(); - } - - public Floor floor(){ - return floor; - } - - public Block block(){ - return wall; - } + public byte getFloorID(){ + return (byte) floor.id; + } - public Team getTeam(){ - return Team.all[team]; - } + /** Return relative rotation to a coordinate. Returns -1 if the coordinate is not near this tile. */ + public byte relativeTo(int cx, int cy){ + if(x == cx && y == cy - 1) return 1; + if(x == cx && y == cy + 1) return 3; + if(x == cx - 1 && y == cy) return 0; + if(x == cx + 1 && y == cy) return 2; + return -1; + } - public byte getTeamID(){ - return team; - } + public byte absoluteRelativeTo(int cx, int cy){ + if(x == cx && y <= cy - 1) return 1; + if(x == cx && y >= cy + 1) return 3; + if(x <= cx - 1 && y == cy) return 0; + if(x >= cx + 1 && y == cy) return 2; + return -1; + } - public void setTeam(Team team){ - this.team = (byte)team.ordinal(); - } - - /**Returns the break time of the block, or the breaktime of the linked block, if this tile is linked.*/ - public float getBreakTime(){ - Block block = target().block(); - if(Recipe.getByResult(block) != null){ - return Recipe.getByResult(block).cost; - }else{ - return 15f; - } - } - - public void setBlock(Block type, int rotation){ - synchronized (tileSetLock) { - preChanged(); - if(rotation < 0) rotation = (-rotation + 2); - this.wall = type; - this.link = 0; - setRotation((byte) (rotation % 4)); - changed(); - } - } - - public void setBlock(Block type){ - synchronized (tileSetLock) { - preChanged(); - this.wall = type; - this.link = 0; - changed(); - } - } - - public void setFloor(Floor type){ - this.floor = type; - } - - public void setRotation(byte rotation){ - this.rotation = rotation; - } - - public void setDump(byte dump){ - this.rotation = dump; - } - - public byte getRotation(){ - return rotation; - } - - public byte getDump(){ - return rotation; - } + public byte sizedRelativeTo(int cx, int cy){ + if(x == cx && y == cy - 1 - block().size / 2) return 1; + if(x == cx && y == cy + 1 + block().size / 2) return 3; + if(x == cx - 1 - block().size / 2 && y == cy) return 0; + if(x == cx + 1 + block().size / 2 && y == cy) return 2; + return -1; + } - public boolean passable(){ - Block block = block(); - Block floor = floor(); - return isLinked() || !((floor.solid && (block == Blocks.air || block.solidifes)) || (block.solid && (!block.destructible && !block.update))); - } + public T entity(){ + return (T) entity; + } - /**Whether this block was placed by a player/unit.*/ - public boolean synthetic(){ - Block block = block(); - return block.update || block.destructible; - } - - public boolean solid(){ - Block block = block(); - Block floor = floor(); - return block.solid || cliffs != 0 || (floor.solid && (block == Blocks.air || block.solidifes)) || block.isSolidFor(this) - || (isLinked() && getLinked().block().isSolidFor(getLinked())); - } - - public boolean breakable(){ - Block block = block(); - if(link == 0){ - return (block.destructible || block.breakable || block.update); - }else{ - return getLinked().breakable(); - } - } - - public boolean isLinked(){ - return link != 0; - } - - /**Sets this to a linked tile, which sets the block to a blockpart. dx and dy can only be -8-7.*/ - public void setLinked(byte dx, byte dy){ - setBlock(Blocks.blockpart); - link = Bits.packByte((byte)(dx + 8), (byte)(dy + 8)); - } - - /**Returns the list of all tiles linked to this multiblock, or an empty array if it's not a multiblock. - * This array contains all linked tiles, including this tile itself.*/ - public synchronized Array getLinkedTiles(Array tmpArray){ - Block block = block(); - tmpArray.clear(); - if(block.isMultiblock()){ - int offsetx = -(block.size-1)/2; - int offsety = -(block.size-1)/2; - for(int dx = 0; dx < block.size; dx ++){ - for(int dy = 0; dy < block.size; dy ++){ - Tile other = world.tile(x + dx + offsetx, y + dy + offsety); - tmpArray.add(other); - } - } - }else{ - tmpArray.add(this); - } - return tmpArray; - } + public int id(){ + return x + y * world.width(); + } - /**Returns the list of all tiles linked to this multiblock if it were this block, or an empty array if it's not a multiblock. - * This array contains all linked tiles, including this tile itself.*/ - public synchronized Array getLinkedTilesAs(Block block, Array tmpArray){ - tmpArray.clear(); - if(block.isMultiblock()){ - int offsetx = -(block.size-1)/2; - int offsety = -(block.size-1)/2; - for(int dx = 0; dx < block.size; dx ++){ - for(int dy = 0; dy < block.size; dy ++){ - Tile other = world.tile(x + dx + offsetx, y + dy + offsety); - tmpArray.add(other); - } - } - }else{ - tmpArray.add(this); - } - return tmpArray; - } - - /**Returns the block the multiblock is linked to, or null if it is not linked to any block.*/ - public Tile getLinked(){ - if(link == 0){ - return null; - }else{ - byte dx = Bits.getLeftByte(link); - byte dy = Bits.getRightByte(link); - return world.tile(x - (dx - 8), y - (dy - 8)); - } - } + public float worldx(){ + return x * tilesize; + } - public void allNearby(Consumer cons){ - for(GridPoint2 point : Edges.getEdges(block().size)){ - Tile tile = world.tile(x + point.x, y + point.y); - if(tile != null){ - cons.accept(tile.target()); - } - } - } + public float worldy(){ + return y * tilesize; + } - public void allInside(Consumer cons){ - for(GridPoint2 point : Edges.getInsideEdges(block().size)){ - Tile tile = world.tile(x + point.x, y + point.y); - if(tile != null){ - cons.accept(tile); - } - } - } + public float drawx(){ + return block().offset() + worldx(); + } - public Tile target(){ - Tile link = getLinked(); - return link == null ? this : link; - } + public float drawy(){ + return block().offset() + worldy(); + } - public Tile getNearby(GridPoint2 relative){ - return world.tile(x + relative.x, y + relative.y); - } + public Floor floor(){ + return floor; + } - public Tile getNearby(int dx, int dy){ - return world.tile(x + dx, y + dy); - } + public Block block(){ + return wall; + } - public Tile getNearby(int rotation){ - if(rotation == 0) return world.tile(x + 1, y); - if(rotation == 1) return world.tile(x, y + 1); - if(rotation == 2) return world.tile(x - 1, y); - if(rotation == 3) return world.tile(x, y - 1); - return null; - } + public Team getTeam(){ + return Team.all[team]; + } - public Tile[] getNearby(Tile[] temptiles){ - temptiles[0] = world.tile(x+1, y); - temptiles[1] = world.tile(x, y+1); - temptiles[2] = world.tile(x-1, y); - temptiles[3] = world.tile(x, y-1); - return temptiles; - } + public void setTeam(Team team){ + this.team = (byte) team.ordinal(); + } - public void updateOcclusion(){ - cost = 1; - cliffs = 0; - boolean occluded = false; + public byte getTeamID(){ + return team; + } - //check for occlusion - for(int i = 0; i < 8; i ++){ - GridPoint2 point = Geometry.d8[i]; - Tile tile = world.tile(x + point.x, y + point.y); - if(tile != null && tile.solid()){ - occluded = true; - break; - } - } + /** Returns the break time of the block, or the breaktime of the linked block, if this tile is linked. */ + public float getBreakTime(){ + Block block = target().block(); + if(Recipe.getByResult(block) != null){ + return Recipe.getByResult(block).cost; + }else{ + return 15f; + } + } - //check for bitmasking cliffs - for(int i = 0; i < 4; i ++){ - GridPoint2 pc = Geometry.d4[i]; - GridPoint2 pcprev = Geometry.d4[Mathf.mod(i - 1, 4)]; - GridPoint2 pcnext = Geometry.d4[(i + 1) % 4]; + public void setBlock(Block type, int rotation){ + synchronized(tileSetLock){ + preChanged(); + if(rotation < 0) rotation = (-rotation + 2); + this.wall = type; + this.link = 0; + setRotation((byte) (rotation % 4)); + changed(); + } + } - Tile tc = world.tile(x + pc.x, y + pc.y); - Tile tprev = world.tile(x + pcprev.x, y + pcprev.y); - Tile tnext = world.tile(x + pcnext.x, y + pcnext.y); + public void setBlock(Block type){ + synchronized(tileSetLock){ + preChanged(); + this.wall = type; + this.link = 0; + changed(); + } + } - //check for cardinal direction elevation changes and bitmask that - if(tc != null && tprev != null && tnext != null && ((tc.elevation < elevation && tc.elevation != -1))){ - cliffs |= (1 << (i*2)); - } - } - if(occluded){ - cost += 1; - } - } + public void setFloor(Floor type){ + this.floor = type; + } - private void preChanged(){ - synchronized (tileSetLock) { - if (entity != null) { - entity.removeFromProximity(); - } - } - } - - private void changed(){ + public byte getRotation(){ + return rotation; + } - synchronized (tileSetLock) { - if (entity != null) { - entity.remove(); - entity = null; - } + public void setRotation(byte rotation){ + this.rotation = rotation; + } - team = 0; + public byte getDump(){ + return rotation; + } - Block block = block(); + public void setDump(byte dump){ + this.rotation = dump; + } - if (block.hasEntity()) { - entity = block.getEntity().init(this, block.update); - entity.cons = new ConsumeModule(); - if(block.hasItems) entity.items = new InventoryModule(); - if(block.hasLiquids) entity.liquids = new LiquidModule(); - if(block.hasPower) entity.power = new PowerModule(); - entity.updateProximity(); - } + public boolean passable(){ + Block block = block(); + Block floor = floor(); + return isLinked() || !((floor.solid && (block == Blocks.air || block.solidifes)) || (block.solid && (!block.destructible && !block.update))); + } - updateOcclusion(); - } + /** Whether this block was placed by a player/unit. */ + public boolean synthetic(){ + Block block = block(); + return block.update || block.destructible; + } - world.notifyChanged(this); - } + public boolean solid(){ + Block block = block(); + Block floor = floor(); + return block.solid || cliffs != 0 || (floor.solid && (block == Blocks.air || block.solidifes)) || block.isSolidFor(this) + || (isLinked() && getLinked().block().isSolidFor(getLinked())); + } - @Override - public boolean isDead() { - return false; //tiles never die - } + public boolean breakable(){ + Block block = block(); + if(link == 0){ + return (block.destructible || block.breakable || block.update); + }else{ + return getLinked().breakable(); + } + } - @Override - public Vector2 getVelocity() { - return Vector2.Zero; - } + public boolean isLinked(){ + return link != 0; + } - @Override - public float getX() { - return drawx(); - } + /** Sets this to a linked tile, which sets the block to a blockpart. dx and dy can only be -8-7. */ + public void setLinked(byte dx, byte dy){ + setBlock(Blocks.blockpart); + link = Bits.packByte((byte) (dx + 8), (byte) (dy + 8)); + } - @Override - public float getY() { - return drawy(); - } + /** + * Returns the list of all tiles linked to this multiblock, or an empty array if it's not a multiblock. + * This array contains all linked tiles, including this tile itself. + */ + public synchronized Array getLinkedTiles(Array tmpArray){ + Block block = block(); + tmpArray.clear(); + if(block.isMultiblock()){ + int offsetx = -(block.size - 1) / 2; + int offsety = -(block.size - 1) / 2; + for(int dx = 0; dx < block.size; dx++){ + for(int dy = 0; dy < block.size; dy++){ + Tile other = world.tile(x + dx + offsetx, y + dy + offsety); + tmpArray.add(other); + } + } + }else{ + tmpArray.add(this); + } + return tmpArray; + } - @Override - public void setX(float x) {} + /** + * Returns the list of all tiles linked to this multiblock if it were this block, or an empty array if it's not a multiblock. + * This array contains all linked tiles, including this tile itself. + */ + public synchronized Array getLinkedTilesAs(Block block, Array tmpArray){ + tmpArray.clear(); + if(block.isMultiblock()){ + int offsetx = -(block.size - 1) / 2; + int offsety = -(block.size - 1) / 2; + for(int dx = 0; dx < block.size; dx++){ + for(int dy = 0; dy < block.size; dy++){ + Tile other = world.tile(x + dx + offsetx, y + dy + offsety); + tmpArray.add(other); + } + } + }else{ + tmpArray.add(this); + } + return tmpArray; + } - @Override - public void setY(float y) {} + /** Returns the block the multiblock is linked to, or null if it is not linked to any block. */ + public Tile getLinked(){ + if(link == 0){ + return null; + }else{ + byte dx = Bits.getLeftByte(link); + byte dy = Bits.getRightByte(link); + return world.tile(x - (dx - 8), y - (dy - 8)); + } + } - @Override - public String toString(){ - Block block = block(); - Block floor = floor(); - - return floor.name() + ":" + block.name() + "[" + x + "," + y + "] " + "entity=" + (entity == null ? "null" : ClassReflection.getSimpleName(entity.getClass())) + - (link != 0 ? " link=[" + (Bits.getLeftByte(link) - 8) + ", " + (Bits.getRightByte(link) - 8) + "]" : ""); - } -} + public void allNearby(Consumer cons){ + for(GridPoint2 point : Edges.getEdges(block().size)){ + Tile tile = world.tile(x + point.x, y + point.y); + if(tile != null){ + cons.accept(tile.target()); + } + } + } + + public void allInside(Consumer cons){ + for(GridPoint2 point : Edges.getInsideEdges(block().size)){ + Tile tile = world.tile(x + point.x, y + point.y); + if(tile != null){ + cons.accept(tile); + } + } + } + + public Tile target(){ + Tile link = getLinked(); + return link == null ? this : link; + } + + public Tile getNearby(GridPoint2 relative){ + return world.tile(x + relative.x, y + relative.y); + } + + public Tile getNearby(int dx, int dy){ + return world.tile(x + dx, y + dy); + } + + public Tile getNearby(int rotation){ + if(rotation == 0) return world.tile(x + 1, y); + if(rotation == 1) return world.tile(x, y + 1); + if(rotation == 2) return world.tile(x - 1, y); + if(rotation == 3) return world.tile(x, y - 1); + return null; + } + + public Tile[] getNearby(Tile[] temptiles){ + temptiles[0] = world.tile(x + 1, y); + temptiles[1] = world.tile(x, y + 1); + temptiles[2] = world.tile(x - 1, y); + temptiles[3] = world.tile(x, y - 1); + return temptiles; + } + + public void updateOcclusion(){ + cost = 1; + cliffs = 0; + boolean occluded = false; + + //check for occlusion + for(int i = 0; i < 8; i++){ + GridPoint2 point = Geometry.d8[i]; + Tile tile = world.tile(x + point.x, y + point.y); + if(tile != null && tile.solid()){ + occluded = true; + break; + } + } + + //check for bitmasking cliffs + for(int i = 0; i < 4; i++){ + GridPoint2 pc = Geometry.d4[i]; + GridPoint2 pcprev = Geometry.d4[Mathf.mod(i - 1, 4)]; + GridPoint2 pcnext = Geometry.d4[(i + 1) % 4]; + + Tile tc = world.tile(x + pc.x, y + pc.y); + Tile tprev = world.tile(x + pcprev.x, y + pcprev.y); + Tile tnext = world.tile(x + pcnext.x, y + pcnext.y); + + //check for cardinal direction elevation changes and bitmask that + if(tc != null && tprev != null && tnext != null && ((tc.elevation < elevation && tc.elevation != -1))){ + cliffs |= (1 << (i * 2)); + } + } + if(occluded){ + cost += 1; + } + } + + private void preChanged(){ + synchronized(tileSetLock){ + if(entity != null){ + entity.removeFromProximity(); + } + } + } + + private void changed(){ + + synchronized(tileSetLock){ + if(entity != null){ + entity.remove(); + entity = null; + } + + team = 0; + + Block block = block(); + + if(block.hasEntity()){ + entity = block.getEntity().init(this, block.update); + entity.cons = new ConsumeModule(); + if(block.hasItems) entity.items = new InventoryModule(); + if(block.hasLiquids) entity.liquids = new LiquidModule(); + if(block.hasPower) entity.power = new PowerModule(); + entity.updateProximity(); + } + + updateOcclusion(); + } + + world.notifyChanged(this); + } + + @Override + public boolean isDead(){ + return false; //tiles never die + } + + @Override + public Vector2 getVelocity(){ + return Vector2.Zero; + } + + @Override + public float getX(){ + return drawx(); + } + + @Override + public void setX(float x){ + } + + @Override + public float getY(){ + return drawy(); + } + + @Override + public void setY(float y){ + } + + @Override + public String toString(){ + Block block = block(); + Block floor = floor(); + + return floor.name() + ":" + block.name() + "[" + x + "," + y + "] " + "entity=" + (entity == null ? "null" : ClassReflection.getSimpleName(entity.getClass())) + + (link != 0 ? " link=[" + (Bits.getLeftByte(link) - 8) + ", " + (Bits.getRightByte(link) - 8) + "]" : ""); + } +} \ No newline at end of file diff --git a/core/src/io/anuke/mindustry/world/blocks/BlockPart.java b/core/src/io/anuke/mindustry/world/blocks/BlockPart.java index 37788c5f23..dd90451dec 100644 --- a/core/src/io/anuke/mindustry/world/blocks/BlockPart.java +++ b/core/src/io/anuke/mindustry/world/blocks/BlockPart.java @@ -5,75 +5,77 @@ import io.anuke.mindustry.type.Liquid; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; -/**Used for multiblocks. Each block that is not the center of the multiblock is a blockpart. +/** + * Used for multiblocks. Each block that is not the center of the multiblock is a blockpart. * Think of these as delegates to the actual block; all events are passed to the target block. - * They are made to share all properties from the linked tile/block.*/ + * They are made to share all properties from the linked tile/block. + */ public class BlockPart extends Block{ - public BlockPart() { - super("blockpart"); - solid = false; - hasPower = hasItems = hasLiquids = true; - } - - @Override - public void draw(Tile tile){ - //do nothing - } + public BlockPart(){ + super("blockpart"); + solid = false; + hasPower = hasItems = hasLiquids = true; + } - @Override - public void drawShadow(Tile tile){ - //also do nothing - } - - @Override - public boolean isSolidFor(Tile tile){ - return tile.getLinked() == null - || (tile.getLinked().block() instanceof BlockPart || tile.getLinked().solid() - || tile.getLinked().block().isSolidFor(tile.getLinked())); - } - - @Override - public void handleItem(Item item, Tile tile, Tile source){ - tile.getLinked().block().handleItem(item, tile.getLinked(), source); - } - - @Override - public boolean acceptItem(Item item, Tile tile, Tile source){ - return tile.getLinked().block().acceptItem(item, tile.getLinked(), source); - } + @Override + public void draw(Tile tile){ + //do nothing + } - @Override - public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){ - Block block = linked(tile); - return block.hasLiquids - && block.acceptLiquid(tile.getLinked(), source, liquid, amount); - } + @Override + public void drawShadow(Tile tile){ + //also do nothing + } - @Override - public void handleLiquid(Tile tile, Tile source, Liquid liquid, float amount){ - Block block = linked(tile); - block.handleLiquid(tile.getLinked(), source, liquid, amount); - } + @Override + public boolean isSolidFor(Tile tile){ + return tile.getLinked() == null + || (tile.getLinked().block() instanceof BlockPart || tile.getLinked().solid() + || tile.getLinked().block().isSolidFor(tile.getLinked())); + } - @Override - public float addPower(Tile tile, float amount){ - Block block = linked(tile); - if(block.hasPower){ - return block.addPower(tile.getLinked(), amount); - }else{ - return amount; - } - } - - @Override - public boolean acceptPower(Tile tile, Tile from, float amount) { + @Override + public void handleItem(Item item, Tile tile, Tile source){ + tile.getLinked().block().handleItem(item, tile.getLinked(), source); + } + + @Override + public boolean acceptItem(Item item, Tile tile, Tile source){ + return tile.getLinked().block().acceptItem(item, tile.getLinked(), source); + } + + @Override + public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){ + Block block = linked(tile); + return block.hasLiquids + && block.acceptLiquid(tile.getLinked(), source, liquid, amount); + } + + @Override + public void handleLiquid(Tile tile, Tile source, Liquid liquid, float amount){ + Block block = linked(tile); + block.handleLiquid(tile.getLinked(), source, liquid, amount); + } + + @Override + public float addPower(Tile tile, float amount){ + Block block = linked(tile); + if(block.hasPower){ + return block.addPower(tile.getLinked(), amount); + }else{ + return amount; + } + } + + @Override + public boolean acceptPower(Tile tile, Tile from, float amount){ Block block = linked(tile); return block.hasPower && block.acceptPower(tile.getLinked(), from, amount); } - - private Block linked(Tile tile){ - return tile.getLinked().block(); - } + + private Block linked(Tile tile){ + return tile.getLinked().block(); + } } diff --git a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java index 810049b4b6..6456b886ad 100644 --- a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java @@ -35,30 +35,51 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.*; -public class BuildBlock extends Block { - public BuildBlock(String name) { +public class BuildBlock extends Block{ + public BuildBlock(String name){ super(name); update = true; - size = Integer.parseInt(name.charAt(name.length()-1) + ""); + size = Integer.parseInt(name.charAt(name.length() - 1) + ""); health = 1; layer = Layer.placement; consumesTap = true; solidifes = true; } + @Remote(called = Loc.server, in = In.blocks) + public static void onDeconstructFinish(Tile tile, Block block){ + Effects.effect(Fx.breakBlock, tile.drawx(), tile.drawy(), block.size); + world.removeBlock(tile); + } + + @Remote(called = Loc.server, in = In.blocks) + public static void onConstructFinish(Tile tile, Block block, int builderID, byte rotation, Team team){ + world.setBlock(tile, block, team); + tile.setRotation(rotation); + tile.setTeam(team); + Effects.effect(Fx.placeBlock, tile.drawx(), tile.drawy(), block.size); + + //last builder was this local client player, call placed() + if(!headless && builderID == players[0].id){ + //this is run delayed, since if this is called on the server, all clients need to recieve the onBuildFinish() + //event first before they can recieve the placed() event modification results + threads.runDelay(() -> tile.block().placed(tile)); + } + } + @Override - public boolean isSolidFor(Tile tile) { + public boolean isSolidFor(Tile tile){ BuildEntity entity = tile.entity(); return entity == null || entity.recipe == null || entity.recipe.result.solid || entity.previous.solid; } @Override - public CursorType getCursor(Tile tile) { + public CursorType getCursor(Tile tile){ return CursorType.hand; } @Override - public void tapped(Tile tile, Player player) { + public void tapped(Tile tile, Player player){ BuildEntity entity = tile.entity(); //if the target is constructible, begin constructing @@ -84,7 +105,7 @@ public class BuildBlock extends Block { @Override public void afterDestroyed(Tile tile, TileEntity e){ - BuildEntity entity = (BuildEntity)e; + BuildEntity entity = (BuildEntity) e; if(entity.previous != null && entity.previous.synthetic()){ tile.setBlock(entity.previous); @@ -102,13 +123,13 @@ public class BuildBlock extends Block { if(entity.previous == null) return; - for (TextureRegion region : entity.previous.getBlockIcon()) { + for(TextureRegion region : entity.previous.getBlockIcon()){ Draw.rect(region, tile.drawx(), tile.drawy(), entity.previous.rotate ? tile.getRotation() * 90 : 0); } } @Override - public void drawLayer(Tile tile) { + public void drawLayer(Tile tile){ BuildEntity entity = tile.entity(); @@ -120,7 +141,7 @@ public class BuildBlock extends Block { for(TextureRegion region : target.getBlockIcon()){ Shaders.blockbuild.region = region; - Shaders.blockbuild.progress = (float)entity.progress; + Shaders.blockbuild.progress = (float) entity.progress; Shaders.blockbuild.apply(); Draw.rect(region, tile.drawx(), tile.drawy(), target.rotate ? tile.getRotation() * 90 : 0); @@ -130,7 +151,7 @@ public class BuildBlock extends Block { } @Override - public void drawShadow(Tile tile) { + public void drawShadow(Tile tile){ BuildEntity entity = tile.entity(); Recipe recipe = entity.recipe; @@ -144,45 +165,28 @@ public class BuildBlock extends Block { } @Override - public void update(Tile tile) { + public void update(Tile tile){ } @Override - public TileEntity getEntity() { + public TileEntity getEntity(){ return new BuildEntity(); } - @Remote(called = Loc.server, in = In.blocks) - public static void onDeconstructFinish(Tile tile, Block block){ - Effects.effect(Fx.breakBlock, tile.drawx(), tile.drawy(), block.size); - world.removeBlock(tile); - } - - @Remote(called = Loc.server, in = In.blocks) - public static void onConstructFinish(Tile tile, Block block, int builderID, byte rotation, Team team){ - world.setBlock(tile, block, team); - tile.setRotation(rotation); - tile.setTeam(team); - Effects.effect(Fx.placeBlock, tile.drawx(), tile.drawy(), block.size); - - //last builder was this local client player, call placed() - if(!headless && builderID == players[0].id){ - //this is run delayed, since if this is called on the server, all clients need to recieve the onBuildFinish() - //event first before they can recieve the placed() event modification results - threads.runDelay(() -> tile.block().placed(tile)); - } - } - public class BuildEntity extends TileEntity{ - /**The recipe of the block that is being constructed. - * If there is no recipe for this block, as is the case with rocks, 'previous' is used.*/ + /** + * The recipe of the block that is being constructed. + * If there is no recipe for this block, as is the case with rocks, 'previous' is used. + */ public Recipe recipe; public float progress = 0; public float buildCost; - /**The block that used to be here. - * If a non-recipe block is being deconstructed, this is the block that is being deconstructed.*/ + /** + * The block that used to be here. + * If a non-recipe block is being deconstructed, this is the block that is being deconstructed. + */ public Block previous; public int builderID = -1; @@ -191,8 +195,8 @@ public class BuildBlock extends Block { public void construct(Unit builder, TileEntity core, float amount){ float maxProgress = checkRequired(core.items, amount, false); - for (int i = 0; i < recipe.requirements.length; i++) { - accumulator[i] += recipe.requirements[i].amount*maxProgress; //add min amount progressed to the accumulator + for(int i = 0; i < recipe.requirements.length; i++){ + accumulator[i] += recipe.requirements[i].amount * maxProgress; //add min amount progressed to the accumulator } maxProgress = checkRequired(core.items, maxProgress, true); @@ -211,14 +215,14 @@ public class BuildBlock extends Block { public void deconstruct(Unit builder, TileEntity core, float amount){ Recipe recipe = Recipe.getByResult(previous); - if(recipe != null) { + if(recipe != null){ ItemStack[] requirements = recipe.requirements; - for (int i = 0; i < requirements.length; i++) { + for(int i = 0; i < requirements.length; i++){ accumulator[i] += requirements[i].amount * amount / 2f; //add scaled amount progressed to the accumulator int accumulated = (int) (accumulator[i]); //get amount - if (amount > 0) { //if it's positive, add it to the core + if(amount > 0){ //if it's positive, add it to the core int accepting = core.tile.block().acceptStack(requirements[i].item, accumulated, core.tile, builder); core.tile.block().handleStack(requirements[i].item, accepting, core.tile, builder); @@ -237,8 +241,8 @@ public class BuildBlock extends Block { private float checkRequired(InventoryModule inventory, float amount, boolean remove){ float maxProgress = amount; - for(int i = 0; i < recipe.requirements.length; i ++){ - int required = (int)(accumulator[i]); //calculate items that are required now + for(int i = 0; i < recipe.requirements.length; i++){ + int required = (int) (accumulator[i]); //calculate items that are required now if(inventory.get(recipe.requirements[i].item) == 0){ maxProgress = 0f; @@ -246,15 +250,15 @@ public class BuildBlock extends Block { //calculate how many items it can actually use int maxUse = Math.min(required, inventory.get(recipe.requirements[i].item)); //get this as a fraction - float fraction = maxUse / (float)required; + float fraction = maxUse / (float) required; //move max progress down if this fraction is less than 1 - maxProgress = Math.min(maxProgress, maxProgress*fraction); + maxProgress = Math.min(maxProgress, maxProgress * fraction); accumulator[i] -= maxUse; //remove stuff that is actually used - if(remove) { + if(remove){ inventory.remove(recipe.requirements[i].item, maxUse); } } @@ -288,7 +292,7 @@ public class BuildBlock extends Block { } @Override - public void write(DataOutputStream stream) throws IOException { + public void write(DataOutputStream stream) throws IOException{ stream.writeFloat(progress); stream.writeShort(previous == null ? -1 : previous.id); stream.writeShort(recipe == null ? -1 : recipe.result.id); @@ -304,15 +308,15 @@ public class BuildBlock extends Block { } @Override - public void read(DataInputStream stream) throws IOException { + public void read(DataInputStream stream) throws IOException{ progress = stream.readFloat(); short pid = stream.readShort(); short rid = stream.readShort(); byte acsize = stream.readByte(); - + if(acsize != -1){ accumulator = new float[acsize]; - for (int i = 0; i < acsize; i++) { + for(int i = 0; i < acsize; i++){ accumulator[i] = stream.readFloat(); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/Floor.java b/core/src/io/anuke/mindustry/world/blocks/Floor.java index 37c2938516..c03745e90d 100644 --- a/core/src/io/anuke/mindustry/world/blocks/Floor.java +++ b/core/src/io/anuke/mindustry/world/blocks/Floor.java @@ -20,175 +20,173 @@ import io.anuke.ucore.util.Mathf; import static io.anuke.mindustry.Vars.world; public class Floor extends Block{ - //TODO implement proper bitmasking - protected static IntIntMap bitmask = Mathf.mapInt(2, 1, 8, 2, 10, 3, 11, 4, 16, 5, 18, 6, 22, 7, 24, 8, - 26, 9, 27, 10, 30, 11, 31, 12, 64, 13, 66, 14, 72, 15, 74, 16, 75, 17, 80, 18, - 82, 19, 86, 20, 88, 21, 90, 22, 91, 23, 94, 24, 95, 25, 104, 26, 106, 27, 107, 28, - 120, 29, 122, 30, 123, 31, 126, 32, 127, 33, 208, 34, 210, 35, 214, 36, 216, 37, - 218, 38, 219, 39, 222, 40, 223, 41, 248, 42, 250, 43, 251, 44, 254, 45, 255, 46, 0, 47); - - protected TextureRegion edgeRegion; - protected TextureRegion[] edgeRegions; - protected TextureRegion[] cliffRegions; - protected TextureRegion[] variantRegions; - protected Vector2[] offsets; - protected Predicate blends = block -> block != this && !block.blendOverride(this); - protected boolean blend = true; - - /**number of different variant regions to use*/ - public int variants = 0; - /**edge fallback, used mainly for ores*/ + //TODO implement proper bitmasking + protected static IntIntMap bitmask = Mathf.mapInt(2, 1, 8, 2, 10, 3, 11, 4, 16, 5, 18, 6, 22, 7, 24, 8, + 26, 9, 27, 10, 30, 11, 31, 12, 64, 13, 66, 14, 72, 15, 74, 16, 75, 17, 80, 18, + 82, 19, 86, 20, 88, 21, 90, 22, 91, 23, 94, 24, 95, 25, 104, 26, 106, 27, 107, 28, + 120, 29, 122, 30, 123, 31, 126, 32, 127, 33, 208, 34, 210, 35, 214, 36, 216, 37, + 218, 38, 219, 39, 222, 40, 223, 41, 248, 42, 250, 43, 251, 44, 254, 45, 255, 46, 0, 47); + /** number of different variant regions to use */ + public int variants = 0; + /** edge fallback, used mainly for ores */ public String edge = "stone"; - /**Multiplies unit velocity by this when walked on.*/ - public float speedMultiplier = 1f; - /**Multiplies unit drag by this when walked on.*/ - public float dragMultiplier = 1f; - /**Damage taken per tick on this tile.*/ - public float damageTaken = 0f; - /**How many ticks it takes to drown on this.*/ - public float drownTime = 0f; - /**Effect when walking on this floor.*/ - public Effect walkEffect = BlockFx.ripple; - /**Effect displayed when drowning on this floor.*/ + /** Multiplies unit velocity by this when walked on. */ + public float speedMultiplier = 1f; + /** Multiplies unit drag by this when walked on. */ + public float dragMultiplier = 1f; + /** Damage taken per tick on this tile. */ + public float damageTaken = 0f; + /** How many ticks it takes to drown on this. */ + public float drownTime = 0f; + /** Effect when walking on this floor. */ + public Effect walkEffect = BlockFx.ripple; + /** Effect displayed when drowning on this floor. */ public Effect drownUpdateEffect = BlockFx.bubble; - /**Status effect applied when walking on.*/ - public StatusEffect status = StatusEffects.none; - /**Intensity of applied status effect.*/ - public float statusIntensity = 0.6f; - /**Color of this floor's liquid. Used for tinting sprites.*/ - public Color liquidColor; - /**liquids that drop from this block, used for pumps*/ - public Liquid liquidDrop = null; - /**Whether ores generate on this block.*/ - public boolean hasOres = false; - /**whether this block can be drowned in*/ - public boolean isLiquid; - /**if true, this block cannot be mined by players. useful for annoying things like stone.*/ - public boolean playerUnmineable = false; - - public Floor(String name) { - super(name); - variants = 3; - } + /** Status effect applied when walking on. */ + public StatusEffect status = StatusEffects.none; + /** Intensity of applied status effect. */ + public float statusIntensity = 0.6f; + /** Color of this floor's liquid. Used for tinting sprites. */ + public Color liquidColor; + /** liquids that drop from this block, used for pumps */ + public Liquid liquidDrop = null; + /** Whether ores generate on this block. */ + public boolean hasOres = false; + /** whether this block can be drowned in */ + public boolean isLiquid; + /** if true, this block cannot be mined by players. useful for annoying things like stone. */ + public boolean playerUnmineable = false; + protected TextureRegion edgeRegion; + protected TextureRegion[] edgeRegions; + protected TextureRegion[] cliffRegions; + protected TextureRegion[] variantRegions; + protected Vector2[] offsets; + protected Predicate blends = block -> block != this && !block.blendOverride(this); + protected boolean blend = true; - @Override - public void load() { - super.load(); + public Floor(String name){ + super(name); + variants = 3; + } - if(blend) { - edgeRegion = Draw.hasRegion(name + "edge") ? Draw.region(name + "edge") : Draw.region(edge + "edge"); - edgeRegions = new TextureRegion[8]; - offsets = new Vector2[8]; + @Override + public void load(){ + super.load(); - for(int i = 0; i < 8; i ++){ - int dx = Geometry.d8[i].x, dy = Geometry.d8[i].y; + if(blend){ + edgeRegion = Draw.hasRegion(name + "edge") ? Draw.region(name + "edge") : Draw.region(edge + "edge"); + edgeRegions = new TextureRegion[8]; + offsets = new Vector2[8]; - TextureRegion result = new TextureRegion(); + for(int i = 0; i < 8; i++){ + int dx = Geometry.d8[i].x, dy = Geometry.d8[i].y; - int sx = -dx*8+2, sy = -dy*8+2; - int x = Mathf.clamp(sx, 0, 12); - int y = Mathf.clamp(sy, 0, 12); - int w = Mathf.clamp(sx+8, 0, 12) - x, h = Mathf.clamp(sy+8, 0, 12) - y; + TextureRegion result = new TextureRegion(); - float rx = Mathf.clamp(dx*8, 0, 8-w); - float ry = Mathf.clamp(dy*8, 0, 8-h); + int sx = -dx * 8 + 2, sy = -dy * 8 + 2; + int x = Mathf.clamp(sx, 0, 12); + int y = Mathf.clamp(sy, 0, 12); + int w = Mathf.clamp(sx + 8, 0, 12) - x, h = Mathf.clamp(sy + 8, 0, 12) - y; - result.setTexture(edgeRegion.getTexture()); - result.setRegion(edgeRegion.getRegionX()+x, edgeRegion.getRegionY()+y+h, w, -h); + float rx = Mathf.clamp(dx * 8, 0, 8 - w); + float ry = Mathf.clamp(dy * 8, 0, 8 - h); - edgeRegions[i] = result; - offsets[i] = new Vector2(-4 + rx, -4 + ry); - } + result.setTexture(edgeRegion.getTexture()); + result.setRegion(edgeRegion.getRegionX() + x, edgeRegion.getRegionY() + y + h, w, -h); - cliffRegions = new TextureRegion[4]; - cliffRegions[0] = Draw.region(name + "-cliff-edge-2"); - cliffRegions[1] = Draw.region(name + "-cliff-edge"); - cliffRegions[2] = Draw.region(name + "-cliff-edge-1"); - cliffRegions[3] = Draw.region(name + "-cliff-side"); - } + edgeRegions[i] = result; + offsets[i] = new Vector2(-4 + rx, -4 + ry); + } - //load variant regions for drawing - if(variants > 0){ - variantRegions = new TextureRegion[variants]; + cliffRegions = new TextureRegion[4]; + cliffRegions[0] = Draw.region(name + "-cliff-edge-2"); + cliffRegions[1] = Draw.region(name + "-cliff-edge"); + cliffRegions[2] = Draw.region(name + "-cliff-edge-1"); + cliffRegions[3] = Draw.region(name + "-cliff-side"); + } - for (int i = 0; i < variants; i++) { - variantRegions[i] = Draw.region(name + (i + 1)); - } - }else{ - variantRegions = new TextureRegion[1]; - variantRegions[0] = Draw.region(name); - } - } + //load variant regions for drawing + if(variants > 0){ + variantRegions = new TextureRegion[variants]; - @Override - public void init(){ - super.init(); + for(int i = 0; i < variants; i++){ + variantRegions[i] = Draw.region(name + (i + 1)); + } + }else{ + variantRegions = new TextureRegion[1]; + variantRegions[0] = Draw.region(name); + } + } - if(isLiquid && liquidColor == null){ - throw new RuntimeException("All liquids must define a liquidColor! Problematic block: " + name); - } - } + @Override + public void init(){ + super.init(); - @Override - public void drawNonLayer(Tile tile){ - MathUtils.random.setSeed(tile.id()); + if(isLiquid && liquidColor == null){ + throw new RuntimeException("All liquids must define a liquidColor! Problematic block: " + name); + } + } - drawEdges(tile, true); - } - - @Override - public void draw(Tile tile){ - MathUtils.random.setSeed(tile.id()); + @Override + public void drawNonLayer(Tile tile){ + MathUtils.random.setSeed(tile.id()); - Draw.rect(variantRegions[Mathf.randomSeed(tile.id(), 0, Math.max(0, variantRegions.length-1))], tile.worldx(), tile.worldy()); + drawEdges(tile, true); + } - if(tile.cliffs != 0 && cliffRegions != null){ - for(int i = 0; i < 4; i ++){ - if((tile.cliffs & (1 << i*2)) != 0) { - Draw.colorl(i > 1 ? 0.6f : 1f); + @Override + public void draw(Tile tile){ + MathUtils.random.setSeed(tile.id()); - boolean above = (tile.cliffs & (1 << ((i+1)%4)*2)) != 0, below = (tile.cliffs & (1 << (Mathf.mod(i-1, 4))*2)) != 0; + Draw.rect(variantRegions[Mathf.randomSeed(tile.id(), 0, Math.max(0, variantRegions.length - 1))], tile.worldx(), tile.worldy()); - if(above && below){ - Draw.rect(cliffRegions[0], tile.worldx(), tile.worldy(), i * 90); - }else if(above){ - Draw.rect(cliffRegions[1], tile.worldx(), tile.worldy(), i * 90); - }else if(below){ - Draw.rect(cliffRegions[2], tile.worldx(), tile.worldy(), i * 90); - }else{ - Draw.rect(cliffRegions[3], tile.worldx(), tile.worldy(), i * 90); - } - } - } - } - Draw.reset(); + if(tile.cliffs != 0 && cliffRegions != null){ + for(int i = 0; i < 4; i++){ + if((tile.cliffs & (1 << i * 2)) != 0){ + Draw.colorl(i > 1 ? 0.6f : 1f); - drawEdges(tile, false); - } + boolean above = (tile.cliffs & (1 << ((i + 1) % 4) * 2)) != 0, below = (tile.cliffs & (1 << (Mathf.mod(i - 1, 4)) * 2)) != 0; - public boolean blendOverride(Block block){ - return false; - } + if(above && below){ + Draw.rect(cliffRegions[0], tile.worldx(), tile.worldy(), i * 90); + }else if(above){ + Draw.rect(cliffRegions[1], tile.worldx(), tile.worldy(), i * 90); + }else if(below){ + Draw.rect(cliffRegions[2], tile.worldx(), tile.worldy(), i * 90); + }else{ + Draw.rect(cliffRegions[3], tile.worldx(), tile.worldy(), i * 90); + } + } + } + } + Draw.reset(); - protected void drawEdges(Tile tile, boolean sameLayer){ - if(!blend || tile.cliffs > 0) return; + drawEdges(tile, false); + } - for(int i = 0; i < 8; i ++){ - int dx = Geometry.d8[i].x, dy = Geometry.d8[i].y; + public boolean blendOverride(Block block){ + return false; + } - Tile other = world.tile(tile.x+dx, tile.y+dy); + protected void drawEdges(Tile tile, boolean sameLayer){ + if(!blend || tile.cliffs > 0) return; - if(other == null) continue; + for(int i = 0; i < 8; i++){ + int dx = Geometry.d8[i].x, dy = Geometry.d8[i].y; - Floor floor = other.floor(); + Tile other = world.tile(tile.x + dx, tile.y + dy); - if(floor.id <= this.id || !blends.test(floor) || (floor.cacheLayer.ordinal() > this.cacheLayer.ordinal() && !sameLayer) || - (sameLayer && floor.cacheLayer == this.cacheLayer)) continue; + if(other == null) continue; - TextureRegion region = floor.edgeRegions[i]; + Floor floor = other.floor(); - Draw.crect(region, tile.worldx() + floor.offsets[i].x, tile.worldy() + floor.offsets[i].y, region.getRegionWidth(), region.getRegionHeight()); - } - } + if(floor.id <= this.id || !blends.test(floor) || (floor.cacheLayer.ordinal() > this.cacheLayer.ordinal() && !sameLayer) || + (sameLayer && floor.cacheLayer == this.cacheLayer)) continue; -} + TextureRegion region = floor.edgeRegions[i]; + + Draw.crect(region, tile.worldx() + floor.offsets[i].x, tile.worldy() + floor.offsets[i].y, region.getRegionWidth(), region.getRegionHeight()); + } + } + +} \ No newline at end of file diff --git a/core/src/io/anuke/mindustry/world/blocks/LiquidBlock.java b/core/src/io/anuke/mindustry/world/blocks/LiquidBlock.java index 11b98f66b3..1d640466c6 100644 --- a/core/src/io/anuke/mindustry/world/blocks/LiquidBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/LiquidBlock.java @@ -2,52 +2,52 @@ package io.anuke.mindustry.world.blocks; import com.badlogic.gdx.graphics.g2d.TextureRegion; import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.meta.BlockGroup; import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.meta.BlockGroup; import io.anuke.mindustry.world.modules.LiquidModule; import io.anuke.ucore.graphics.Draw; public class LiquidBlock extends Block{ - protected TextureRegion liquidRegion, bottomRegion, topRegion; - - public LiquidBlock(String name) { - super(name); - update = true; - solid = true; - hasLiquids = true; - group = BlockGroup.liquids; - outputsLiquid = true; - } + protected TextureRegion liquidRegion, bottomRegion, topRegion; - @Override - public void load() { - super.load(); + public LiquidBlock(String name){ + super(name); + update = true; + solid = true; + hasLiquids = true; + group = BlockGroup.liquids; + outputsLiquid = true; + } - liquidRegion = Draw.region(name + "-liquid"); - topRegion = Draw.region(name + "-top"); - bottomRegion = Draw.region(name + "-bottom"); - } + @Override + public void load(){ + super.load(); - @Override - public TextureRegion[] getIcon(){ - return new TextureRegion[]{Draw.region(name() + "-bottom"), Draw.region(name() + "-top")}; - } - - @Override - public void draw(Tile tile){ - LiquidModule mod = tile.entity.liquids; + liquidRegion = Draw.region(name + "-liquid"); + topRegion = Draw.region(name + "-top"); + bottomRegion = Draw.region(name + "-bottom"); + } - int rotation = rotate ? tile.getRotation() * 90 : 0; - - Draw.rect(bottomRegion, tile.drawx(), tile.drawy(), rotation); - - if(mod.total() > 0.001f){ - Draw.color(mod.current().color); - Draw.alpha(mod.total() / liquidCapacity); - Draw.rect(liquidRegion, tile.drawx(), tile.drawy(), rotation); - Draw.color(); - } - - Draw.rect(topRegion, tile.drawx(), tile.drawy(), rotation); - } + @Override + public TextureRegion[] getIcon(){ + return new TextureRegion[]{Draw.region(name() + "-bottom"), Draw.region(name() + "-top")}; + } + + @Override + public void draw(Tile tile){ + LiquidModule mod = tile.entity.liquids; + + int rotation = rotate ? tile.getRotation() * 90 : 0; + + Draw.rect(bottomRegion, tile.drawx(), tile.drawy(), rotation); + + if(mod.total() > 0.001f){ + Draw.color(mod.current().color); + Draw.alpha(mod.total() / liquidCapacity); + Draw.rect(liquidRegion, tile.drawx(), tile.drawy(), rotation); + Draw.color(); + } + + Draw.rect(topRegion, tile.drawx(), tile.drawy(), rotation); + } } diff --git a/core/src/io/anuke/mindustry/world/blocks/Ore.java b/core/src/io/anuke/mindustry/world/blocks/Ore.java index ff0f155849..398771f070 100644 --- a/core/src/io/anuke/mindustry/world/blocks/Ore.java +++ b/core/src/io/anuke/mindustry/world/blocks/Ore.java @@ -4,9 +4,9 @@ import io.anuke.mindustry.content.blocks.Blocks; public class Ore extends Floor{ - public Ore(String name) { - super(name); - blends = block -> block != this && block != Blocks.stone; - } + public Ore(String name){ + super(name); + blends = block -> block != this && block != Blocks.stone; + } } diff --git a/core/src/io/anuke/mindustry/world/blocks/OreBlock.java b/core/src/io/anuke/mindustry/world/blocks/OreBlock.java index 18fcd86711..57c7f67aa2 100644 --- a/core/src/io/anuke/mindustry/world/blocks/OreBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/OreBlock.java @@ -9,7 +9,7 @@ import io.anuke.mindustry.world.Tile; import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.util.Mathf; -public class OreBlock extends Floor { +public class OreBlock extends Floor{ public Floor base; public OreBlock(Item ore, Floor base){ @@ -32,7 +32,7 @@ public class OreBlock extends Floor { @Override public void draw(Tile tile){ - Draw.rect(variantRegions[Mathf.randomSeed(tile.id(), 0, Math.max(0, variantRegions.length-1))], tile.worldx(), tile.worldy()); + Draw.rect(variantRegions[Mathf.randomSeed(tile.id(), 0, Math.max(0, variantRegions.length - 1))], tile.worldx(), tile.worldy()); drawEdges(tile, false); } @@ -45,7 +45,7 @@ public class OreBlock extends Floor { } @Override - public boolean blendOverride(Block block) { + public boolean blendOverride(Block block){ return block == base; } } diff --git a/core/src/io/anuke/mindustry/world/blocks/PowerBlock.java b/core/src/io/anuke/mindustry/world/blocks/PowerBlock.java index 095aa42034..83e550a7e4 100644 --- a/core/src/io/anuke/mindustry/world/blocks/PowerBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/PowerBlock.java @@ -4,12 +4,12 @@ import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.meta.BlockGroup; public abstract class PowerBlock extends Block{ - - public PowerBlock(String name) { - super(name); - update = true; - solid = true; - hasPower = true; - group = BlockGroup.power; - } + + public PowerBlock(String name){ + super(name); + update = true; + solid = true; + hasPower = true; + group = BlockGroup.power; + } } diff --git a/core/src/io/anuke/mindustry/world/blocks/Rock.java b/core/src/io/anuke/mindustry/world/blocks/Rock.java index 976e398a68..f5771124fe 100644 --- a/core/src/io/anuke/mindustry/world/blocks/Rock.java +++ b/core/src/io/anuke/mindustry/world/blocks/Rock.java @@ -6,28 +6,28 @@ import io.anuke.mindustry.world.Tile; import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.util.Mathf; -public class Rock extends Block { +public class Rock extends Block{ protected TextureRegion[] shadowRegions, regions; protected int variants; - public Rock(String name) { + public Rock(String name){ super(name); breakable = true; alwaysReplace = true; } @Override - public void draw(Tile tile) { + public void draw(Tile tile){ if(variants > 0){ - Draw.rect(regions[Mathf.randomSeed(tile.id(), 0, Math.max(0, regions.length-1))], tile.worldx(), tile.worldy()); + Draw.rect(regions[Mathf.randomSeed(tile.id(), 0, Math.max(0, regions.length - 1))], tile.worldx(), tile.worldy()); }else{ Draw.rect(region, tile.worldx(), tile.worldy()); } } @Override - public void drawShadow(Tile tile) { - if(shadowRegions != null) { + public void drawShadow(Tile tile){ + if(shadowRegions != null){ Draw.rect(shadowRegions[(Mathf.randomSeed(tile.id(), 0, variants - 1))], tile.worldx(), tile.worldy()); }else if(shadowRegion != null){ Draw.rect(shadowRegion, tile.drawx(), tile.drawy()); @@ -35,16 +35,16 @@ public class Rock extends Block { } @Override - public void load() { + public void load(){ super.load(); if(variants > 0){ shadowRegions = new TextureRegion[variants]; regions = new TextureRegion[variants]; - for (int i = 0; i < variants; i++) { - shadowRegions[i] = Draw.region(name + "shadow" + (i+1)); - regions[i] = Draw.region(name + (i+1)); + for(int i = 0; i < variants; i++){ + shadowRegions[i] = Draw.region(name + "shadow" + (i + 1)); + regions[i] = Draw.region(name + (i + 1)); } } } diff --git a/core/src/io/anuke/mindustry/world/blocks/SelectionTrait.java b/core/src/io/anuke/mindustry/world/blocks/SelectionTrait.java index 615acb974a..45ba5bb105 100644 --- a/core/src/io/anuke/mindustry/world/blocks/SelectionTrait.java +++ b/core/src/io/anuke/mindustry/world/blocks/SelectionTrait.java @@ -12,7 +12,7 @@ import io.anuke.ucore.scene.ui.layout.Table; import static io.anuke.mindustry.Vars.control; -public interface SelectionTrait { +public interface SelectionTrait{ default void buildItemTable(Table table, Supplier holder, Consumer consumer){ @@ -32,7 +32,7 @@ public interface SelectionTrait { button.getStyle().imageUp = new TextureRegionDrawable(new TextureRegion(items.get(i).region)); button.setChecked(holder.get().id == f); - if(i++%4 == 3){ + if(i++ % 4 == 3){ cont.row(); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/StaticBlock.java b/core/src/io/anuke/mindustry/world/blocks/StaticBlock.java index 61f11a5549..c39015cff9 100644 --- a/core/src/io/anuke/mindustry/world/blocks/StaticBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/StaticBlock.java @@ -5,9 +5,9 @@ import io.anuke.mindustry.world.Block; public class StaticBlock extends Block{ - public StaticBlock(String name) { - super(name); - cacheLayer = CacheLayer.walls; - } + public StaticBlock(String name){ + super(name); + cacheLayer = CacheLayer.walls; + } } diff --git a/core/src/io/anuke/mindustry/world/blocks/Wall.java b/core/src/io/anuke/mindustry/world/blocks/Wall.java index e28fc5d7e5..e2cb089f92 100644 --- a/core/src/io/anuke/mindustry/world/blocks/Wall.java +++ b/core/src/io/anuke/mindustry/world/blocks/Wall.java @@ -5,16 +5,16 @@ import io.anuke.mindustry.world.meta.BlockGroup; public class Wall extends Block{ - public Wall(String name) { - super(name); - solid = true; - destructible = true; - group = BlockGroup.walls; - } + public Wall(String name){ + super(name); + solid = true; + destructible = true; + group = BlockGroup.walls; + } - @Override - public boolean canReplace(Block other){ - return super.canReplace(other) && health > other.health; - } + @Override + public boolean canReplace(Block other){ + return super.canReplace(other) && health > other.health; + } } diff --git a/core/src/io/anuke/mindustry/world/blocks/defense/DeflectorWall.java b/core/src/io/anuke/mindustry/world/blocks/defense/DeflectorWall.java index 1f39e386f9..c8c72c9439 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/DeflectorWall.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/DeflectorWall.java @@ -16,13 +16,13 @@ import io.anuke.ucore.util.Physics; import static io.anuke.mindustry.Vars.tilesize; -public class DeflectorWall extends Wall { +public class DeflectorWall extends Wall{ static final float hitTime = 10f; protected float maxDamageDeflect = 5f; protected Rectangle rect = new Rectangle(); - public DeflectorWall(String name) { + public DeflectorWall(String name){ super(name); } @@ -41,7 +41,7 @@ public class DeflectorWall extends Wall { Draw.rect("blank", tile.drawx(), tile.drawy(), tilesize * size, tilesize * size); Draw.reset(); - entity.hit = Mathf.clamp(entity.hit - Timers.delta()/hitTime); + entity.hit = Mathf.clamp(entity.hit - Timers.delta() / hitTime); Graphics.setNormalBlending(); } @@ -56,13 +56,13 @@ public class DeflectorWall extends Wall { float penX = Math.abs(entity.x - bullet.x), penY = Math.abs(entity.y - bullet.y); Vector2 position = Physics.raycastRect(bullet.lastPosition().x, bullet.lastPosition().y, bullet.x, bullet.y, - rect.setCenter(entity.x, entity.y).setSize(size * tilesize + bullet.hitbox.width + bullet.hitbox.height)); + rect.setCenter(entity.x, entity.y).setSize(size * tilesize + bullet.hitbox.width + bullet.hitbox.height)); if(position != null){ bullet.set(position.x, position.y); } - if(penX > penY) { + if(penX > penY){ bullet.getVelocity().x *= -1; }else{ bullet.getVelocity().y *= -1; @@ -73,11 +73,11 @@ public class DeflectorWall extends Wall { bullet.scaleTime(1f); bullet.supressCollision(); - ((DeflectorEntity)entity).hit = 1f; + ((DeflectorEntity) entity).hit = 1f; } @Override - public TileEntity getEntity() { + public TileEntity getEntity(){ return new DeflectorEntity(); } diff --git a/core/src/io/anuke/mindustry/world/blocks/defense/Door.java b/core/src/io/anuke/mindustry/world/blocks/defense/Door.java index 824fbaa961..d186815e23 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/Door.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/Door.java @@ -20,86 +20,86 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.threads; public class Door extends Wall{ - protected final Rectangle rect = new Rectangle(); + protected final Rectangle rect = new Rectangle(); - protected Effect openfx = BlockFx.dooropen; - protected Effect closefx = BlockFx.doorclose; + protected Effect openfx = BlockFx.dooropen; + protected Effect closefx = BlockFx.doorclose; - protected TextureRegion openRegion; + protected TextureRegion openRegion; - public Door(String name) { - super(name); - solid = false; - solidifes = true; - consumesTap = true; - } + public Door(String name){ + super(name); + solid = false; + solidifes = true; + consumesTap = true; + } - @Override - public void load() { - super.load(); - openRegion = Draw.region(name + "-open"); - } + @Override + public void load(){ + super.load(); + openRegion = Draw.region(name + "-open"); + } - @Override - public void draw(Tile tile){ - DoorEntity entity = tile.entity(); - - if(!entity.open){ - Draw.rect(region, tile.drawx(), tile.drawy()); - }else{ - Draw.rect(openRegion, tile.drawx(), tile.drawy()); - } - } + @Override + public void draw(Tile tile){ + DoorEntity entity = tile.entity(); - @Override - public CursorType getCursor(Tile tile){ - return CursorType.hand; - } - - @Override - public boolean isSolidFor(Tile tile){ - DoorEntity entity = tile.entity(); - return !entity.open; - } + if(!entity.open){ + Draw.rect(region, tile.drawx(), tile.drawy()); + }else{ + Draw.rect(openRegion, tile.drawx(), tile.drawy()); + } + } - @Override - public void tapped(Tile tile, Player player){ - DoorEntity entity = tile.entity(); + @Override + public CursorType getCursor(Tile tile){ + return CursorType.hand; + } - threads.run(() -> { + @Override + public boolean isSolidFor(Tile tile){ + DoorEntity entity = tile.entity(); + return !entity.open; + } - if(Units.anyEntities(tile) && entity.open){ - return; - } + @Override + public void tapped(Tile tile, Player player){ + DoorEntity entity = tile.entity(); - entity.open = !entity.open; - if(!entity.open){ - Effects.effect(closefx, tile.drawx(), tile.drawy()); - }else{ - Effects.effect(openfx, tile.drawx(), tile.drawy()); - } - }); - } - - @Override - public TileEntity getEntity(){ - return new DoorEntity(); - } - - public class DoorEntity extends TileEntity{ - public boolean open = false; - - @Override - public void write(DataOutputStream stream) throws IOException{ - super.write(stream); - stream.writeBoolean(open); - } - - @Override - public void read(DataInputStream stream) throws IOException{ - super.read(stream); - open = stream.readBoolean(); - } - } + threads.run(() -> { + + if(Units.anyEntities(tile) && entity.open){ + return; + } + + entity.open = !entity.open; + if(!entity.open){ + Effects.effect(closefx, tile.drawx(), tile.drawy()); + }else{ + Effects.effect(openfx, tile.drawx(), tile.drawy()); + } + }); + } + + @Override + public TileEntity getEntity(){ + return new DoorEntity(); + } + + public class DoorEntity extends TileEntity{ + public boolean open = false; + + @Override + public void write(DataOutputStream stream) throws IOException{ + super.write(stream); + stream.writeBoolean(open); + } + + @Override + public void read(DataInputStream stream) throws IOException{ + super.read(stream); + open = stream.readBoolean(); + } + } } diff --git a/core/src/io/anuke/mindustry/world/blocks/defense/PhaseWall.java b/core/src/io/anuke/mindustry/world/blocks/defense/PhaseWall.java index 073968c565..6127a8b469 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/PhaseWall.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/PhaseWall.java @@ -5,16 +5,16 @@ import io.anuke.mindustry.world.blocks.Wall; import io.anuke.ucore.core.Timers; import io.anuke.ucore.util.Mathf; -public class PhaseWall extends Wall { +public class PhaseWall extends Wall{ protected float regenSpeed = 0.25f; - public PhaseWall(String name) { + public PhaseWall(String name){ super(name); update = true; } @Override - public void update(Tile tile) { + public void update(Tile tile){ tile.entity.health = Mathf.clamp(tile.entity.health + regenSpeed * Timers.delta(), 0f, health); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/defense/ShieldBlock.java b/core/src/io/anuke/mindustry/world/blocks/defense/ShieldBlock.java index 80984d505f..b87dc6ac4e 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/ShieldBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/ShieldBlock.java @@ -11,71 +11,71 @@ import io.anuke.ucore.util.Mathf; //TODO remove public class ShieldBlock extends PowerBlock{ - public float shieldRadius = 40f; - public float powerDrain = 0.005f; - public float powerPerDamage = 0.06f; - public float maxRadius = 40f; - public float radiusScale = 300f; - - public ShieldBlock(String name) { - super(name); - powerCapacity = 80f; - } - - @Override - public void setStats(){ - super.setStats(); - //stats.add("powersecond", Strings.toFixed(powerDrain*60, 2)); - //stats.add("powerdraindamage", Strings.toFixed(powerPerDamage, 2)); - //stats.add("shieldradius", (int)shieldRadius); - } + public float shieldRadius = 40f; + public float powerDrain = 0.005f; + public float powerPerDamage = 0.06f; + public float maxRadius = 40f; + public float radiusScale = 300f; - @Override - public void update(Tile tile){ - ShieldEntity entity = tile.entity(); + public ShieldBlock(String name){ + super(name); + powerCapacity = 80f; + } - if(entity.shield == null){ - entity.shield = new Shield(tile); - if(Vars.infiniteAmmo && Vars.debug) - entity.shield.add(); - } + @Override + public void setStats(){ + super.setStats(); + //stats.add("powersecond", Strings.toFixed(powerDrain*60, 2)); + //stats.add("powerdraindamage", Strings.toFixed(powerPerDamage, 2)); + //stats.add("shieldradius", (int)shieldRadius); + } - if(entity.power.amount > powerPerDamage){ - if(!entity.shield.active){ - entity.shield.add(); - } + @Override + public void update(Tile tile){ + ShieldEntity entity = tile.entity(); - entity.power.amount -= powerDrain * Timers.delta(); - }else{ - if(entity.shield.active && !(Vars.infiniteAmmo && Vars.debug)){ - entity.shield.removeDelay(); - } - } - - entity.shield.radius = Mathf.lerp(entity.shield.radius, Math.min(entity.power.amount / powerCapacity * radiusScale, maxRadius), Timers.delta() * 0.05f); + if(entity.shield == null){ + entity.shield = new Shield(tile); + if(Vars.infiniteAmmo && Vars.debug) + entity.shield.add(); + } - } + if(entity.power.amount > powerPerDamage){ + if(!entity.shield.active){ + entity.shield.add(); + } - @Override - public TileEntity getEntity(){ - return new ShieldEntity(); - } - - public void handleBullet(Tile tile, BulletEntity bullet){ - ShieldEntity entity = tile.entity(); - - if(entity.power.amount < bullet.getDamage() * powerPerDamage){ - return; - } - - bullet.remove(); - //Effects.effect(bullet.damage > 5 ? BulletFx.shieldhit : BulletFx.laserhit, bullet); - //if(!headless) renderer.addShieldHit(bullet.x, bullet.y); - - entity.power.amount -= bullet.getDamage() * powerPerDamage; - } + entity.power.amount -= powerDrain * Timers.delta(); + }else{ + if(entity.shield.active && !(Vars.infiniteAmmo && Vars.debug)){ + entity.shield.removeDelay(); + } + } - static class ShieldEntity extends TileEntity{ - Shield shield; - } + entity.shield.radius = Mathf.lerp(entity.shield.radius, Math.min(entity.power.amount / powerCapacity * radiusScale, maxRadius), Timers.delta() * 0.05f); + + } + + @Override + public TileEntity getEntity(){ + return new ShieldEntity(); + } + + public void handleBullet(Tile tile, BulletEntity bullet){ + ShieldEntity entity = tile.entity(); + + if(entity.power.amount < bullet.getDamage() * powerPerDamage){ + return; + } + + bullet.remove(); + //Effects.effect(bullet.damage > 5 ? BulletFx.shieldhit : BulletFx.laserhit, bullet); + //if(!headless) renderer.addShieldHit(bullet.x, bullet.y); + + entity.power.amount -= bullet.getDamage() * powerPerDamage; + } + + static class ShieldEntity extends TileEntity{ + Shield shield; + } } diff --git a/core/src/io/anuke/mindustry/world/blocks/defense/ShieldedWallBlock.java b/core/src/io/anuke/mindustry/world/blocks/defense/ShieldedWallBlock.java index 261836887b..9416aa1258 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/ShieldedWallBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/ShieldedWallBlock.java @@ -11,66 +11,66 @@ import static io.anuke.mindustry.Vars.tilesize; //TODO remove public class ShieldedWallBlock extends PowerBlock{ - static final float hitTime = 18f; - static final Color hitColor = Color.SKY.cpy().mul(1.2f); - public float powerPerDamage = 0.08f; + static final float hitTime = 18f; + static final Color hitColor = Color.SKY.cpy().mul(1.2f); + public float powerPerDamage = 0.08f; - public ShieldedWallBlock(String name) { - super(name); - destructible = true; - update = false; - } - - @Override - public float handleDamage(Tile tile, float amount){ - float drain = amount * powerPerDamage; - ShieldedWallEntity entity = tile.entity(); - - if(entity.power.amount > drain){ - entity.power.amount -= drain; - entity.hit = hitTime; - return 0; - }else if(entity.power.amount > 0){ - int reduction = (int)(entity.power.amount / powerPerDamage); - entity.power.amount = 0; - - return amount - reduction; - } - - return amount; - } + public ShieldedWallBlock(String name){ + super(name); + destructible = true; + update = false; + } - @Override - public void setStats(){ - super.setStats(); - //stats.add("powerdraindamage", Strings.toFixed(powerPerDamage, 2)); - } - - @Override - public void draw(Tile tile){ - super.draw(tile); - - ShieldedWallEntity entity = tile.entity(); - - if(entity.power.amount > powerPerDamage){ - //renderer.addShield(() -> Draw.rect("blank", tile.worldx(), tile.worldy(), tilesize, tilesize)); - } - - Draw.color(hitColor); - Draw.alpha(entity.hit / hitTime * 0.9f); - Draw.rect("blank", tile.worldx(), tile.worldy(), tilesize, tilesize); - Draw.reset(); - - entity.hit -= Timers.delta(); - entity.hit = Math.max(entity.hit, 0); - } - - @Override - public TileEntity getEntity(){ - return new ShieldedWallEntity(); - } - - static class ShieldedWallEntity extends TileEntity{ - public float hit; - } + @Override + public float handleDamage(Tile tile, float amount){ + float drain = amount * powerPerDamage; + ShieldedWallEntity entity = tile.entity(); + + if(entity.power.amount > drain){ + entity.power.amount -= drain; + entity.hit = hitTime; + return 0; + }else if(entity.power.amount > 0){ + int reduction = (int) (entity.power.amount / powerPerDamage); + entity.power.amount = 0; + + return amount - reduction; + } + + return amount; + } + + @Override + public void setStats(){ + super.setStats(); + //stats.add("powerdraindamage", Strings.toFixed(powerPerDamage, 2)); + } + + @Override + public void draw(Tile tile){ + super.draw(tile); + + ShieldedWallEntity entity = tile.entity(); + + if(entity.power.amount > powerPerDamage){ + //renderer.addShield(() -> Draw.rect("blank", tile.worldx(), tile.worldy(), tilesize, tilesize)); + } + + Draw.color(hitColor); + Draw.alpha(entity.hit / hitTime * 0.9f); + Draw.rect("blank", tile.worldx(), tile.worldy(), tilesize, tilesize); + Draw.reset(); + + entity.hit -= Timers.delta(); + entity.hit = Math.max(entity.hit, 0); + } + + @Override + public TileEntity getEntity(){ + return new ShieldedWallEntity(); + } + + static class ShieldedWallEntity extends TileEntity{ + public float hit; + } } diff --git a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/ArtilleryTurret.java b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/ArtilleryTurret.java index 853695ba46..3e1f66e7dc 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/ArtilleryTurret.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/ArtilleryTurret.java @@ -9,11 +9,13 @@ import io.anuke.ucore.util.Mathf; import static io.anuke.mindustry.Vars.tilesize; -/**Artillery turrets have special shooting calculations done to hit targets.*/ -public class ArtilleryTurret extends ItemTurret { +/** + * Artillery turrets have special shooting calculations done to hit targets. + */ +public class ArtilleryTurret extends ItemTurret{ protected float velocityInaccuracy = 0f; - public ArtilleryTurret(String name) { + public ArtilleryTurret(String name){ super(name); targetAir = false; } @@ -34,9 +36,9 @@ public class ArtilleryTurret extends ItemTurret { float dst = entity.distanceTo(predict.x, predict.y); float maxTraveled = type.bullet.lifetime * type.bullet.speed; - for (int i = 0; i < shots; i++) { + for(int i = 0; i < shots; i++){ Bullet.create(ammo.bullet, tile.entity, tile.getTeam(), tile.drawx() + tr.x, tile.drawy() + tr.y, - entity.rotation + Mathf.range(inaccuracy + type.inaccuracy), dst/maxTraveled + Mathf.range(velocityInaccuracy)); + entity.rotation + Mathf.range(inaccuracy + type.inaccuracy), dst / maxTraveled + Mathf.range(velocityInaccuracy)); } effects(tile); diff --git a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/BurstTurret.java b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/BurstTurret.java index 1093721863..c31fe33cd4 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/BurstTurret.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/BurstTurret.java @@ -7,11 +7,11 @@ import io.anuke.ucore.util.Mathf; import static io.anuke.mindustry.Vars.tilesize; -public class BurstTurret extends ItemTurret { +public class BurstTurret extends ItemTurret{ protected float burstSpacing = 5; protected float xRand = 0f; - public BurstTurret(String name) { + public BurstTurret(String name){ super(name); } @@ -21,7 +21,7 @@ public class BurstTurret extends ItemTurret { entity.heat = 1f; - for (int i = 0; i < shots; i++) { + for(int i = 0; i < shots; i++){ Timers.run(burstSpacing * i, () -> { if(!(tile.entity instanceof TurretEntity) || !hasAmmo(tile)) return; diff --git a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/CooledTurret.java b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/CooledTurret.java index d7f0e8dd89..62c832f6ac 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/CooledTurret.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/CooledTurret.java @@ -12,14 +12,18 @@ import io.anuke.ucore.util.Mathf; import static io.anuke.mindustry.Vars.tilesize; -public class CooledTurret extends Turret { - /**How much reload is lowered by for each unit of liquid of heat capacity 1.*/ +public class CooledTurret extends Turret{ + /** + * How much reload is lowered by for each unit of liquid of heat capacity 1. + */ protected float coolantMultiplier = 1f; - /**Max coolant used per tick.*/ + /** + * Max coolant used per tick. + */ protected float maxUsed = 1f; protected Effect coolEffect = BlockFx.fuelburn; - public CooledTurret(String name) { + public CooledTurret(String name){ super(name); hasLiquids = true; liquidCapacity = 20f; @@ -28,7 +32,7 @@ public class CooledTurret extends Turret { } @Override - protected void updateShooting(Tile tile) { + protected void updateShooting(Tile tile){ super.updateShooting(tile); TurretEntity entity = tile.entity(); @@ -39,7 +43,7 @@ public class CooledTurret extends Turret { entity.liquids.remove(liquid, used); if(Mathf.chance(0.04 * used)){ - Effects.effect(coolEffect, tile.drawx() + Mathf.range(size * tilesize/2f), tile.drawy() + Mathf.range(size * tilesize/2f)); + Effects.effect(coolEffect, tile.drawx() + Mathf.range(size * tilesize / 2f), tile.drawy() + Mathf.range(size * tilesize / 2f)); } //don't use oil as coolant, thanks @@ -49,7 +53,7 @@ public class CooledTurret extends Turret { } @Override - public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount) { + public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){ return super.acceptLiquid(tile, source, liquid, amount) && liquid.temperature <= 0.5f; } } diff --git a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/DoubleTurret.java b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/DoubleTurret.java index 1a46cb7be9..1601b15a4c 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/DoubleTurret.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/DoubleTurret.java @@ -6,10 +6,10 @@ import io.anuke.ucore.util.Mathf; import static io.anuke.mindustry.Vars.tilesize; -public class DoubleTurret extends ItemTurret { +public class DoubleTurret extends ItemTurret{ protected float shotWidth = 2f; - public DoubleTurret(String name) { + public DoubleTurret(String name){ super(name); shots = 2; } @@ -17,7 +17,7 @@ public class DoubleTurret extends ItemTurret { @Override protected void shoot(Tile tile, AmmoType ammo){ TurretEntity entity = tile.entity(); - entity.shots ++; + entity.shots++; int i = Mathf.signs[entity.shots % 2]; diff --git a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/ItemTurret.java b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/ItemTurret.java index d1b1fde9c8..b982ff5a0b 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/ItemTurret.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/ItemTurret.java @@ -11,18 +11,18 @@ import io.anuke.mindustry.world.meta.BlockBar; import io.anuke.mindustry.world.meta.BlockStat; import io.anuke.mindustry.world.meta.values.ItemFilterValue; -public class ItemTurret extends CooledTurret { +public class ItemTurret extends CooledTurret{ protected int maxAmmo = 50; protected AmmoType[] ammoTypes; protected ObjectMap ammoMap = new ObjectMap<>(); - public ItemTurret(String name) { + public ItemTurret(String name){ super(name); hasItems = true; } @Override - public void setStats() { + public void setStats(){ super.setStats(); stats.remove(BlockStat.itemCapacity); @@ -31,31 +31,31 @@ public class ItemTurret extends CooledTurret { } @Override - public int acceptStack(Item item, int amount, Tile tile, Unit source) { + public int acceptStack(Item item, int amount, Tile tile, Unit source){ TurretEntity entity = tile.entity(); AmmoType type = ammoMap.get(item); if(type == null) return 0; - return Math.min((int)((maxAmmo - entity.totalAmmo) / ammoMap.get(item).quantityMultiplier), amount); + return Math.min((int) ((maxAmmo - entity.totalAmmo) / ammoMap.get(item).quantityMultiplier), amount); } - + @Override public void handleStack(Item item, int amount, Tile tile, Unit source){ - for (int i = 0; i < amount; i++) { + for(int i = 0; i < amount; i++){ handleItem(item, tile, null); } } //currently can't remove items from turrets. @Override - public int removeStack(Tile tile, Item item, int amount) { + public int removeStack(Tile tile, Item item, int amount){ return 0; } @Override - public void handleItem(Item item, Tile tile, Tile source) { + public void handleItem(Item item, Tile tile, Tile source){ TurretEntity entity = tile.entity(); AmmoType type = ammoMap.get(item); @@ -63,19 +63,19 @@ public class ItemTurret extends CooledTurret { entity.items.add(item, 1); //find ammo entry by type - for(int i = 0; i < entity.ammo.size; i ++){ + for(int i = 0; i < entity.ammo.size; i++){ AmmoEntry entry = entity.ammo.get(i); //if found, put it to the right if(entry.type == type){ entry.amount += type.quantityMultiplier; - entity.ammo.swap(i, entity.ammo.size-1); + entity.ammo.swap(i, entity.ammo.size - 1); return; } } //must not be found - AmmoEntry entry = new AmmoEntry(type, (int)type.quantityMultiplier); + AmmoEntry entry = new AmmoEntry(type, (int) type.quantityMultiplier); entity.ammo.add(entry); } @@ -89,19 +89,19 @@ public class ItemTurret extends CooledTurret { @Override public void setBars(){ super.setBars(); - bars.replace(new BlockBar(BarType.inventory, true, tile -> (float)tile.entity().totalAmmo / maxAmmo)); + bars.replace(new BlockBar(BarType.inventory, true, tile -> (float) tile.entity().totalAmmo / maxAmmo)); } @Override public void init(){ super.init(); - if(ammoTypes != null) { - for (AmmoType type : ammoTypes) { + if(ammoTypes != null){ + for(AmmoType type : ammoTypes){ if(type.item == null) continue; - if (ammoMap.containsKey(type.item)) { + if(ammoMap.containsKey(type.item)){ throw new RuntimeException("Turret \"" + name + "\" has two conflicting ammo entries on item type " + type.item + "!"); - } else { + }else{ ammoMap.put(type.item, type); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/LaserTurret.java b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/LaserTurret.java index 1b69866132..63115407cf 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/LaserTurret.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/LaserTurret.java @@ -10,7 +10,7 @@ import io.anuke.ucore.util.Mathf; import static io.anuke.mindustry.Vars.tilesize; -public class LaserTurret extends PowerTurret { +public class LaserTurret extends PowerTurret{ protected float chargeTime = 30f; protected int chargeEffects = 5; @@ -18,7 +18,7 @@ public class LaserTurret extends PowerTurret { protected Effect chargeEffect = Fx.none; protected Effect chargeBeginEffect = Fx.none; - public LaserTurret(String name) { + public LaserTurret(String name){ super(name); } @@ -31,7 +31,7 @@ public class LaserTurret extends PowerTurret { tr.trns(entity.rotation, size * tilesize / 2); Effects.effect(chargeBeginEffect, tile.drawx() + tr.x, tile.drawy() + tr.y, entity.rotation); - for(int i = 0; i < chargeEffects; i ++){ + for(int i = 0; i < chargeEffects; i++){ Timers.run(Mathf.random(chargeMaxDelay), () -> { if(!isTurret(tile)) return; tr.trns(entity.rotation, size * tilesize / 2); diff --git a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/LiquidTurret.java b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/LiquidTurret.java index e4ac82acfb..e053be47e1 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/LiquidTurret.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/LiquidTurret.java @@ -11,24 +11,24 @@ import io.anuke.mindustry.world.meta.BlockStat; import io.anuke.mindustry.world.meta.values.LiquidFilterValue; import io.anuke.ucore.core.Effects; -public abstract class LiquidTurret extends Turret { +public abstract class LiquidTurret extends Turret{ protected AmmoType[] ammoTypes; protected ObjectMap liquidAmmoMap = new ObjectMap<>(); - public LiquidTurret(String name) { + public LiquidTurret(String name){ super(name); hasLiquids = true; } @Override - public void setStats() { + public void setStats(){ super.setStats(); stats.add(BlockStat.inputLiquid, new LiquidFilterValue(item -> liquidAmmoMap.containsKey(item))); } @Override - public void setBars() { + public void setBars(){ super.setBars(); bars.remove(BarType.inventory); bars.replace(new BlockBar(BarType.liquid, true, tile -> tile.entity.liquids.total() / liquidCapacity)); @@ -43,7 +43,7 @@ public abstract class LiquidTurret extends Turret { Effects.effect(shootEffect, type.liquid.color, tile.drawx() + tr.x, tile.drawy() + tr.y, entity.rotation); Effects.effect(smokeEffect, type.liquid.color, tile.drawx() + tr.x, tile.drawy() + tr.y, entity.rotation); - if (shootShake > 0) { + if(shootShake > 0){ Effects.shake(shootShake, shootShake, tile.entity); } @@ -73,17 +73,17 @@ public abstract class LiquidTurret extends Turret { public void init(){ super.init(); - for (AmmoType type : ammoTypes) { - if (liquidAmmoMap.containsKey(type.liquid)) { + for(AmmoType type : ammoTypes){ + if(liquidAmmoMap.containsKey(type.liquid)){ throw new RuntimeException("Turret \"" + name + "\" has two conflicting ammo entries on liquid type " + type.liquid + "!"); - } else { + }else{ liquidAmmoMap.put(type.liquid, type); } } } @Override - public boolean acceptItem(Item item, Tile tile, Tile source) { + public boolean acceptItem(Item item, Tile tile, Tile source){ return false; } diff --git a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/PowerTurret.java b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/PowerTurret.java index da4580cd2b..0c1b84698f 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/PowerTurret.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/PowerTurret.java @@ -5,35 +5,35 @@ import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.meta.BlockStat; import io.anuke.mindustry.world.meta.StatUnit; -public abstract class PowerTurret extends CooledTurret { - protected float powerUsed = 0.5f; - protected AmmoType shootType; +public abstract class PowerTurret extends CooledTurret{ + protected float powerUsed = 0.5f; + protected AmmoType shootType; - public PowerTurret(String name) { - super(name); - hasPower = true; - } - - @Override - public void setStats(){ - super.setStats(); + public PowerTurret(String name){ + super(name); + hasPower = true; + } - stats.add(BlockStat.powerShot, powerUsed, StatUnit.powerUnits); - } - - @Override - public boolean hasAmmo(Tile tile){ - return tile.entity.power.amount >= powerUsed; - } + @Override + public void setStats(){ + super.setStats(); - @Override - public AmmoType useAmmo(Tile tile){ - tile.entity.power.amount -= powerUsed; - return shootType; - } + stats.add(BlockStat.powerShot, powerUsed, StatUnit.powerUnits); + } - @Override - public AmmoType peekAmmo(Tile tile) { - return shootType; - } + @Override + public boolean hasAmmo(Tile tile){ + return tile.entity.power.amount >= powerUsed; + } + + @Override + public AmmoType useAmmo(Tile tile){ + tile.entity.power.amount -= powerUsed; + return shootType; + } + + @Override + public AmmoType peekAmmo(Tile tile){ + return shootType; + } } diff --git a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/Turret.java b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/Turret.java index abebf95b80..a6171e6cb5 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/Turret.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/Turret.java @@ -39,298 +39,304 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.tilesize; public abstract class Turret extends Block{ - protected static final int targetInterval = 15; - - protected final int timerTarget = timers++; + protected static final int targetInterval = 15; - protected Color heatColor = Palette.turretHeat; - protected Effect shootEffect = Fx.none; - protected Effect smokeEffect = Fx.none; - protected Effect ammoUseEffect = Fx.none; + protected final int timerTarget = timers++; + + protected Color heatColor = Palette.turretHeat; + protected Effect shootEffect = Fx.none; + protected Effect smokeEffect = Fx.none; + protected Effect ammoUseEffect = Fx.none; protected int ammoPerShot = 1; protected float ammoEjectBack = 1f; - protected float range = 50f; - protected float reload = 10f; - protected float inaccuracy = 0f; - protected int shots = 1; - protected float recoil = 1f; - protected float restitution = 0.02f; - protected float cooldown = 0.02f; - protected float rotatespeed = 5f; //in degrees per tick - protected float shootCone = 8f; - protected float shootShake = 0f; - protected boolean targetAir = true; + protected float range = 50f; + protected float reload = 10f; + protected float inaccuracy = 0f; + protected int shots = 1; + protected float recoil = 1f; + protected float restitution = 0.02f; + protected float cooldown = 0.02f; + protected float rotatespeed = 5f; //in degrees per tick + protected float shootCone = 8f; + protected float shootShake = 0f; + protected boolean targetAir = true; - protected Translator tr = new Translator(); - protected Translator tr2 = new Translator(); + protected Translator tr = new Translator(); + protected Translator tr2 = new Translator(); - protected TextureRegion baseRegion; - protected TextureRegion heatRegion; - protected TextureRegion baseTopRegion; + protected TextureRegion baseRegion; + protected TextureRegion heatRegion; + protected TextureRegion baseTopRegion; protected BiConsumer drawer = (tile, entity) -> Draw.rect(region, tile.drawx() + tr2.x, tile.drawy() + tr2.y, entity.rotation - 90); - protected BiConsumer heatDrawer = (tile, entity) ->{ - if(entity.heat <= 0.00001f) return; - Graphics.setAdditiveBlending(); - Draw.color(heatColor); - Draw.alpha(entity.heat); - Draw.rect(heatRegion, tile.drawx() + tr2.x, tile.drawy() + tr2.y, entity.rotation - 90); - Graphics.setNormalBlending(); - }; + protected BiConsumer heatDrawer = (tile, entity) -> { + if(entity.heat <= 0.00001f) return; + Graphics.setAdditiveBlending(); + Draw.color(heatColor); + Draw.alpha(entity.heat); + Draw.rect(heatRegion, tile.drawx() + tr2.x, tile.drawy() + tr2.y, entity.rotation - 90); + Graphics.setNormalBlending(); + }; - public Turret(String name) { - super(name); - update = true; - solid = true; - layer = Layer.turret; - group = BlockGroup.turrets; - } + public Turret(String name){ + super(name); + update = true; + solid = true; + layer = Layer.turret; + group = BlockGroup.turrets; + } - @Override - public void init() { - super.init(); - viewRange = range; - } + @Override + public void init(){ + super.init(); + viewRange = range; + } - @Override - public void load() { - super.load(); + @Override + public void load(){ + super.load(); - baseRegion = Draw.region("block-" + size); - baseTopRegion = Draw.region("block-" +size + "-top"); - heatRegion = Draw.region(name + "-heat"); - } + baseRegion = Draw.region("block-" + size); + baseTopRegion = Draw.region("block-" + size + "-top"); + heatRegion = Draw.region(name + "-heat"); + } - @Override - public void setStats(){ - super.setStats(); + @Override + public void setStats(){ + super.setStats(); /* if(ammo != null) stats.add("ammo", ammo); if(ammo != null) stats.add("ammocapacity", maxAmmo); if(ammo != null) stats.add("ammoitem", ammoMultiplier);*/ - stats.add(BlockStat.shootRange, range, StatUnit.blocks); - stats.add(BlockStat.inaccuracy, (int)inaccuracy, StatUnit.degrees); - stats.add(BlockStat.reload, 60f/reload, StatUnit.seconds); - stats.add(BlockStat.shots, shots, StatUnit.none); - stats.add(BlockStat.targetsAir, targetAir); - } - - @Override - public void draw(Tile tile){ - Draw.rect(baseRegion, tile.drawx(), tile.drawy()); - Draw.color(tile.getTeam().color, Color.WHITE, 0.45f); - Draw.rect(baseTopRegion, tile.drawx(), tile.drawy()); - Draw.color(); - } - - @Override - public void drawLayer(Tile tile){ - TurretEntity entity = tile.entity(); + stats.add(BlockStat.shootRange, range, StatUnit.blocks); + stats.add(BlockStat.inaccuracy, (int) inaccuracy, StatUnit.degrees); + stats.add(BlockStat.reload, 60f / reload, StatUnit.seconds); + stats.add(BlockStat.shots, shots, StatUnit.none); + stats.add(BlockStat.targetsAir, targetAir); + } - tr2.trns(entity.rotation, -entity.recoil); + @Override + public void draw(Tile tile){ + Draw.rect(baseRegion, tile.drawx(), tile.drawy()); + Draw.color(tile.getTeam().color, Color.WHITE, 0.45f); + Draw.rect(baseTopRegion, tile.drawx(), tile.drawy()); + Draw.color(); + } - drawer.accept(tile, entity); + @Override + public void drawLayer(Tile tile){ + TurretEntity entity = tile.entity(); - if(heatRegion != null){ - heatDrawer.accept(tile, entity); - } + tr2.trns(entity.rotation, -entity.recoil); - Draw.color(); - } + drawer.accept(tile, entity); - @Override + if(heatRegion != null){ + heatDrawer.accept(tile, entity); + } + + Draw.color(); + } + + @Override public TextureRegion[] getBlockIcon(){ - if(blockIcon == null){ - blockIcon = new TextureRegion[]{Draw.region("block-icon-" + name)}; + if(blockIcon == null){ + blockIcon = new TextureRegion[]{Draw.region("block-icon-" + name)}; } return blockIcon; } @Override - public TextureRegion[] getCompactIcon(){ - if(compactIcon == null) { - compactIcon = new TextureRegion[]{iconRegion(Draw.region("block-icon-" + name))}; - } - return compactIcon; - } + public TextureRegion[] getCompactIcon(){ + if(compactIcon == null){ + compactIcon = new TextureRegion[]{iconRegion(Draw.region("block-icon-" + name))}; + } + return compactIcon; + } - @Override - public void drawSelect(Tile tile){ - Draw.color(tile.getTeam().color); - Lines.dashCircle(tile.drawx(), tile.drawy(), range); - Draw.reset(); - } - - @Override - public void drawPlace(int x, int y, int rotation, boolean valid){ - Draw.color(Palette.placing); - Lines.stroke(1f); - Lines.dashCircle(x * tilesize + offset(), y * tilesize + offset(), range); - } + @Override + public void drawSelect(Tile tile){ + Draw.color(tile.getTeam().color); + Lines.dashCircle(tile.drawx(), tile.drawy(), range); + Draw.reset(); + } - @Override - public void update(Tile tile){ - TurretEntity entity = tile.entity(); - - if(entity.target != null && entity.target.isDead()) - entity.target = null; + @Override + public void drawPlace(int x, int y, int rotation, boolean valid){ + Draw.color(Palette.placing); + Lines.stroke(1f); + Lines.dashCircle(x * tilesize + offset(), y * tilesize + offset(), range); + } - entity.recoil = Mathf.lerpDelta(entity.recoil, 0f, restitution); - entity.heat = Mathf.lerpDelta(entity.heat, 0f, cooldown); - - if(hasAmmo(tile)){ - - if(entity.timer.get(timerTarget, targetInterval)){ - entity.target = Units.getClosestEnemy(tile.getTeam(), - tile.drawx(), tile.drawy(), range, e -> !e.isDead() && (!e.isFlying() || targetAir)); - } - - if(entity.target != null){ - AmmoType type = peekAmmo(tile); - float speed = type.bullet.speed; - if(speed < 0.1f) speed = 9999999f; + @Override + public void update(Tile tile){ + TurretEntity entity = tile.entity(); - Vector2 result = Predict.intercept(entity, entity.target, speed); - if(result.isZero()){ - result.set(entity.target.getX(), entity.target.getY()); - } - - float targetRot = result.sub(tile.drawx(), tile.drawy()).angle(); - - if(Float.isNaN(entity.rotation)){ - entity.rotation = 0; - } + if(entity.target != null && entity.target.isDead()) + entity.target = null; - entity.rotation = Angles.moveToward(entity.rotation, targetRot, rotatespeed * Timers.delta()); + entity.recoil = Mathf.lerpDelta(entity.recoil, 0f, restitution); + entity.heat = Mathf.lerpDelta(entity.heat, 0f, cooldown); - if(Angles.angleDist(entity.rotation, targetRot) < shootCone){ - updateShooting(tile); - } - } - } - } + if(hasAmmo(tile)){ - /**Consume ammo and return a type.*/ - public AmmoType useAmmo(Tile tile){ + if(entity.timer.get(timerTarget, targetInterval)){ + entity.target = Units.getClosestEnemy(tile.getTeam(), + tile.drawx(), tile.drawy(), range, e -> !e.isDead() && (!e.isFlying() || targetAir)); + } + + if(entity.target != null){ + AmmoType type = peekAmmo(tile); + float speed = type.bullet.speed; + if(speed < 0.1f) speed = 9999999f; + + Vector2 result = Predict.intercept(entity, entity.target, speed); + if(result.isZero()){ + result.set(entity.target.getX(), entity.target.getY()); + } + + float targetRot = result.sub(tile.drawx(), tile.drawy()).angle(); + + if(Float.isNaN(entity.rotation)){ + entity.rotation = 0; + } + + entity.rotation = Angles.moveToward(entity.rotation, targetRot, rotatespeed * Timers.delta()); + + if(Angles.angleDist(entity.rotation, targetRot) < shootCone){ + updateShooting(tile); + } + } + } + } + + /** + * Consume ammo and return a type. + */ + public AmmoType useAmmo(Tile tile){ TurretEntity entity = tile.entity(); AmmoEntry entry = entity.ammo.peek(); entry.amount -= ammoPerShot; if(entry.amount == 0) entity.ammo.pop(); entity.totalAmmo -= ammoPerShot; - Timers.run(reload/2f, () -> ejectEffects(tile)); + Timers.run(reload / 2f, () -> ejectEffects(tile)); return entry.type; } - /**Get the ammo type that will be returned if useAmmo is called.*/ + /** + * Get the ammo type that will be returned if useAmmo is called. + */ public AmmoType peekAmmo(Tile tile){ TurretEntity entity = tile.entity(); return entity.ammo.peek().type; } - /**Returns whether the turret has ammo.*/ - public boolean hasAmmo(Tile tile){ - TurretEntity entity = tile.entity(); - return entity.ammo.size > 0 && entity.ammo.peek().amount >= ammoPerShot; - } - - protected void updateShooting(Tile tile){ - TurretEntity entity = tile.entity(); + /** + * Returns whether the turret has ammo. + */ + public boolean hasAmmo(Tile tile){ + TurretEntity entity = tile.entity(); + return entity.ammo.size > 0 && entity.ammo.peek().amount >= ammoPerShot; + } - if(entity.reload >= reload) { - AmmoType type = peekAmmo(tile); + protected void updateShooting(Tile tile){ + TurretEntity entity = tile.entity(); + + if(entity.reload >= reload){ + AmmoType type = peekAmmo(tile); shoot(tile, type); entity.reload = 0f; }else{ - entity.reload += Timers.delta() * peekAmmo(tile).reloadMultiplier; - } - } + entity.reload += Timers.delta() * peekAmmo(tile).reloadMultiplier; + } + } - protected void shoot(Tile tile, AmmoType ammo){ - TurretEntity entity = tile.entity(); + protected void shoot(Tile tile, AmmoType ammo){ + TurretEntity entity = tile.entity(); - entity.recoil = recoil; - entity.heat = 1f; + entity.recoil = recoil; + entity.heat = 1f; - AmmoType type = peekAmmo(tile); - useAmmo(tile); + AmmoType type = peekAmmo(tile); + useAmmo(tile); - tr.trns(entity.rotation, size * tilesize / 2); + tr.trns(entity.rotation, size * tilesize / 2); - bullet(tile, ammo.bullet, entity.rotation + Mathf.range(inaccuracy + type.inaccuracy)); + bullet(tile, ammo.bullet, entity.rotation + Mathf.range(inaccuracy + type.inaccuracy)); - effects(tile); - } - - protected void bullet(Tile tile, BulletType type, float angle){ - Bullet.create(type, tile.entity, tile.getTeam(), tile.drawx() + tr.x, tile.drawy() + tr.y, angle); - } + effects(tile); + } - protected void effects(Tile tile){ - Effect shootEffect = this.shootEffect == Fx.none ? peekAmmo(tile).shootEffect : this.shootEffect; - Effect smokeEffect = this.smokeEffect == Fx.none ? peekAmmo(tile).smokeEffect : this.smokeEffect; + protected void bullet(Tile tile, BulletType type, float angle){ + Bullet.create(type, tile.entity, tile.getTeam(), tile.drawx() + tr.x, tile.drawy() + tr.y, angle); + } - TurretEntity entity = tile.entity(); + protected void effects(Tile tile){ + Effect shootEffect = this.shootEffect == Fx.none ? peekAmmo(tile).shootEffect : this.shootEffect; + Effect smokeEffect = this.smokeEffect == Fx.none ? peekAmmo(tile).smokeEffect : this.smokeEffect; - Effects.effect(shootEffect, tile.drawx() + tr.x, tile.drawy() + tr.y, entity.rotation); - Effects.effect(smokeEffect, tile.drawx() + tr.x, tile.drawy() + tr.y, entity.rotation); + TurretEntity entity = tile.entity(); - if (shootShake > 0) { - Effects.shake(shootShake, shootShake, tile.entity); - } + Effects.effect(shootEffect, tile.drawx() + tr.x, tile.drawy() + tr.y, entity.rotation); + Effects.effect(smokeEffect, tile.drawx() + tr.x, tile.drawy() + tr.y, entity.rotation); - entity.recoil = recoil; - } + if(shootShake > 0){ + Effects.shake(shootShake, shootShake, tile.entity); + } - protected void ejectEffects(Tile tile){ - if(!isTurret(tile)) return; - TurretEntity entity = tile.entity(); + entity.recoil = recoil; + } - Effects.effect(ammoUseEffect, tile.drawx() - Angles.trnsx(entity.rotation, ammoEjectBack), - tile.drawy() - Angles.trnsy(entity.rotation, ammoEjectBack), entity.rotation); - } + protected void ejectEffects(Tile tile){ + if(!isTurret(tile)) return; + TurretEntity entity = tile.entity(); - protected boolean isTurret(Tile tile){ - return (tile.entity instanceof TurretEntity); - } + Effects.effect(ammoUseEffect, tile.drawx() - Angles.trnsx(entity.rotation, ammoEjectBack), + tile.drawy() - Angles.trnsy(entity.rotation, ammoEjectBack), entity.rotation); + } - @Override - public TileEntity getEntity(){ - return new TurretEntity(); - } - - public static class TurretEntity extends TileEntity{ - public TileEntity blockTarget; - public Array ammo = new ThreadArray<>(); - public int totalAmmo; - public float reload; - public float rotation = 90; - public float recoil = 0f; - public float heat; - public int shots; - public Unit target; - - @Override - public void write(DataOutputStream stream) throws IOException{ - stream.writeByte(ammo.size); - for(AmmoEntry entry : ammo){ + protected boolean isTurret(Tile tile){ + return (tile.entity instanceof TurretEntity); + } + + @Override + public TileEntity getEntity(){ + return new TurretEntity(); + } + + public static class TurretEntity extends TileEntity{ + public TileEntity blockTarget; + public Array ammo = new ThreadArray<>(); + public int totalAmmo; + public float reload; + public float rotation = 90; + public float recoil = 0f; + public float heat; + public int shots; + public Unit target; + + @Override + public void write(DataOutputStream stream) throws IOException{ + stream.writeByte(ammo.size); + for(AmmoEntry entry : ammo){ stream.writeByte(entry.type.id); stream.writeShort(entry.amount); } - } - - @Override - public void read(DataInputStream stream) throws IOException{ - byte amount = stream.readByte(); - for(int i = 0; i < amount; i ++){ - AmmoType type = AmmoType.getByID(stream.readByte()); - short ta = stream.readShort(); - ammo.add(new AmmoEntry(type, ta)); - totalAmmo += ta; + } + + @Override + public void read(DataInputStream stream) throws IOException{ + byte amount = stream.readByte(); + for(int i = 0; i < amount; i++){ + AmmoType type = AmmoType.getByID(stream.readByte()); + short ta = stream.readShort(); + ammo.add(new AmmoEntry(type, ta)); + totalAmmo += ta; } - } - } + } + } } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/BufferedItemBridge.java b/core/src/io/anuke/mindustry/world/blocks/distribution/BufferedItemBridge.java index c16ae2f2bf..bd5a6bfb8e 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/BufferedItemBridge.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/BufferedItemBridge.java @@ -10,13 +10,13 @@ import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -public class BufferedItemBridge extends ExtendingItemBridge { - protected int timerAccept = timers ++; +public class BufferedItemBridge extends ExtendingItemBridge{ + protected int timerAccept = timers++; protected float speed = 40f; protected int bufferCapacity = 50; - public BufferedItemBridge(String name) { + public BufferedItemBridge(String name){ super(name); hasPower = false; hasItems = true; @@ -41,7 +41,7 @@ public class BufferedItemBridge extends ExtendingItemBridge { } @Override - public TileEntity getEntity() { + public TileEntity getEntity(){ return new BufferedItemBridgeEntity(); } @@ -49,12 +49,12 @@ public class BufferedItemBridge extends ExtendingItemBridge { ItemBuffer buffer = new ItemBuffer(bufferCapacity, speed); @Override - public void write(DataOutputStream stream) throws IOException { + public void write(DataOutputStream stream) throws IOException{ super.write(stream); } @Override - public void read(DataInputStream stream) throws IOException { + public void read(DataInputStream stream) throws IOException{ super.read(stream); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java index 99b8fbfafc..08f908b2ea 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java @@ -13,24 +13,24 @@ import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -public class Conduit extends LiquidBlock { +public class Conduit extends LiquidBlock{ protected final int timerFlow = timers++; protected TextureRegion[] topRegions = new TextureRegion[7]; protected TextureRegion[] botRegions = new TextureRegion[7]; - public Conduit(String name) { + public Conduit(String name){ super(name); rotate = true; solid = false; } @Override - public void load() { + public void load(){ super.load(); liquidRegion = Draw.region("conduit-liquid"); - for (int i = 0; i < topRegions.length; i++) { + for(int i = 0; i < topRegions.length; i++){ topRegions[i] = Draw.region(name + "-top-" + i); botRegions[i] = Draw.region("conduit-bottom-" + i); } @@ -40,23 +40,23 @@ public class Conduit extends LiquidBlock { ConduitEntity entity = tile.entity(); entity.blendbits = 0; - if(blends(tile, 2) && blends(tile, 1) && blends(tile, 3)) { + if(blends(tile, 2) && blends(tile, 1) && blends(tile, 3)){ entity.blendbits = 3; - }else if(blends(tile, 1) && blends(tile, 2)) { + }else if(blends(tile, 1) && blends(tile, 2)){ entity.blendbits = 2; - }else if(blends(tile, 3) && blends(tile, 2)) { + }else if(blends(tile, 3) && blends(tile, 2)){ entity.blendbits = 4; }else if(blends(tile, 0)){ - if(blends(tile, 1) && blends(tile, 3)) { + if(blends(tile, 1) && blends(tile, 3)){ entity.blendbits = 6; - }else if(blends(tile, 1)) { + }else if(blends(tile, 1)){ entity.blendbits = 5; - }else if(blends(tile, 3)) { + }else if(blends(tile, 3)){ entity.blendbits = 1; } - }else if(blends(tile, 1)) { + }else if(blends(tile, 1)){ entity.blendbits = 5; - }else if(blends(tile, 3)) { + }else if(blends(tile, 3)){ entity.blendbits = 1; } } @@ -90,7 +90,7 @@ public class Conduit extends LiquidBlock { @Override public void update(Tile tile){ ConduitEntity entity = tile.entity(); - entity.smoothLiquid = Mathf.lerpDelta(entity.smoothLiquid, entity.liquids.total()/liquidCapacity, 0.05f); + entity.smoothLiquid = Mathf.lerpDelta(entity.smoothLiquid, entity.liquids.total() / liquidCapacity, 0.05f); if(tile.entity.liquids.total() > 0.001f && tile.entity.timer.get(timerFlow, 1)){ tryMoveLiquid(tile, tile.getNearby(tile.getRotation()), true, tile.entity.liquids.current()); @@ -109,27 +109,27 @@ public class Conduit extends LiquidBlock { } @Override - public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount) { + public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){ tile.entity.wakeUp(); return super.acceptLiquid(tile, source, liquid, amount) && ((2 + source.relativeTo(tile.x, tile.y)) % 4 != tile.getRotation()); } @Override - public TileEntity getEntity() { + public TileEntity getEntity(){ return new ConduitEntity(); } - public static class ConduitEntity extends TileEntity { + public static class ConduitEntity extends TileEntity{ public float smoothLiquid; public byte blendbits; @Override - public void write(DataOutputStream stream) throws IOException { + public void write(DataOutputStream stream) throws IOException{ stream.writeFloat(smoothLiquid); } @Override - public void read(DataInputStream stream) throws IOException { + public void read(DataInputStream stream) throws IOException{ smoothLiquid = stream.readFloat(); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/Conveyor.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Conveyor.java index d47d269d16..d62e936095 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Conveyor.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Conveyor.java @@ -25,410 +25,410 @@ import static io.anuke.mindustry.Vars.itemSize; import static io.anuke.mindustry.Vars.tilesize; public class Conveyor extends Block{ - private static ItemPos drawpos = new ItemPos(); - private static ItemPos pos1 = new ItemPos(); - private static ItemPos pos2 = new ItemPos(); - - private static final float itemSpace = 0.135f * 2.2f; - private static final float offsetScl = 128f*3f; - private static final float minmove = 1f / (Short.MAX_VALUE - 2); - - private final Translator tr1 = new Translator(); - private final Translator tr2 = new Translator(); - private final TextureRegion region1 = new TextureRegion(); - private final TextureRegion region2 = new TextureRegion(); - - protected float speed = 0f; - protected float carryCapacity = 8f; - - protected Conveyor(String name) { - super(name); - rotate = true; - update = true; - layer = Layer.overlay; - group = BlockGroup.transportation; - hasItems = true; - autoSleep = true; - itemCapacity = Math.round(tilesize/ itemSpace); - } - - @Override - public void setBars() {} - - @Override - public void setStats(){ - super.setStats(); - stats.add(BlockStat.itemSpeed, speed * 60, StatUnit.pixelsSecond); - } - - @Override - public void draw(Tile tile){ - ConveyorEntity entity = tile.entity(); - byte rotation = tile.getRotation(); - - GridPoint2 point = Geometry.d4[rotation]; - - int offset = entity.clogHeat <= 0.5f ? (int)((Timers.time()/4f)%8) : 0; - TextureRegion region = Draw.region(name); - - region1.setRegion(region, 0, 0, region.getRegionWidth() - offset, region.getRegionHeight()); - region2.setRegion(region, region.getRegionWidth() - offset, 0, offset, region.getRegionHeight()); - - float x = tile.drawx(), y = tile.drawy(); - - if(offset % 2 == 1){ - if(point.x < 0) x += 0.75f; - if(point.y < 0) - y += 0.5f; - else if(point.y > 0) - y -= 0.5f; - } - - Draw.rect(region1, - x + (point.x * (tilesize/2f - region1.getRegionWidth()/2f)), - y + (point.y * (tilesize/2f - region1.getRegionWidth()/2f)), rotation * 90); - Draw.rect(region2, - x - (point.x * (tilesize/2f - region2.getRegionWidth()/2f)), - y - (point.y * (tilesize/2f - region2.getRegionWidth()/2f)), rotation * 90); - } - - @Override - public boolean isLayer(Tile tile){ - return tile.entity().convey.size > 0; - } - - @Override - public void drawLayer(Tile tile){ - ConveyorEntity entity = tile.entity(); - - byte rotation = tile.getRotation(); - - try { - - for (int i = 0; i < entity.convey.size; i++) { - ItemPos pos = drawpos.set(entity.convey.get(i), ItemPos.drawShorts); - - if (pos.item == null) continue; - - tr1.trns(rotation * 90, tilesize, 0); - tr2.trns(rotation * 90, -tilesize / 2, pos.x * tilesize / 2); - - Draw.rect(pos.item.region, - (int)(tile.x * tilesize + tr1.x * pos.y + tr2.x), - (int)(tile.y * tilesize + tr1.y * pos.y + tr2.y), itemSize, itemSize); - } - - }catch (IndexOutOfBoundsException e){ - Log.err(e); - } - } - - @Override - public void unitOn(Tile tile, Unit unit) { - ConveyorEntity entity = tile.entity(); - - entity.wakeUp(); - - float speed = this.speed * tilesize / 2.3f; - float tx = Geometry.d4[tile.getRotation()].x, ty = Geometry.d4[tile.getRotation()].y; - - float min; - - if(Math.abs(tx) > Math.abs(ty)){ - float rx = tile.worldx() - tx/2f*tilesize; - min = Mathf.clamp((unit.x - rx) * tx / tilesize); - }else{ - float ry = tile.worldy() - ty/2f*tilesize; - min = Mathf.clamp((unit.y - ry) * ty / tilesize); - } - - entity.minCarry = Math.min(entity.minCarry, min); - entity.carrying += unit.getMass(); - - if(entity.convey.size * itemSpace < 0.9f){ - unit.getVelocity().add(tx * speed * Timers.delta(), ty * speed * Timers.delta()); - } - } - - @Override - public synchronized void update(Tile tile){ - - ConveyorEntity entity = tile.entity(); - entity.minitem = 1f; - - int minremove = Integer.MAX_VALUE; - float speed = Math.max(this.speed - (1f - (carryCapacity - entity.carrying) / carryCapacity), 0f); - float totalMoved = 0f; - - for (int i = entity.convey.size - 1; i >= 0; i--) { - long value = entity.convey.get(i); - ItemPos pos = pos1.set(value, ItemPos.updateShorts); - - //..this should never happen, but in case it does, remove it and stop here - if (pos.item == null) { - entity.convey.removeValue(value); - break; - } - - float nextpos = (i == entity.convey.size - 1 ? 100f : pos2.set(entity.convey.get(i + 1), ItemPos.updateShorts).y) - itemSpace; - if (entity.minCarry >= pos.y && entity.minCarry <= nextpos) { - nextpos = entity.minCarry; - } - float maxmove = Math.min(nextpos - pos.y, speed * Timers.delta()); - - if (maxmove > minmove) { - pos.y += maxmove; - pos.x = Mathf.lerpDelta(pos.x, 0, 0.06f); - totalMoved += maxmove; - } else { - pos.x = Mathf.lerpDelta(pos.x, pos.seed / offsetScl, 0.1f); - } - - pos.y = Mathf.clamp(pos.y); - - if (pos.y >= 0.9999f && offloadDir(tile, pos.item)) { - minremove = Math.min(i, minremove); - totalMoved = 1f; - tile.entity.items.remove(pos.item, 1); - } else { - value = pos.pack(); - - if (pos.y < entity.minitem) - entity.minitem = pos.y; - entity.convey.set(i, value); - } - } - - if(entity.minitem < itemSpace){ - entity.clogHeat = Mathf.lerpDelta(entity.clogHeat, 1f, 0.02f); - }else{ - entity.clogHeat = Mathf.lerpDelta(entity.clogHeat, 0f, 1f); - } - - entity.carrying = 0f; - entity.minCarry = 2f; - - if(totalMoved <= 0.0001f){ - entity.sleep(); - } - - if (minremove != Integer.MAX_VALUE) entity.convey.truncate(minremove); - } - - @Override - public boolean isAccessible(){ - return true; - } - - @Override - public synchronized int removeStack(Tile tile, Item item, int amount) { - ConveyorEntity entity = tile.entity(); - entity.wakeUp(); - int removed = 0; - - for(int j = 0; j < amount; j ++) { - for (int i = 0; i < entity.convey.size; i++) { - long val = entity.convey.get(i); - ItemPos pos = pos1.set(val, ItemPos.drawShorts); - if(pos.item == item){ - entity.convey.removeValue(val); - entity.items.remove(item, 1); - removed ++; - break; - } - } - } - return removed; - } - - @Override - public void getStackOffset(Item item, Tile tile, Translator trns) { - trns.trns(tile.getRotation()*90 + 180f, tilesize/2f); - } - - @Override - public synchronized int acceptStack(Item item, int amount, Tile tile, Unit source) { - ConveyorEntity entity = tile.entity(); - return entity.minitem > itemSpace ? 1 : 0; - } - - @Override - public synchronized void handleStack(Item item, int amount, Tile tile, Unit source) { - ConveyorEntity entity = tile.entity(); - - long result = ItemPos.packItem(item, 0f, 0f, (byte)Mathf.random(255)); - entity.convey.insert(0, result); - entity.items.add(item, 1); - entity.wakeUp(); - } - - @Override - public boolean acceptItem(Item item, Tile tile, Tile source){ - int direction = source == null ? 0 : Math.abs(source.relativeTo(tile.x, tile.y) - tile.getRotation()); - float minitem = tile.entity().minitem; - return (((direction == 0) && minitem > itemSpace) || - ((direction %2 == 1) && minitem > 0.52f)) && (source == null || !(source.block().rotate && (source.getRotation() + 2) % 4 == tile.getRotation())); - } - - @Override - public void handleItem(Item item, Tile tile, Tile source){ - byte rotation = tile.getRotation(); - - int ch = Math.abs(source.relativeTo(tile.x, tile.y) - rotation); - int ang = ((source.relativeTo(tile.x, tile.y) - rotation)); - - float pos = ch == 0 ? 0 : ch % 2 == 1 ? 0.5f : 1f; - float y = (ang == -1 || ang == 3) ? 1 : (ang == 1 || ang == -3) ? -1 : 0; - - ConveyorEntity entity = tile.entity(); - entity.wakeUp(); - long result = ItemPos.packItem(item, y*0.9f, pos, (byte)Mathf.random(255)); - boolean inserted = false; - - tile.entity.items.add(item, 1); - - for(int i = 0; i < entity.convey.size; i ++){ - if(compareItems(result, entity.convey.get(i)) < 0){ - entity.convey.insert(i, result); - inserted = true; - break; - } - } - - //this item must be greater than anything there... - if(!inserted){ - entity.convey.add(result); - } - } - - @Override - public Array getDebugInfo(Tile tile) { - ConveyorEntity entity = tile.entity(); - Array arr = super.getDebugInfo(tile); - arr.addAll(Array.with( - "mincarry", entity.minCarry, - "minitem", entity.minCarry, - "carrying", entity.carrying, - "clogHeat", entity.clogHeat, - "sleeping", entity.isSleeping() - )); - return arr; - } - - @Override - public TileEntity getEntity(){ - return new ConveyorEntity(); - } - - public static class ConveyorEntity extends TileEntity{ - - LongArray convey = new LongArray(); - float minitem = 1; - float carrying; - float minCarry = 2f; - - float clogHeat = 0f; - - @Override - public void write(DataOutputStream stream) throws IOException{ - stream.writeInt(convey.size); - - for(int i = 0; i < convey.size; i ++){ - stream.writeInt(ItemPos.toInt(convey.get(i))); - } - } - - @Override - public void read(DataInputStream stream) throws IOException{ - convey.clear(); - int amount = stream.readInt(); - convey.ensureCapacity(amount); - - for(int i = 0; i < amount; i ++){ - convey.add(ItemPos.toLong(stream.readInt())); - } - } - } - - private static int compareItems(Long a, Long b){ - pos1.set(a, ItemPos.packShorts); - pos2.set(b, ItemPos.packShorts); - return Float.compare(pos1.y, pos2.y); - } - - //Container class. Do not instantiate. - static class ItemPos{ - private static short[] writeShort = new short[4]; - private static byte[] writeByte = new byte[4]; - - private static short[] packShorts = new short[4]; - private static short[] drawShorts = new short[4]; - private static short[] updateShorts = new short[4]; - - Item item; - float x, y; - byte seed; - - private ItemPos(){} - - ItemPos set(long lvalue, short[] values){ - Bits.getShorts(lvalue, values); - - if(values[0] >= Item.all().size || values[0] < 0) - item = null; - else - item = Item.all().get(values[0]); - - x = values[1] / (float)Short.MAX_VALUE; - y = ((float)values[2]) / Short.MAX_VALUE + 1f; - seed = (byte)values[3]; - return this; - } - - long pack(){ - return packItem(item, x, y, seed); - } - - static long packItem(Item item, float x, float y, byte seed){ - short[] shorts = packShorts; - shorts[0] = (short)item.id; - shorts[1] = (short)(x*Short.MAX_VALUE); - shorts[2] = (short)((y - 1f)*Short.MAX_VALUE); - shorts[3] = seed; - return Bits.packLong(shorts); - } - - static int toInt(long value){ - short[] values = Bits.getShorts(value, writeShort); - - short itemid = values[0]; - float x = values[1] / (float)Short.MAX_VALUE; - float y = ((float)values[2]) / Short.MAX_VALUE + 1f; - byte seed = (byte)values[3]; - - byte[] bytes = writeByte; - bytes[0] = (byte)itemid; - bytes[1] = (byte)(x*127); - bytes[2] = (byte)(y*255-128); - bytes[3] = seed; - - return Bits.packInt(bytes); - } - - static long toLong(int value){ - byte[] values = Bits.getBytes(value, writeByte); - - byte itemid = values[0]; - float x = values[1] / 127f; - float y = ((int)values[2] + 128) / 255f; - byte seed = values[3]; - - short[] shorts = writeShort; - shorts[0] = (short)itemid; - shorts[1] = (short)(x*Short.MAX_VALUE); - shorts[2] = (short)((y - 1f)*Short.MAX_VALUE); - shorts[3] = seed; - return Bits.packLong(shorts); - } - } + private static final float itemSpace = 0.135f * 2.2f; + private static final float offsetScl = 128f * 3f; + private static final float minmove = 1f / (Short.MAX_VALUE - 2); + private static ItemPos drawpos = new ItemPos(); + private static ItemPos pos1 = new ItemPos(); + private static ItemPos pos2 = new ItemPos(); + private final Translator tr1 = new Translator(); + private final Translator tr2 = new Translator(); + private final TextureRegion region1 = new TextureRegion(); + private final TextureRegion region2 = new TextureRegion(); + + protected float speed = 0f; + protected float carryCapacity = 8f; + + protected Conveyor(String name){ + super(name); + rotate = true; + update = true; + layer = Layer.overlay; + group = BlockGroup.transportation; + hasItems = true; + autoSleep = true; + itemCapacity = Math.round(tilesize / itemSpace); + } + + private static int compareItems(Long a, Long b){ + pos1.set(a, ItemPos.packShorts); + pos2.set(b, ItemPos.packShorts); + return Float.compare(pos1.y, pos2.y); + } + + @Override + public void setBars(){ + } + + @Override + public void setStats(){ + super.setStats(); + stats.add(BlockStat.itemSpeed, speed * 60, StatUnit.pixelsSecond); + } + + @Override + public void draw(Tile tile){ + ConveyorEntity entity = tile.entity(); + byte rotation = tile.getRotation(); + + GridPoint2 point = Geometry.d4[rotation]; + + int offset = entity.clogHeat <= 0.5f ? (int) ((Timers.time() / 4f) % 8) : 0; + TextureRegion region = Draw.region(name); + + region1.setRegion(region, 0, 0, region.getRegionWidth() - offset, region.getRegionHeight()); + region2.setRegion(region, region.getRegionWidth() - offset, 0, offset, region.getRegionHeight()); + + float x = tile.drawx(), y = tile.drawy(); + + if(offset % 2 == 1){ + if(point.x < 0) x += 0.75f; + if(point.y < 0) + y += 0.5f; + else if(point.y > 0) + y -= 0.5f; + } + + Draw.rect(region1, + x + (point.x * (tilesize / 2f - region1.getRegionWidth() / 2f)), + y + (point.y * (tilesize / 2f - region1.getRegionWidth() / 2f)), rotation * 90); + Draw.rect(region2, + x - (point.x * (tilesize / 2f - region2.getRegionWidth() / 2f)), + y - (point.y * (tilesize / 2f - region2.getRegionWidth() / 2f)), rotation * 90); + } + + @Override + public boolean isLayer(Tile tile){ + return tile.entity().convey.size > 0; + } + + @Override + public void drawLayer(Tile tile){ + ConveyorEntity entity = tile.entity(); + + byte rotation = tile.getRotation(); + + try{ + + for(int i = 0; i < entity.convey.size; i++){ + ItemPos pos = drawpos.set(entity.convey.get(i), ItemPos.drawShorts); + + if(pos.item == null) continue; + + tr1.trns(rotation * 90, tilesize, 0); + tr2.trns(rotation * 90, -tilesize / 2, pos.x * tilesize / 2); + + Draw.rect(pos.item.region, + (int) (tile.x * tilesize + tr1.x * pos.y + tr2.x), + (int) (tile.y * tilesize + tr1.y * pos.y + tr2.y), itemSize, itemSize); + } + + }catch(IndexOutOfBoundsException e){ + Log.err(e); + } + } + + @Override + public void unitOn(Tile tile, Unit unit){ + ConveyorEntity entity = tile.entity(); + + entity.wakeUp(); + + float speed = this.speed * tilesize / 2.3f; + float tx = Geometry.d4[tile.getRotation()].x, ty = Geometry.d4[tile.getRotation()].y; + + float min; + + if(Math.abs(tx) > Math.abs(ty)){ + float rx = tile.worldx() - tx / 2f * tilesize; + min = Mathf.clamp((unit.x - rx) * tx / tilesize); + }else{ + float ry = tile.worldy() - ty / 2f * tilesize; + min = Mathf.clamp((unit.y - ry) * ty / tilesize); + } + + entity.minCarry = Math.min(entity.minCarry, min); + entity.carrying += unit.getMass(); + + if(entity.convey.size * itemSpace < 0.9f){ + unit.getVelocity().add(tx * speed * Timers.delta(), ty * speed * Timers.delta()); + } + } + + @Override + public synchronized void update(Tile tile){ + + ConveyorEntity entity = tile.entity(); + entity.minitem = 1f; + + int minremove = Integer.MAX_VALUE; + float speed = Math.max(this.speed - (1f - (carryCapacity - entity.carrying) / carryCapacity), 0f); + float totalMoved = 0f; + + for(int i = entity.convey.size - 1; i >= 0; i--){ + long value = entity.convey.get(i); + ItemPos pos = pos1.set(value, ItemPos.updateShorts); + + //..this should never happen, but in case it does, remove it and stop here + if(pos.item == null){ + entity.convey.removeValue(value); + break; + } + + float nextpos = (i == entity.convey.size - 1 ? 100f : pos2.set(entity.convey.get(i + 1), ItemPos.updateShorts).y) - itemSpace; + if(entity.minCarry >= pos.y && entity.minCarry <= nextpos){ + nextpos = entity.minCarry; + } + float maxmove = Math.min(nextpos - pos.y, speed * Timers.delta()); + + if(maxmove > minmove){ + pos.y += maxmove; + pos.x = Mathf.lerpDelta(pos.x, 0, 0.06f); + totalMoved += maxmove; + }else{ + pos.x = Mathf.lerpDelta(pos.x, pos.seed / offsetScl, 0.1f); + } + + pos.y = Mathf.clamp(pos.y); + + if(pos.y >= 0.9999f && offloadDir(tile, pos.item)){ + minremove = Math.min(i, minremove); + totalMoved = 1f; + tile.entity.items.remove(pos.item, 1); + }else{ + value = pos.pack(); + + if(pos.y < entity.minitem) + entity.minitem = pos.y; + entity.convey.set(i, value); + } + } + + if(entity.minitem < itemSpace){ + entity.clogHeat = Mathf.lerpDelta(entity.clogHeat, 1f, 0.02f); + }else{ + entity.clogHeat = Mathf.lerpDelta(entity.clogHeat, 0f, 1f); + } + + entity.carrying = 0f; + entity.minCarry = 2f; + + if(totalMoved <= 0.0001f){ + entity.sleep(); + } + + if(minremove != Integer.MAX_VALUE) entity.convey.truncate(minremove); + } + + @Override + public boolean isAccessible(){ + return true; + } + + @Override + public synchronized int removeStack(Tile tile, Item item, int amount){ + ConveyorEntity entity = tile.entity(); + entity.wakeUp(); + int removed = 0; + + for(int j = 0; j < amount; j++){ + for(int i = 0; i < entity.convey.size; i++){ + long val = entity.convey.get(i); + ItemPos pos = pos1.set(val, ItemPos.drawShorts); + if(pos.item == item){ + entity.convey.removeValue(val); + entity.items.remove(item, 1); + removed++; + break; + } + } + } + return removed; + } + + @Override + public void getStackOffset(Item item, Tile tile, Translator trns){ + trns.trns(tile.getRotation() * 90 + 180f, tilesize / 2f); + } + + @Override + public synchronized int acceptStack(Item item, int amount, Tile tile, Unit source){ + ConveyorEntity entity = tile.entity(); + return entity.minitem > itemSpace ? 1 : 0; + } + + @Override + public synchronized void handleStack(Item item, int amount, Tile tile, Unit source){ + ConveyorEntity entity = tile.entity(); + + long result = ItemPos.packItem(item, 0f, 0f, (byte) Mathf.random(255)); + entity.convey.insert(0, result); + entity.items.add(item, 1); + entity.wakeUp(); + } + + @Override + public boolean acceptItem(Item item, Tile tile, Tile source){ + int direction = source == null ? 0 : Math.abs(source.relativeTo(tile.x, tile.y) - tile.getRotation()); + float minitem = tile.entity().minitem; + return (((direction == 0) && minitem > itemSpace) || + ((direction % 2 == 1) && minitem > 0.52f)) && (source == null || !(source.block().rotate && (source.getRotation() + 2) % 4 == tile.getRotation())); + } + + @Override + public void handleItem(Item item, Tile tile, Tile source){ + byte rotation = tile.getRotation(); + + int ch = Math.abs(source.relativeTo(tile.x, tile.y) - rotation); + int ang = ((source.relativeTo(tile.x, tile.y) - rotation)); + + float pos = ch == 0 ? 0 : ch % 2 == 1 ? 0.5f : 1f; + float y = (ang == -1 || ang == 3) ? 1 : (ang == 1 || ang == -3) ? -1 : 0; + + ConveyorEntity entity = tile.entity(); + entity.wakeUp(); + long result = ItemPos.packItem(item, y * 0.9f, pos, (byte) Mathf.random(255)); + boolean inserted = false; + + tile.entity.items.add(item, 1); + + for(int i = 0; i < entity.convey.size; i++){ + if(compareItems(result, entity.convey.get(i)) < 0){ + entity.convey.insert(i, result); + inserted = true; + break; + } + } + + //this item must be greater than anything there... + if(!inserted){ + entity.convey.add(result); + } + } + + @Override + public Array getDebugInfo(Tile tile){ + ConveyorEntity entity = tile.entity(); + Array arr = super.getDebugInfo(tile); + arr.addAll(Array.with( + "mincarry", entity.minCarry, + "minitem", entity.minCarry, + "carrying", entity.carrying, + "clogHeat", entity.clogHeat, + "sleeping", entity.isSleeping() + )); + return arr; + } + + @Override + public TileEntity getEntity(){ + return new ConveyorEntity(); + } + + public static class ConveyorEntity extends TileEntity{ + + LongArray convey = new LongArray(); + float minitem = 1; + float carrying; + float minCarry = 2f; + + float clogHeat = 0f; + + @Override + public void write(DataOutputStream stream) throws IOException{ + stream.writeInt(convey.size); + + for(int i = 0; i < convey.size; i++){ + stream.writeInt(ItemPos.toInt(convey.get(i))); + } + } + + @Override + public void read(DataInputStream stream) throws IOException{ + convey.clear(); + int amount = stream.readInt(); + convey.ensureCapacity(amount); + + for(int i = 0; i < amount; i++){ + convey.add(ItemPos.toLong(stream.readInt())); + } + } + } + + //Container class. Do not instantiate. + static class ItemPos{ + private static short[] writeShort = new short[4]; + private static byte[] writeByte = new byte[4]; + + private static short[] packShorts = new short[4]; + private static short[] drawShorts = new short[4]; + private static short[] updateShorts = new short[4]; + + Item item; + float x, y; + byte seed; + + private ItemPos(){ + } + + static long packItem(Item item, float x, float y, byte seed){ + short[] shorts = packShorts; + shorts[0] = (short) item.id; + shorts[1] = (short) (x * Short.MAX_VALUE); + shorts[2] = (short) ((y - 1f) * Short.MAX_VALUE); + shorts[3] = seed; + return Bits.packLong(shorts); + } + + static int toInt(long value){ + short[] values = Bits.getShorts(value, writeShort); + + short itemid = values[0]; + float x = values[1] / (float) Short.MAX_VALUE; + float y = ((float) values[2]) / Short.MAX_VALUE + 1f; + byte seed = (byte) values[3]; + + byte[] bytes = writeByte; + bytes[0] = (byte) itemid; + bytes[1] = (byte) (x * 127); + bytes[2] = (byte) (y * 255 - 128); + bytes[3] = seed; + + return Bits.packInt(bytes); + } + + static long toLong(int value){ + byte[] values = Bits.getBytes(value, writeByte); + + byte itemid = values[0]; + float x = values[1] / 127f; + float y = ((int) values[2] + 128) / 255f; + byte seed = values[3]; + + short[] shorts = writeShort; + shorts[0] = (short) itemid; + shorts[1] = (short) (x * Short.MAX_VALUE); + shorts[2] = (short) ((y - 1f) * Short.MAX_VALUE); + shorts[3] = seed; + return Bits.packLong(shorts); + } + + ItemPos set(long lvalue, short[] values){ + Bits.getShorts(lvalue, values); + + if(values[0] >= Item.all().size || values[0] < 0) + item = null; + else + item = Item.all().get(values[0]); + + x = values[1] / (float) Short.MAX_VALUE; + y = ((float) values[2]) / Short.MAX_VALUE + 1f; + seed = (byte) values[3]; + return this; + } + + long pack(){ + return packItem(item, x, y, seed); + } + } } \ No newline at end of file diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/ExtendingItemBridge.java b/core/src/io/anuke/mindustry/world/blocks/distribution/ExtendingItemBridge.java index 4c7309859d..a072628481 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/ExtendingItemBridge.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/ExtendingItemBridge.java @@ -10,15 +10,15 @@ import io.anuke.ucore.util.Mathf; import static io.anuke.mindustry.Vars.tilesize; import static io.anuke.mindustry.Vars.world; -public class ExtendingItemBridge extends ItemBridge { +public class ExtendingItemBridge extends ItemBridge{ - public ExtendingItemBridge(String name) { + public ExtendingItemBridge(String name){ super(name); hasItems = true; } @Override - public void drawLayer(Tile tile) { + public void drawLayer(Tile tile){ ItemBridgeEntity entity = tile.entity(); Tile other = world.tile(entity.link); @@ -26,36 +26,36 @@ public class ExtendingItemBridge extends ItemBridge { int i = tile.absoluteRelativeTo(other.x, other.y); - float ex = other.worldx() - tile.worldx() - Geometry.d4[i].x*tilesize/2f, - ey = other.worldy() - tile.worldy() - Geometry.d4[i].y*tilesize/2f; + float ex = other.worldx() - tile.worldx() - Geometry.d4[i].x * tilesize / 2f, + ey = other.worldy() - tile.worldy() - Geometry.d4[i].y * tilesize / 2f; ex *= entity.uptime; ey *= entity.uptime; Lines.stroke(8f); Lines.line(bridgeRegion, - tile.worldx() + Geometry.d4[i].x*tilesize/2f, - tile.worldy() + Geometry.d4[i].y*tilesize/2f, + tile.worldx() + Geometry.d4[i].x * tilesize / 2f, + tile.worldy() + Geometry.d4[i].y * tilesize / 2f, tile.worldx() + ex, tile.worldy() + ey, CapStyle.none, 0f); - Draw.rect(endRegion, tile.drawx(), tile.drawy(), i*90 + 90); + Draw.rect(endRegion, tile.drawx(), tile.drawy(), i * 90 + 90); Draw.rect(endRegion, - tile.worldx() + ex + Geometry.d4[i].x*tilesize/2f, - tile.worldy() + ey + Geometry.d4[i].y*tilesize/2f, i*90 + 270); + tile.worldx() + ex + Geometry.d4[i].x * tilesize / 2f, + tile.worldy() + ey + Geometry.d4[i].y * tilesize / 2f, i * 90 + 270); int dist = Math.max(Math.abs(other.x - tile.x), Math.abs(other.y - tile.y)); - int arrows = (dist)*tilesize/6-1; + int arrows = (dist) * tilesize / 6 - 1; Draw.color(); - for(int a = 0; a < arrows; a ++){ - Draw.alpha(Mathf.absin(a/(float)arrows - entity.time/100f, 0.1f, 1f) * entity.uptime); + for(int a = 0; a < arrows; a++){ + Draw.alpha(Mathf.absin(a / (float) arrows - entity.time / 100f, 0.1f, 1f) * entity.uptime); Draw.rect(arrowRegion, - tile.worldx() + Geometry.d4[i].x*(tilesize/2f + a*6f + 2) * entity.uptime, - tile.worldy() + Geometry.d4[i].y*(tilesize/2f + a*6f + 2) * entity.uptime, - i*90f); + tile.worldx() + Geometry.d4[i].x * (tilesize / 2f + a * 6f + 2) * entity.uptime, + tile.worldy() + Geometry.d4[i].y * (tilesize / 2f + a * 6f + 2) * entity.uptime, + i * 90f); } Draw.reset(); } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java b/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java index 089e01ced8..f162f1a530 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java @@ -30,7 +30,7 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.tilesize; import static io.anuke.mindustry.Vars.world; -public class ItemBridge extends Block { +public class ItemBridge extends Block{ protected static int lastPlaced; protected int timerTransport = timers++; @@ -40,7 +40,7 @@ public class ItemBridge extends Block { protected TextureRegion endRegion, bridgeRegion, arrowRegion; - public ItemBridge(String name) { + public ItemBridge(String name){ super(name); update = true; solid = true; @@ -52,8 +52,26 @@ public class ItemBridge extends Block { hasItems = true; } + @Remote(targets = Loc.both, called = Loc.both, in = In.blocks, forward = true) + public static void linkItemBridge(Player player, Tile tile, Tile other){ + ItemBridgeEntity entity = tile.entity(); + ItemBridgeEntity oe = other.entity(); + entity.link = other.packedPosition(); + oe.incoming.add(tile.packedPosition()); + } + + @Remote(targets = Loc.both, called = Loc.server, in = In.blocks, forward = true) + public static void unlinkItemBridge(Player player, Tile tile, Tile other){ + ItemBridgeEntity entity = tile.entity(); + entity.link = -1; + if(other != null){ + ItemBridgeEntity oe = other.entity(); + oe.incoming.remove(tile.packedPosition()); + } + } + @Override - public void load() { + public void load(){ super.load(); endRegion = Draw.region(name + "-end"); @@ -62,7 +80,7 @@ public class ItemBridge extends Block { } @Override - public void placed(Tile tile) { + public void placed(Tile tile){ Tile last = world.tile(lastPlaced); if(linkValid(tile, last)){ ItemBridgeEntity entity = last.entity(); @@ -74,13 +92,13 @@ public class ItemBridge extends Block { } @Override - public void drawPlace(int x, int y, int rotation, boolean valid) { + public void drawPlace(int x, int y, int rotation, boolean valid){ Lines.stroke(2f); Draw.color(Palette.placing); - for(int i = 0; i < 4; i ++){ + for(int i = 0; i < 4; i++){ Lines.dashLine( - x * tilesize + Geometry.d4[i].x * (tilesize/2f + 2), - y * tilesize + Geometry.d4[i].y * (tilesize/2f + 2), + x * tilesize + Geometry.d4[i].x * (tilesize / 2f + 2), + y * tilesize + Geometry.d4[i].y * (tilesize / 2f + 2), x * tilesize + Geometry.d4[i].x * range * tilesize, y * tilesize + Geometry.d4[i].y * range * tilesize, range); @@ -98,8 +116,8 @@ public class ItemBridge extends Block { Lines.square(tile.drawx(), tile.drawy(), tile.block().size * tilesize / 2f + 1f); - for(int i = 1; i <= range; i ++){ - for(int j = 0; j < 4; j ++){ + for(int i = 1; i <= range; i++){ + for(int j = 0; j < 4; j++){ Tile other = tile.getNearby(Geometry.d4[j].x * i, Geometry.d4[j].y * i); if(linkValid(tile, other)){ boolean linked = other.packedPosition() == entity.link; @@ -115,7 +133,7 @@ public class ItemBridge extends Block { } @Override - public boolean onConfigureTileTapped(Tile tile, Tile other) { + public boolean onConfigureTileTapped(Tile tile, Tile other){ ItemBridgeEntity entity = tile.entity(); if(linkValid(tile, other)){ @@ -130,11 +148,11 @@ public class ItemBridge extends Block { } @Override - public void update(Tile tile) { + public void update(Tile tile){ ItemBridgeEntity entity = tile.entity(); - entity.time += entity.cycleSpeed*Timers.delta(); - entity.time2 += (entity.cycleSpeed-1f)*Timers.delta(); + entity.time += entity.cycleSpeed * Timers.delta(); + entity.time2 += (entity.cycleSpeed - 1f) * Timers.delta(); removals.clear(); @@ -148,7 +166,7 @@ public class ItemBridge extends Block { } } - for(int j = 0; j < removals.size; j ++) + for(int j = 0; j < removals.size; j++) entity.incoming.remove(removals.get(j)); Tile other = world.tile(entity.link); @@ -183,7 +201,7 @@ public class ItemBridge extends Block { } @Override - public void drawLayer(Tile tile) { + public void drawLayer(Tile tile){ ItemBridgeEntity entity = tile.entity(); Tile other = world.tile(entity.link); @@ -194,35 +212,35 @@ public class ItemBridge extends Block { Draw.color(Color.WHITE, Color.BLACK, Mathf.absin(Timers.time(), 6f, 0.07f)); Draw.alpha(Math.max(entity.uptime, 0.25f)); - Draw.rect(endRegion, tile.drawx(), tile.drawy(), i*90 + 90); - Draw.rect(endRegion, other.drawx(), other.drawy(), i*90 + 270); + Draw.rect(endRegion, tile.drawx(), tile.drawy(), i * 90 + 90); + Draw.rect(endRegion, other.drawx(), other.drawy(), i * 90 + 270); Lines.stroke(8f); Lines.line(bridgeRegion, tile.worldx(), tile.worldy(), other.worldx(), - other.worldy(), CapStyle.none, -tilesize/2f); + other.worldy(), CapStyle.none, -tilesize / 2f); int dist = Math.max(Math.abs(other.x - tile.x), Math.abs(other.y - tile.y)); - float time = entity.time2/1.7f; - int arrows = (dist)*tilesize/4-2; + float time = entity.time2 / 1.7f; + int arrows = (dist) * tilesize / 4 - 2; Draw.color(); - for(int a = 0; a < arrows; a ++){ - Draw.alpha(Mathf.absin(a/(float)arrows - entity.time/100f, 0.1f, 1f) * entity.uptime); + for(int a = 0; a < arrows; a++){ + Draw.alpha(Mathf.absin(a / (float) arrows - entity.time / 100f, 0.1f, 1f) * entity.uptime); Draw.rect(arrowRegion, - tile.worldx() + Geometry.d4[i].x*(tilesize/2f + a*4f + time % 4f), - tile.worldy() + Geometry.d4[i].y*(tilesize/2f + a*4f + time % 4f), - i*90f); + tile.worldx() + Geometry.d4[i].x * (tilesize / 2f + a * 4f + time % 4f), + tile.worldy() + Geometry.d4[i].y * (tilesize / 2f + a * 4f + time % 4f), + i * 90f); } Draw.reset(); } @Override - public boolean acceptItem(Item item, Tile tile, Tile source) { + public boolean acceptItem(Item item, Tile tile, Tile source){ ItemBridgeEntity entity = tile.entity(); Tile other = world.tile(entity.link); @@ -250,7 +268,7 @@ public class ItemBridge extends Block { } @Override - public boolean canDump(Tile tile, Tile to, Item item) { + public boolean canDump(Tile tile, Tile to, Item item){ ItemBridgeEntity entity = tile.entity(); Tile other = world.tile(entity.link); @@ -277,7 +295,7 @@ public class ItemBridge extends Block { } @Override - public TileEntity getEntity() { + public TileEntity getEntity(){ return new ItemBridgeEntity(); } @@ -298,24 +316,6 @@ public class ItemBridge extends Block { return other.block() == this && (!checkDouble || other.entity().link != tile.packedPosition()); } - @Remote(targets = Loc.both, called = Loc.both, in = In.blocks, forward = true) - public static void linkItemBridge(Player player, Tile tile, Tile other){ - ItemBridgeEntity entity = tile.entity(); - ItemBridgeEntity oe = other.entity(); - entity.link = other.packedPosition(); - oe.incoming.add(tile.packedPosition()); - } - - @Remote(targets = Loc.both, called = Loc.server, in = In.blocks, forward = true) - public static void unlinkItemBridge(Player player, Tile tile, Tile other){ - ItemBridgeEntity entity = tile.entity(); - entity.link = -1; - if(other != null) { - ItemBridgeEntity oe = other.entity(); - oe.incoming.remove(tile.packedPosition()); - } - } - public static class ItemBridgeEntity extends TileEntity{ public int link = -1; public IntSet incoming = new IntSet(); @@ -325,7 +325,7 @@ public class ItemBridge extends Block { public float cycleSpeed = 1f; @Override - public void write(DataOutputStream stream) throws IOException { + public void write(DataOutputStream stream) throws IOException{ stream.writeInt(link); stream.writeFloat(uptime); stream.writeByte(incoming.size); @@ -338,11 +338,11 @@ public class ItemBridge extends Block { } @Override - public void read(DataInputStream stream) throws IOException { + public void read(DataInputStream stream) throws IOException{ link = stream.readInt(); uptime = stream.readFloat(); byte links = stream.readByte(); - for(int i = 0; i < links; i ++){ + for(int i = 0; i < links; i++){ incoming.add(stream.readInt()); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/Junction.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Junction.java index d8aa792fe4..f3267a1cf1 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Junction.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Junction.java @@ -5,97 +5,98 @@ import com.badlogic.gdx.utils.NumberUtils; import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.type.Item; import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.meta.BlockGroup; import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.meta.BlockGroup; import io.anuke.ucore.core.Timers; import io.anuke.ucore.function.Consumer; import io.anuke.ucore.util.Bits; public class Junction extends Block{ - protected float speed = 26; //frames taken to go through this junction - protected int capacity = 32; + protected float speed = 26; //frames taken to go through this junction + protected int capacity = 32; - public Junction(String name) { - super(name); - update = true; - solid = true; - instantTransfer = true; - group = BlockGroup.transportation; - } + public Junction(String name){ + super(name); + update = true; + solid = true; + instantTransfer = true; + group = BlockGroup.transportation; + } - @Override - public void update(Tile tile){ - JunctionEntity entity = tile.entity(); + @Override + public void update(Tile tile){ + JunctionEntity entity = tile.entity(); - for(int i = 0; i < 2; i ++){ - Buffer buffer = (i == 0 ? entity.bx : entity.by); + for(int i = 0; i < 2; i++){ + Buffer buffer = (i == 0 ? entity.bx : entity.by); - if(buffer.index > 0){ - if(buffer.index > buffer.items.length) buffer.index = buffer.items.length; - long l = buffer.items[0]; - float time = NumberUtils.intBitsToFloat(Bits.getLeftInt(l)); + if(buffer.index > 0){ + if(buffer.index > buffer.items.length) buffer.index = buffer.items.length; + long l = buffer.items[0]; + float time = NumberUtils.intBitsToFloat(Bits.getLeftInt(l)); - if(Timers.time() >= time + speed || Timers.time() < time){ + if(Timers.time() >= time + speed || Timers.time() < time){ - int val = Bits.getRightInt(l); + int val = Bits.getRightInt(l); - Item item = Item.getByID(Bits.getLeftShort(val)); - int direction = Bits.getRightShort(val); - Tile dest = tile.getNearby(direction); + Item item = Item.getByID(Bits.getLeftShort(val)); + int direction = Bits.getRightShort(val); + Tile dest = tile.getNearby(direction); - if(dest == null || !dest.block().acceptItem(item, dest, tile)){ - if(buffer.index > 1 && Bits.getRightShort(Bits.getRightInt(buffer.items[1])) != direction){ - System.arraycopy(buffer.items, 1, buffer.items, 0, buffer.index - 1); - buffer.index --; - } - continue; - } + if(dest == null || !dest.block().acceptItem(item, dest, tile)){ + if(buffer.index > 1 && Bits.getRightShort(Bits.getRightInt(buffer.items[1])) != direction){ + System.arraycopy(buffer.items, 1, buffer.items, 0, buffer.index - 1); + buffer.index--; + } + continue; + } - dest.block().handleItem(item, dest, tile); - System.arraycopy(buffer.items, 1, buffer.items, 0, buffer.index - 1); - buffer.index --; - } - } - } - } + dest.block().handleItem(item, dest, tile); + System.arraycopy(buffer.items, 1, buffer.items, 0, buffer.index - 1); + buffer.index--; + } + } + } + } - @Override - public void handleItem(Item item, Tile tile, Tile source){ - JunctionEntity entity = tile.entity(); - boolean x = tile.x == source.x; - long value = Bits.packLong(NumberUtils.floatToIntBits(Timers.time()), Bits.packInt((short)item.id, source.relativeTo(tile.x, tile.y))); - if(x){ - entity.bx.add(value); - }else { - entity.by.add(value); - } - } + @Override + public void handleItem(Item item, Tile tile, Tile source){ + JunctionEntity entity = tile.entity(); + boolean x = tile.x == source.x; + long value = Bits.packLong(NumberUtils.floatToIntBits(Timers.time()), Bits.packInt((short) item.id, source.relativeTo(tile.x, tile.y))); + if(x){ + entity.bx.add(value); + }else{ + entity.by.add(value); + } + } - @Override - public boolean acceptItem(Item item, Tile tile, Tile source){ - JunctionEntity entity = tile.entity(); - boolean x = tile.x == source.x; + @Override + public boolean acceptItem(Item item, Tile tile, Tile source){ + JunctionEntity entity = tile.entity(); + boolean x = tile.x == source.x; - if(entity == null || entity.bx == null || entity.by == null || (x && entity.bx.full()) || (!x && entity.by.full())) return false; - int dir = source.relativeTo(tile.x, tile.y); - if(dir == -1) return false; - Tile to = tile.getNearby(dir); - return to != null && to.block().acceptItem(item, to, tile); - } + if(entity == null || entity.bx == null || entity.by == null || (x && entity.bx.full()) || (!x && entity.by.full())) + return false; + int dir = source.relativeTo(tile.x, tile.y); + if(dir == -1) return false; + Tile to = tile.getNearby(dir); + return to != null && to.block().acceptItem(item, to, tile); + } - @Override - public TileEntity getEntity() { - return new JunctionEntity(); - } + @Override + public TileEntity getEntity(){ + return new JunctionEntity(); + } - @Override - public Array getDebugInfo(Tile tile){ - JunctionEntity entity = tile.entity(); - Array arr = super.getDebugInfo(tile); - for(int i = 0; i < 4; i ++){ - arr.add("nearby." + i); - arr.add(tile.getNearby(i)); - } + @Override + public Array getDebugInfo(Tile tile){ + JunctionEntity entity = tile.entity(); + Array arr = super.getDebugInfo(tile); + for(int i = 0; i < 4; i++){ + arr.add("nearby." + i); + arr.add(tile.getNearby(i)); + } Consumer write = b -> { for(int i = 0; i < b.index; i++){ @@ -110,32 +111,32 @@ public class Junction extends Block{ } }; - arr.add("buffer.bx"); - arr.add(entity.bx.index); - write.accept(entity.bx); + arr.add("buffer.bx"); + arr.add(entity.bx.index); + write.accept(entity.bx); arr.add("buffer.by"); arr.add(entity.bx.index); - write.accept(entity.by); + write.accept(entity.by); - return arr; - } + return arr; + } - class JunctionEntity extends TileEntity{ - Buffer bx = new Buffer(); - Buffer by = new Buffer(); - } + class JunctionEntity extends TileEntity{ + Buffer bx = new Buffer(); + Buffer by = new Buffer(); + } - class Buffer{ - long[] items = new long[capacity]; - int index; + class Buffer{ + long[] items = new long[capacity]; + int index; - void add(long id){ - if(full()) return; - items[index++] = id; - } + void add(long id){ + if(full()) return; + items[index++] = id; + } - boolean full(){ - return index >= items.length - 1; - } - } + boolean full(){ + return index >= items.length - 1; + } + } } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidBridge.java b/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidBridge.java index 8562b0735c..383a86cf03 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidBridge.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidBridge.java @@ -7,9 +7,9 @@ import io.anuke.ucore.util.Mathf; import static io.anuke.mindustry.Vars.world; -public class LiquidBridge extends ItemBridge { +public class LiquidBridge extends ItemBridge{ - public LiquidBridge(String name) { + public LiquidBridge(String name){ super(name); hasItems = false; hasLiquids = true; @@ -17,11 +17,11 @@ public class LiquidBridge extends ItemBridge { } @Override - public void update(Tile tile) { + public void update(Tile tile){ ItemBridgeEntity entity = tile.entity(); - entity.time += entity.cycleSpeed* Timers.delta(); - entity.time2 += (entity.cycleSpeed-1f)*Timers.delta(); + entity.time += entity.cycleSpeed * Timers.delta(); + entity.time2 += (entity.cycleSpeed - 1f) * Timers.delta(); Tile other = world.tile(entity.link); if(!linkValid(tile, other)){ @@ -46,7 +46,7 @@ public class LiquidBridge extends ItemBridge { } @Override - public boolean acceptItem(Item item, Tile tile, Tile source) { + public boolean acceptItem(Item item, Tile tile, Tile source){ return false; } } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidExtendingBridge.java b/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidExtendingBridge.java index 5076806347..7be3b85cc5 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidExtendingBridge.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidExtendingBridge.java @@ -7,9 +7,9 @@ import io.anuke.ucore.util.Mathf; import static io.anuke.mindustry.Vars.world; -public class LiquidExtendingBridge extends ExtendingItemBridge { +public class LiquidExtendingBridge extends ExtendingItemBridge{ - public LiquidExtendingBridge(String name) { + public LiquidExtendingBridge(String name){ super(name); hasItems = false; hasLiquids = true; @@ -17,11 +17,11 @@ public class LiquidExtendingBridge extends ExtendingItemBridge { } @Override - public void update(Tile tile) { + public void update(Tile tile){ ItemBridgeEntity entity = tile.entity(); - entity.time += entity.cycleSpeed* Timers.delta(); - entity.time2 += (entity.cycleSpeed-1f)*Timers.delta(); + entity.time += entity.cycleSpeed * Timers.delta(); + entity.time2 += (entity.cycleSpeed - 1f) * Timers.delta(); Tile other = world.tile(entity.link); if(!linkValid(tile, other)){ @@ -45,7 +45,7 @@ public class LiquidExtendingBridge extends ExtendingItemBridge { } @Override - public boolean acceptItem(Item item, Tile tile, Tile source) { + public boolean acceptItem(Item item, Tile tile, Tile source){ return false; } } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidJunction.java b/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidJunction.java index 3f99bce32b..4bb6bc1d39 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidJunction.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidJunction.java @@ -9,37 +9,37 @@ import io.anuke.ucore.graphics.Draw; //TODO fix public class LiquidJunction extends LiquidBlock{ - public LiquidJunction(String name) { - super(name); - hasLiquids = true; - } - - @Override - public void draw(Tile tile){ - Draw.rect(name(), tile.worldx(), tile.worldy()); - } + public LiquidJunction(String name){ + super(name); + hasLiquids = true; + } - @Override - public TextureRegion[] getIcon(){ - return new TextureRegion[]{Draw.region(name)}; - } + @Override + public void draw(Tile tile){ + Draw.rect(name(), tile.worldx(), tile.worldy()); + } - @Override - public void handleLiquid(Tile tile, Tile source, Liquid liquid, float amount){ - int dir = source.relativeTo(tile.x, tile.y); - dir = (dir+4)%4; - Tile to = tile.getNearby(dir); + @Override + public TextureRegion[] getIcon(){ + return new TextureRegion[]{Draw.region(name)}; + } + + @Override + public void handleLiquid(Tile tile, Tile source, Liquid liquid, float amount){ + int dir = source.relativeTo(tile.x, tile.y); + dir = (dir + 4) % 4; + Tile to = tile.getNearby(dir); if(to.block().hasLiquids && to.block().acceptLiquid(to, tile, liquid, amount)) to.block().handleLiquid(to, tile, liquid, amount); - } + } - @Override - public boolean acceptLiquid(Tile dest, Tile source, Liquid liquid, float amount){ - int dir = source.relativeTo(dest.x, dest.y); - dir = (dir+4)%4; - Tile to = dest.getNearby(dir); - return to != null && to.block().hasLiquids && - to.block().acceptLiquid(to, dest, liquid, amount); - } + @Override + public boolean acceptLiquid(Tile dest, Tile source, Liquid liquid, float amount){ + int dir = source.relativeTo(dest.x, dest.y); + dir = (dir + 4) % 4; + Tile to = dest.getNearby(dir); + return to != null && to.block().hasLiquids && + to.block().acceptLiquid(to, dest, liquid, amount); + } } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidRouter.java b/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidRouter.java index 8083564e7c..4ca44a549c 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidRouter.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidRouter.java @@ -5,16 +5,16 @@ import io.anuke.mindustry.world.blocks.LiquidBlock; public class LiquidRouter extends LiquidBlock{ - public LiquidRouter(String name) { - super(name); - } - - @Override - public void update(Tile tile){ - - if(tile.entity.liquids.total() > 0.01f){ - tryDumpLiquid(tile, tile.entity.liquids.current()); - } - } + public LiquidRouter(String name){ + super(name); + } + + @Override + public void update(Tile tile){ + + if(tile.entity.liquids.total() > 0.01f){ + tryDumpLiquid(tile, tile.entity.liquids.current()); + } + } } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/MassDriver.java b/core/src/io/anuke/mindustry/world/blocks/distribution/MassDriver.java index c7703e8f69..ac20aefb83 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/MassDriver.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/MassDriver.java @@ -33,7 +33,7 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.*; -public class MassDriver extends Block { +public class MassDriver extends Block{ protected float range; protected float rotateSpeed = 0.04f; protected float translation = 7f; @@ -45,7 +45,7 @@ public class MassDriver extends Block { protected Effect recieveEffect = BlockFx.smeltsmoke; protected float shake = 3f; - public MassDriver(String name) { + public MassDriver(String name){ super(name); update = true; solid = true; @@ -56,8 +56,52 @@ public class MassDriver extends Block { hasPower = true; } + @Remote(targets = Loc.both, called = Loc.server, in = In.blocks, forward = true) + public static void linkMassDriver(Player player, Tile tile, int position){ + MassDriverEntity entity = tile.entity(); + + //called in main thread to prevent issues + threads.run(() -> entity.link = position); + } + + @Remote(called = Loc.server, in = In.blocks) + public static void onMassDriverFire(Tile tile, Tile target){ + //just in case the client has invalid data + if(!(tile.entity instanceof MassDriverEntity) || !(target.entity instanceof MassDriverEntity)) return; + + MassDriver driver = (MassDriver) tile.block(); + + MassDriverEntity entity = tile.entity(); + MassDriverEntity other = target.entity(); + + entity.reload = 1f; + + DriverBulletData data = Pooling.obtain(DriverBulletData.class); + data.from = entity; + data.to = other; + for(int i = 0; i < Item.all().size; i++){ + data.items[i] = entity.items.get(Item.getByID(i)); + } + entity.items.clear(); + + float angle = tile.angleTo(target); + + other.isRecieving = true; + Bullet.create(TurretBullets.driverBolt, entity, entity.getTeam(), + tile.drawx() + Angles.trnsx(angle, driver.translation), tile.drawy() + Angles.trnsy(angle, driver.translation), + angle, 1f, data); + + Effects.effect(driver.shootEffect, tile.drawx() + Angles.trnsx(angle, driver.translation), + tile.drawy() + Angles.trnsy(angle, driver.translation), angle); + + Effects.effect(driver.smokeEffect, tile.drawx() + Angles.trnsx(angle, driver.translation), + tile.drawy() + Angles.trnsy(angle, driver.translation), angle); + + Effects.shake(driver.shake, driver.shake, entity); + } + @Override - public void update(Tile tile) { + public void update(Tile tile){ MassDriverEntity entity = tile.entity(); Tile link = world.tile(entity.link); @@ -70,19 +114,19 @@ public class MassDriver extends Block { } if(entity.reload > 0f){ - entity.reload = Mathf.clamp(entity.reload - Timers.delta()/reloadTime); + entity.reload = Mathf.clamp(entity.reload - Timers.delta() / reloadTime); } - if(!entity.isRecieving) { + if(!entity.isRecieving){ - if (entity.waiting.size > 0) { //accepting takes priority over shooting + if(entity.waiting.size > 0){ //accepting takes priority over shooting Tile waiter = entity.waiting.first(); entity.rotation = Mathf.slerpDelta(entity.rotation, tile.angleTo(waiter), rotateSpeed); - }else if (tile.entity.items.total() >= minDistribute && - linkValid(tile) && //only fire when at least at half-capacity and power - tile.entity.power.amount >= powerCapacity && - link.block().itemCapacity - link.entity.items.total() >= minDistribute && entity.reload <= 0.0001f) { + }else if(tile.entity.items.total() >= minDistribute && + linkValid(tile) && //only fire when at least at half-capacity and power + tile.entity.power.amount >= powerCapacity && + link.block().itemCapacity - link.entity.items.total() >= minDistribute && entity.reload <= 0.0001f){ MassDriverEntity other = link.entity(); other.waiting.add(tile); @@ -91,8 +135,8 @@ public class MassDriver extends Block { entity.rotation = Mathf.slerpDelta(entity.rotation, target, rotateSpeed); - if (Mathf.angNear(entity.rotation, target, 1f) && - Mathf.angNear(other.rotation, target + 180f, 1f)) { + if(Mathf.angNear(entity.rotation, target, 1f) && + Mathf.angNear(other.rotation, target + 180f, 1f)){ CallBlocks.onMassDriverFire(tile, link); } } @@ -102,7 +146,7 @@ public class MassDriver extends Block { } @Override - public void drawLayer(Tile tile) { + public void drawLayer(Tile tile){ MassDriverEntity entity = tile.entity(); Draw.rect(name + "-turret", @@ -112,7 +156,7 @@ public class MassDriver extends Block { } @Override - public void drawConfigure(Tile tile) { + public void drawConfigure(Tile tile){ super.drawConfigure(tile); MassDriverEntity entity = tile.entity(); @@ -137,7 +181,7 @@ public class MassDriver extends Block { MassDriverEntity entity = tile.entity(); - if(entity.link == other.packedPosition()) { + if(entity.link == other.packedPosition()){ CallBlocks.linkMassDriver(null, tile, -1); return false; }else if(other.block() instanceof MassDriver && other.distanceTo(tile) <= range){ @@ -149,12 +193,12 @@ public class MassDriver extends Block { } @Override - public boolean acceptItem(Item item, Tile tile, Tile source) { + public boolean acceptItem(Item item, Tile tile, Tile source){ return tile.entity.items.total() < itemCapacity; } @Override - public TileEntity getEntity() { + public TileEntity getEntity(){ return new MassDriverEntity(); } @@ -166,48 +210,16 @@ public class MassDriver extends Block { return link != null && link.block() instanceof MassDriver && tile.distanceTo(link) <= range; } - @Remote(targets = Loc.both, called = Loc.server, in = In.blocks, forward = true) - public static void linkMassDriver(Player player, Tile tile, int position){ - MassDriverEntity entity = tile.entity(); + public static class DriverBulletData implements Poolable{ + public MassDriverEntity from, to; + public int[] items = new int[Item.all().size]; - //called in main thread to prevent issues - threads.run(() -> entity.link = position); - } - - @Remote(called = Loc.server, in = In.blocks) - public static void onMassDriverFire(Tile tile, Tile target){ - //just in case the client has invalid data - if(!(tile.entity instanceof MassDriverEntity) || !(target.entity instanceof MassDriverEntity)) return; - - MassDriver driver = (MassDriver)tile.block(); - - MassDriverEntity entity = tile.entity(); - MassDriverEntity other = target.entity(); - - entity.reload = 1f; - - DriverBulletData data = Pooling.obtain(DriverBulletData.class); - data.from = entity; - data.to = other; - for (int i = 0; i < Item.all().size; i++) { - data.items[i] = entity.items.get(Item.getByID(i)); + @Override + public void reset(){ + from = null; + to = null; + ; } - entity.items.clear(); - - float angle = tile.angleTo(target); - - other.isRecieving = true; - Bullet.create(TurretBullets.driverBolt, entity, entity.getTeam(), - tile.drawx() + Angles.trnsx(angle, driver.translation), tile.drawy() + Angles.trnsy(angle, driver.translation), - angle, 1f, data); - - Effects.effect(driver.shootEffect, tile.drawx() + Angles.trnsx(angle, driver.translation), - tile.drawy() + Angles.trnsy(angle, driver.translation), angle); - - Effects.effect(driver.smokeEffect, tile.drawx() + Angles.trnsx(angle, driver.translation), - tile.drawy() + Angles.trnsy(angle, driver.translation), angle); - - Effects.shake(driver.shake, driver.shake, entity); } public class MassDriverEntity extends TileEntity{ @@ -226,7 +238,7 @@ public class MassDriver extends Block { int totalItems = items.total(); //add all the items possible - for(int i = 0; i < data.items.length; i ++){ + for(int i = 0; i < data.items.length; i++){ int maxAdd = Math.min(data.items[i], itemCapacity - totalItems); items.add(Item.getByID(i), maxAdd); data.items[i] -= maxAdd; @@ -238,7 +250,7 @@ public class MassDriver extends Block { } //drop all items remaining on the ground - for(int i = 0; i < data.items.length; i ++){ + for(int i = 0; i < data.items.length; i++){ int amountDropped = Mathf.random(0, data.items[i]); if(amountDropped > 0){ float angle = Mathf.range(180f); @@ -260,24 +272,13 @@ public class MassDriver extends Block { } @Override - public void write(DataOutputStream stream) throws IOException { + public void write(DataOutputStream stream) throws IOException{ stream.writeInt(link); } @Override - public void read(DataInputStream stream) throws IOException { + public void read(DataInputStream stream) throws IOException{ link = stream.readInt(); } } - - public static class DriverBulletData implements Poolable{ - public MassDriverEntity from, to; - public int[] items = new int[Item.all().size]; - - @Override - public void reset() { - from = null; - to = null;; - } - } } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/OverflowGate.java b/core/src/io/anuke/mindustry/world/blocks/distribution/OverflowGate.java index 60dbf0adc7..c7ebb6e75a 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/OverflowGate.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/OverflowGate.java @@ -4,9 +4,9 @@ import io.anuke.mindustry.type.Item; import io.anuke.mindustry.world.Tile; import io.anuke.ucore.util.Mathf; -public class OverflowGate extends Splitter { +public class OverflowGate extends Splitter{ - public OverflowGate(String name) { + public OverflowGate(String name){ super(name); hasItems = true; } @@ -38,11 +38,11 @@ public class OverflowGate extends Splitter { if(dest.getDump() == 0){ to = a; if(flip) - dest.setDump((byte)1); + dest.setDump((byte) 1); }else{ to = b; if(flip) - dest.setDump((byte)0); + dest.setDump((byte) 0); } } } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/Router.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Router.java index cd23e1990e..5994982a77 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Router.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Router.java @@ -8,48 +8,48 @@ import io.anuke.ucore.core.Timers; public class Router extends Block{ - public Router(String name) { - super(name); - update = true; - solid = true; - itemCapacity = 20; - hasItems = true; - group = BlockGroup.transportation; - autoSleep = true; - } - - @Override - public void update(Tile tile){ - int iterations = Math.max(1, (int) (Timers.delta() + 0.4f)); - boolean moved = tile.entity.items.total() > 0; + public Router(String name){ + super(name); + update = true; + solid = true; + itemCapacity = 20; + hasItems = true; + group = BlockGroup.transportation; + autoSleep = true; + } - for(int i = 0; i < iterations; i ++) { - if (tile.entity.items.total() > 0) { - tryDump(tile); - moved = true; - } - } + @Override + public void update(Tile tile){ + int iterations = Math.max(1, (int) (Timers.delta() + 0.4f)); + boolean moved = tile.entity.items.total() > 0; - if(!moved){ - tile.entity.sleep(); - } - } + for(int i = 0; i < iterations; i++){ + if(tile.entity.items.total() > 0){ + tryDump(tile); + moved = true; + } + } - @Override - public boolean canDump(Tile tile, Tile to, Item item) { + if(!moved){ + tile.entity.sleep(); + } + } + + @Override + public boolean canDump(Tile tile, Tile to, Item item){ return !(to.block() instanceof Router) || ((float) to.target().entity.items.total() / to.target().block().itemCapacity) < ((float) tile.entity.items.total() / to.target().block().itemCapacity); } - @Override - public void handleItem(Item item, Tile tile, Tile source){ - super.handleItem(item, tile, source); - tile.entity.wakeUp(); - } + @Override + public void handleItem(Item item, Tile tile, Tile source){ + super.handleItem(item, tile, source); + tile.entity.wakeUp(); + } - @Override - public boolean acceptItem(Item item, Tile tile, Tile source){ - int items = tile.entity.items.total(); - return items < itemCapacity; - } + @Override + public boolean acceptItem(Item item, Tile tile, Tile source){ + int items = tile.entity.items.total(); + return items < itemCapacity; + } } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/Sorter.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Sorter.java index 29d4324f3f..726aa1aadb 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Sorter.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Sorter.java @@ -21,110 +21,110 @@ import java.io.DataOutputStream; import java.io.IOException; public class Sorter extends Block implements SelectionTrait{ - - public Sorter(String name) { - super(name); - update = true; - solid = true; - instantTransfer = true; - group = BlockGroup.transportation; - configurable = true; - } - - @Override - public void draw(Tile tile){ - super.draw(tile); - //TODO call event for change - - SorterEntity entity = tile.entity(); + public Sorter(String name){ + super(name); + update = true; + solid = true; + instantTransfer = true; + group = BlockGroup.transportation; + configurable = true; + } - Draw.color(entity.sortItem.color); - Draw.rect("blank", tile.worldx(), tile.worldy(), 4f, 4f); - Draw.color(); - } - - @Override - public boolean acceptItem(Item item, Tile tile, Tile source){ - Tile to = getTileTarget(item, tile, source, false); - - return to != null && to.block().acceptItem(item, to, tile); - } - - @Override - public void handleItem(Item item, Tile tile, Tile source){ - Tile to = getTileTarget(item, tile, source, true); + @Remote(targets = Loc.both, called = Loc.both, in = In.blocks, forward = true) + public static void setSorterItem(Player player, Tile tile, Item item){ + SorterEntity entity = tile.entity(); + entity.sortItem = item; + } - to.block().handleItem(item, to, tile); - } - - Tile getTileTarget(Item item, Tile dest, Tile source, boolean flip){ - SorterEntity entity = dest.entity(); - - int dir = source.relativeTo(dest.x, dest.y); - if(dir == -1) return null; - Tile to; - - if(item == entity.sortItem){ - to = dest.getNearby(dir); - }else{ - Tile a = dest.getNearby(Mathf.mod(dir - 1, 4)); - Tile b = dest.getNearby(Mathf.mod(dir + 1, 4)); - boolean ac = a != null && !(a.block().instantTransfer && source.block().instantTransfer) && - a.block().acceptItem(item, a, dest); - boolean bc = b != null && !(b.block().instantTransfer && source.block().instantTransfer) && - b.block().acceptItem(item, b, dest); - - if(ac && !bc){ - to = a; - }else if(bc && !ac){ - to = b; - }else if(!bc){ - return null; - }else{ - if(dest.getDump() == 0){ - to = a; - if(flip) - dest.setDump((byte)1); - }else{ - to = b; - if(flip) - dest.setDump((byte)0); - } - } - } - - return to; - } - - @Override - public void buildTable(Tile tile, Table table){ - SorterEntity entity = tile.entity(); - buildItemTable(table, () -> entity.sortItem, item -> CallBlocks.setSorterItem(null, tile, item)); - } - - @Override - public TileEntity getEntity(){ - return new SorterEntity(); - } + @Override + public void draw(Tile tile){ + super.draw(tile); - @Remote(targets = Loc.both, called = Loc.both, in = In.blocks, forward = true) - public static void setSorterItem(Player player, Tile tile, Item item){ - SorterEntity entity = tile.entity(); - entity.sortItem = item; - } + //TODO call event for change - public static class SorterEntity extends TileEntity{ - public Item sortItem = Items.tungsten; - - @Override - public void write(DataOutputStream stream) throws IOException{ - stream.writeByte(sortItem.id); - } - - @Override - public void read(DataInputStream stream) throws IOException{ - sortItem = Item.all().get(stream.readByte()); - } - } + SorterEntity entity = tile.entity(); + + Draw.color(entity.sortItem.color); + Draw.rect("blank", tile.worldx(), tile.worldy(), 4f, 4f); + Draw.color(); + } + + @Override + public boolean acceptItem(Item item, Tile tile, Tile source){ + Tile to = getTileTarget(item, tile, source, false); + + return to != null && to.block().acceptItem(item, to, tile); + } + + @Override + public void handleItem(Item item, Tile tile, Tile source){ + Tile to = getTileTarget(item, tile, source, true); + + to.block().handleItem(item, to, tile); + } + + Tile getTileTarget(Item item, Tile dest, Tile source, boolean flip){ + SorterEntity entity = dest.entity(); + + int dir = source.relativeTo(dest.x, dest.y); + if(dir == -1) return null; + Tile to; + + if(item == entity.sortItem){ + to = dest.getNearby(dir); + }else{ + Tile a = dest.getNearby(Mathf.mod(dir - 1, 4)); + Tile b = dest.getNearby(Mathf.mod(dir + 1, 4)); + boolean ac = a != null && !(a.block().instantTransfer && source.block().instantTransfer) && + a.block().acceptItem(item, a, dest); + boolean bc = b != null && !(b.block().instantTransfer && source.block().instantTransfer) && + b.block().acceptItem(item, b, dest); + + if(ac && !bc){ + to = a; + }else if(bc && !ac){ + to = b; + }else if(!bc){ + return null; + }else{ + if(dest.getDump() == 0){ + to = a; + if(flip) + dest.setDump((byte) 1); + }else{ + to = b; + if(flip) + dest.setDump((byte) 0); + } + } + } + + return to; + } + + @Override + public void buildTable(Tile tile, Table table){ + SorterEntity entity = tile.entity(); + buildItemTable(table, () -> entity.sortItem, item -> CallBlocks.setSorterItem(null, tile, item)); + } + + @Override + public TileEntity getEntity(){ + return new SorterEntity(); + } + + public static class SorterEntity extends TileEntity{ + public Item sortItem = Items.tungsten; + + @Override + public void write(DataOutputStream stream) throws IOException{ + stream.writeByte(sortItem.id); + } + + @Override + public void read(DataInputStream stream) throws IOException{ + sortItem = Item.all().get(stream.readByte()); + } + } } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/Splitter.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Splitter.java index fd5c57bf19..1edf61002d 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Splitter.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Splitter.java @@ -35,9 +35,9 @@ public class Splitter extends Block{ Tile getTileTarget(Item item, Tile tile, Tile source, boolean flip){ Array proximity = tile.entity.proximity(); int counter = tile.getDump(); - for (int i = 0; i < proximity.size; i++) { + for(int i = 0; i < proximity.size; i++){ Tile other = proximity.get((i + counter) % proximity.size); - if(flip) tile.setDump((byte)((tile.getDump() + 1) % proximity.size)); + if(flip) tile.setDump((byte) ((tile.getDump() + 1) % proximity.size)); if(other != source && !(source.block().instantTransfer && other.block().instantTransfer) && !(other.block() instanceof Splitter) && other.block().acceptItem(item, other, Edges.getFacingEdge(tile, other))){ return other; diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/TunnelConduit.java b/core/src/io/anuke/mindustry/world/blocks/distribution/TunnelConduit.java index a342a41efc..8639a99da9 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/TunnelConduit.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/TunnelConduit.java @@ -7,11 +7,11 @@ import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.LiquidBlock; import io.anuke.ucore.graphics.Draw; -public class TunnelConduit extends LiquidBlock { +public class TunnelConduit extends LiquidBlock{ protected int maxdist = 3; protected float speed = 53; - protected TunnelConduit(String name) { + protected TunnelConduit(String name){ super(name); rotate = true; solid = true; @@ -21,7 +21,7 @@ public class TunnelConduit extends LiquidBlock { } @Override - public void setBars() { + public void setBars(){ super.setBars(); bars.remove(BarType.liquid); } @@ -37,39 +37,39 @@ public class TunnelConduit extends LiquidBlock { } @Override - public void handleLiquid(Tile tile, Tile source, Liquid liquid, float amount) { + public void handleLiquid(Tile tile, Tile source, Liquid liquid, float amount){ Tile tunnel = getDestTunnel(tile); - if (tunnel == null) return; + if(tunnel == null) return; Tile to = tunnel.getNearby(tunnel.getRotation()); - if (to == null || !(to.block().hasLiquids)) return; + if(to == null || !(to.block().hasLiquids)) return; - if (to.block().acceptLiquid(to, tunnel, liquid, amount)) + if(to.block().acceptLiquid(to, tunnel, liquid, amount)) to.block().handleLiquid(to, tunnel, liquid, amount); } @Override - public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount) { + public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){ int rot = source.relativeTo(tile.x, tile.y); - if (rot != (tile.getRotation() + 2) % 4) return false; + if(rot != (tile.getRotation() + 2) % 4) return false; Tile tunnel = getDestTunnel(tile); - if (tunnel != null) { + if(tunnel != null){ Tile to = tunnel.getNearby(tunnel.getRotation()); return to != null && (to.block().hasLiquids) && (to.block()).acceptLiquid(to, tunnel, liquid, amount); - } else { + }else{ return false; } } - Tile getDestTunnel(Tile tile) { + Tile getDestTunnel(Tile tile){ Tile dest = tile; int rel = (tile.getRotation() + 2) % 4; - for (int i = 0; i < maxdist; i++) { - if (dest == null) return null; + for(int i = 0; i < maxdist; i++){ + if(dest == null) return null; dest = dest.getNearby(rel); - if (dest != null && dest.block() instanceof TunnelConduit && dest.getRotation() == rel - && dest.getNearby(rel) != null) { + if(dest != null && dest.block() instanceof TunnelConduit && dest.getRotation() == rel + && dest.getNearby(rel) != null){ return dest; } } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/WarpGate.java b/core/src/io/anuke/mindustry/world/blocks/distribution/WarpGate.java index 4a1b8e849e..0ed512555b 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/WarpGate.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/WarpGate.java @@ -36,133 +36,136 @@ import static io.anuke.mindustry.Vars.tilesize; //TODO implement public class WarpGate extends PowerBlock{ - public static final Color[] colorArray = {Color.ROYAL, Color.ORANGE, Color.SCARLET, Color.LIME, - Color.PURPLE, Color.GOLD, Color.PINK, Color.LIGHT_GRAY}; - public static final int colors = colorArray.length; + public static final Color[] colorArray = {Color.ROYAL, Color.ORANGE, Color.SCARLET, Color.LIME, + Color.PURPLE, Color.GOLD, Color.PINK, Color.LIGHT_GRAY}; + public static final int colors = colorArray.length; + private static ObjectSet[] teleporters = new ObjectSet[colors]; + private static Color color = new Color(); + private static byte lastColor = 0; - protected int timerTeleport = timers++; + static{ + for(int i = 0; i < colors; i++){ + teleporters[i] = new ObjectSet<>(); + } + } - private static ObjectSet[] teleporters = new ObjectSet[colors]; - private static Color color = new Color(); - private static byte lastColor = 0; + protected int timerTeleport = timers++; + protected float warmupTime = 60f; + //time between teleports + protected float teleportMax = 400f; + protected float teleportLiquidUse = 0.3f; + protected Liquid inputLiquid = Liquids.cryofluid; + protected Effect activateEffect = BlockFx.teleportActivate; + protected Effect teleportEffect = BlockFx.teleport; + protected Effect teleportOutEffect = BlockFx.teleportOut; + private Array removal = new Array<>(); + private Array returns = new Array<>(); - private Array removal = new Array<>(); - private Array returns = new Array<>(); + public WarpGate(String name){ + super(name); + update = true; + solid = true; + health = 80; + powerCapacity = 300f; + size = 3; + itemCapacity = 100; + hasLiquids = true; + hasItems = true; + liquidCapacity = 100f; + configurable = true; + } - protected float warmupTime = 60f; - //time between teleports - protected float teleportMax = 400f; - protected float teleportLiquidUse = 0.3f; - protected Liquid inputLiquid = Liquids.cryofluid; - protected Effect activateEffect = BlockFx.teleportActivate; - protected Effect teleportEffect = BlockFx.teleport; - protected Effect teleportOutEffect = BlockFx.teleportOut; + @Remote(targets = Loc.both, called = Loc.both, in = In.blocks, forward = true) + public static void setTeleporterColor(Player player, Tile tile, byte color){ + TeleporterEntity entity = tile.entity(); + entity.color = color; + } - static{ - for(int i = 0; i < colors; i ++){ - teleporters[i] = new ObjectSet<>(); - } - } - - public WarpGate(String name) { - super(name); - update = true; - solid = true; - health = 80; - powerCapacity = 300f; - size = 3; - itemCapacity = 100; - hasLiquids = true; - hasItems = true; - liquidCapacity = 100f; - configurable = true; - } + @Override + public void setStats(){ + super.setStats(); + } - @Override - public void setStats(){ - super.setStats(); - } + @Override + public void placed(Tile tile){ + CallBlocks.setTeleporterColor(null, tile, lastColor); + } - @Override - public void placed(Tile tile){ - CallBlocks.setTeleporterColor(null, tile, lastColor); - } - - @Override - public void draw(Tile tile){ - super.draw(tile); + @Override + public void draw(Tile tile){ + super.draw(tile); - TeleporterEntity entity = tile.entity(); - float time = entity.time; - float rad = entity.activeScl; + TeleporterEntity entity = tile.entity(); + float time = entity.time; + float rad = entity.activeScl; - if(entity.liquidLackScl > 0.01f){ - Graphics.setAdditiveBlending(); - Draw.color(1f, 0.3f, 0.3f, 0.4f * entity.liquidLackScl); - Fill.square(tile.drawx(), tile.drawy(), size * tilesize); - Graphics.setNormalBlending(); - } + if(entity.liquidLackScl > 0.01f){ + Graphics.setAdditiveBlending(); + Draw.color(1f, 0.3f, 0.3f, 0.4f * entity.liquidLackScl); + Fill.square(tile.drawx(), tile.drawy(), size * tilesize); + Graphics.setNormalBlending(); + } - Draw.color(getColor(tile, 0)); - Draw.rect(name+"-top", tile.drawx(), tile.drawy()); - Draw.reset(); + Draw.color(getColor(tile, 0)); + Draw.rect(name + "-top", tile.drawx(), tile.drawy()); + Draw.reset(); - if(rad <= 0.0001f) return; + if(rad <= 0.0001f) return; - Draw.color(getColor(tile, 0)); + Draw.color(getColor(tile, 0)); - Fill.circle(tile.drawx(), tile.drawy(), rad*(7f + Mathf.absin(time+55, 8f, 1f))); + Fill.circle(tile.drawx(), tile.drawy(), rad * (7f + Mathf.absin(time + 55, 8f, 1f))); - Draw.color(getColor(tile, -1)); + Draw.color(getColor(tile, -1)); - Fill.circle(tile.drawx(), tile.drawy(), rad*(2f + Mathf.absin(time, 7f, 3f))); + Fill.circle(tile.drawx(), tile.drawy(), rad * (2f + Mathf.absin(time, 7f, 3f))); - for(int i = 0; i < 11; i ++){ - Lines.swirl(tile.drawx(), tile.drawy(), - rad*(2f + i/3f + Mathf.sin(time - i *75, 20f + i, 3f)), - 0.3f + Mathf.sin(time + i *33, 10f + i, 0.1f), - time * (1f + Mathf.randomSeedRange(i + 1, 1f)) + Mathf.randomSeedRange(i, 360f)); - } + for(int i = 0; i < 11; i++){ + Lines.swirl(tile.drawx(), tile.drawy(), + rad * (2f + i / 3f + Mathf.sin(time - i * 75, 20f + i, 3f)), + 0.3f + Mathf.sin(time + i * 33, 10f + i, 0.1f), + time * (1f + Mathf.randomSeedRange(i + 1, 1f)) + Mathf.randomSeedRange(i, 360f)); + } - Draw.color(getColor(tile, 1)); + Draw.color(getColor(tile, 1)); - Lines.stroke(2f); - Lines.circle(tile.drawx(), tile.drawy(), rad*(7f + Mathf.absin(time+55, 8f, 1f))); - Lines.stroke(1f); + Lines.stroke(2f); + Lines.circle(tile.drawx(), tile.drawy(), rad * (7f + Mathf.absin(time + 55, 8f, 1f))); + Lines.stroke(1f); - for(int i = 0; i < 11; i ++){ - Lines.swirl(tile.drawx(), tile.drawy(), - rad*(3f + i/3f + Mathf.sin(time + i *93, 20f + i, 3f)), - 0.2f + Mathf.sin(time + i *33, 10f + i, 0.1f), - time * (1f + Mathf.randomSeedRange(i + 1, 1f)) + Mathf.randomSeedRange(i, 360f)); - } + for(int i = 0; i < 11; i++){ + Lines.swirl(tile.drawx(), tile.drawy(), + rad * (3f + i / 3f + Mathf.sin(time + i * 93, 20f + i, 3f)), + 0.2f + Mathf.sin(time + i * 33, 10f + i, 0.1f), + time * (1f + Mathf.randomSeedRange(i + 1, 1f)) + Mathf.randomSeedRange(i, 360f)); + } - Draw.reset(); - } - - @Override - public void update(Tile tile){ - TeleporterEntity entity = tile.entity(); + Draw.reset(); + } - teleporters[entity.color].add(tile); + @Override + public void update(Tile tile){ + TeleporterEntity entity = tile.entity(); - if(entity.items.total() > 0){ - tryDump(tile); - } + teleporters[entity.color].add(tile); - if(!entity.active){ - entity.activeScl = Mathf.lerpDelta(entity.activeScl, 0f, 0.01f); + if(entity.items.total() > 0){ + tryDump(tile); + } - if(entity.power.amount >= powerCapacity){ - Color resultColor = new Color(); - resultColor.set(getColor(tile, 0)); + if(!entity.active){ + entity.activeScl = Mathf.lerpDelta(entity.activeScl, 0f, 0.01f); - entity.active = true; - entity.power.amount = 0f; - Effects.effect(activateEffect, resultColor, tile.drawx(), tile.drawy()); - } - }else{ - entity.activeScl = Mathf.lerpDelta(entity.activeScl, 1f, 0.015f); + if(entity.power.amount >= powerCapacity){ + Color resultColor = new Color(); + resultColor.set(getColor(tile, 0)); + + entity.active = true; + entity.power.amount = 0f; + Effects.effect(activateEffect, resultColor, tile.drawx(), tile.drawy()); + } + }else{ + entity.activeScl = Mathf.lerpDelta(entity.activeScl, 1f, 0.015f); /* if (entity.power.amount >= powerUsed) { @@ -185,26 +188,26 @@ public class WarpGate extends PowerBlock{ entity.liquidLackScl = Mathf.lerpDelta(entity.liquidLackScl, 1f, 0.1f); }*/ - if(entity.liquidLackScl >= 0.999f){ - catastrophicFailure(tile); - } + if(entity.liquidLackScl >= 0.999f){ + catastrophicFailure(tile); + } - //TODO draw warning info! + //TODO draw warning info! - if (entity.teleporting) { - entity.speedScl = Mathf.lerpDelta(entity.speedScl, 2f, 0.01f); - //liquidUsed = Math.min(liquidCapacity, teleportLiquidUse * Timers.delta()); + if(entity.teleporting){ + entity.speedScl = Mathf.lerpDelta(entity.speedScl, 2f, 0.01f); + //liquidUsed = Math.min(liquidCapacity, teleportLiquidUse * Timers.delta()); - //if (entity.liquids.amount >= liquidUsed) { - // entity.liquids.amount -= liquidUsed; - //} else { - catastrophicFailure(tile); - //} - } else { - entity.speedScl = Mathf.lerpDelta(entity.speedScl, 1f, 0.04f); - } + //if (entity.liquids.amount >= liquidUsed) { + // entity.liquids.amount -= liquidUsed; + //} else { + catastrophicFailure(tile); + //} + }else{ + entity.speedScl = Mathf.lerpDelta(entity.speedScl, 1f, 0.04f); + } - entity.time += Timers.delta() * entity.speedScl; + entity.time += Timers.delta() * entity.speedScl; /* if (!entity.teleporting && entity.items.total() >= itemCapacity && entity.power.amount >= powerCapacity - 0.01f - powerUse && entity.timer.get(timerTeleport, teleportMax)) { @@ -235,138 +238,132 @@ public class WarpGate extends PowerBlock{ entity.teleporting = false; }); }*/ - } - } - - @Override - public void buildTable(Tile tile, Table table){ - TeleporterEntity entity = tile.entity(); + } + } - //TODO call event for change + @Override + public void buildTable(Tile tile, Table table){ + TeleporterEntity entity = tile.entity(); - ButtonGroup group = new ButtonGroup<>(); - Table cont = new Table(); + //TODO call event for change - for(int i = 0; i < colors; i ++){ - final int f = i; - ImageButton button = cont.addImageButton("white", "toggle", 24, () -> { - lastColor = (byte)f; - CallBlocks.setTeleporterColor(null, tile, (byte)f); - }).size(34, 38).padBottom(-5.1f).group(group).get(); - button.getStyle().imageUpColor = colorArray[f]; - button.setChecked(entity.color == f); + ButtonGroup group = new ButtonGroup<>(); + Table cont = new Table(); - if(i%4 == 3){ - cont.row(); - } - } + for(int i = 0; i < colors; i++){ + final int f = i; + ImageButton button = cont.addImageButton("white", "toggle", 24, () -> { + lastColor = (byte) f; + CallBlocks.setTeleporterColor(null, tile, (byte) f); + }).size(34, 38).padBottom(-5.1f).group(group).get(); + button.getStyle().imageUpColor = colorArray[f]; + button.setChecked(entity.color == f); - table.add(cont); - } - - @Override - public boolean acceptItem(Item item, Tile tile, Tile source){ - TeleporterEntity entity = tile.entity(); - return entity.items.total() < itemCapacity; - } - - @Override - public TileEntity getEntity(){ - return new TeleporterEntity(); - } + if(i % 4 == 3){ + cont.row(); + } + } - @Override - public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount) { - return super.acceptLiquid(tile, source, liquid, amount) && liquid == inputLiquid; - } + table.add(cont); + } - @Override - public void onDestroyed(Tile tile) { - super.onDestroyed(tile); + @Override + public boolean acceptItem(Item item, Tile tile, Tile source){ + TeleporterEntity entity = tile.entity(); + return entity.items.total() < itemCapacity; + } - TeleporterEntity entity = tile.entity(); + @Override + public TileEntity getEntity(){ + return new TeleporterEntity(); + } - if(entity.activeScl < 0.5f) return; + @Override + public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){ + return super.acceptLiquid(tile, source, liquid, amount) && liquid == inputLiquid; + } - //TODO catastrophic failure - } + @Override + public void onDestroyed(Tile tile){ + super.onDestroyed(tile); - private void catastrophicFailure(Tile tile){ - tile.entity.damage(tile.entity.health + 1); - //TODO fail gloriously - } + TeleporterEntity entity = tile.entity(); - private Color getColor(Tile tile, int shift){ - TeleporterEntity entity = tile.entity(); + if(entity.activeScl < 0.5f) return; - Color target = colorArray[entity.color]; - float ss = 0.5f; - float bs = 0.2f; + //TODO catastrophic failure + } - return Hue.shift(Hue.multiply(color.set(target), 1, ss), 2, shift * bs + (entity.speedScl - 1f)/3f); - } - - private Array findLinks(Tile tile){ - TeleporterEntity entity = tile.entity(); - - removal.clear(); - returns.clear(); - - for(Tile other : teleporters[entity.color]){ - if(other != tile){ - if(other.block() instanceof WarpGate){ - TeleporterEntity oe = other.entity(); - if(!oe.active) continue; - if(oe.color != entity.color){ - removal.add(other); - }else if(other.entity.items.total() == 0){ - returns.add(other); - } - }else{ - removal.add(other); - } - } - } + private void catastrophicFailure(Tile tile){ + tile.entity.damage(tile.entity.health + 1); + //TODO fail gloriously + } - for(Tile remove : removal) { - teleporters[entity.color].remove(remove); - } - - return returns; - } + private Color getColor(Tile tile, int shift){ + TeleporterEntity entity = tile.entity(); - @Remote(targets = Loc.both, called = Loc.both, in = In.blocks, forward = true) - public static void setTeleporterColor(Player player, Tile tile, byte color){ - TeleporterEntity entity = tile.entity(); - entity.color = color; - } + Color target = colorArray[entity.color]; + float ss = 0.5f; + float bs = 0.2f; - public static class TeleporterEntity extends TileEntity{ - public byte color = 0; - public boolean teleporting; - public boolean active; - public float activeScl = 0f; - public float speedScl = 1f; - public float powerLackScl, liquidLackScl; - public float time; - - @Override - public void write(DataOutputStream stream) throws IOException{ - stream.writeByte(color); - stream.writeBoolean(active); - stream.writeFloat(activeScl); - stream.writeFloat(speedScl); - stream.writeFloat(powerLackScl); - } - - @Override - public void read(DataInputStream stream) throws IOException{ - color = stream.readByte(); - active = stream.readBoolean(); - activeScl = stream.readFloat(); - speedScl = stream.readFloat(); - powerLackScl = stream.readFloat(); - } - } + return Hue.shift(Hue.multiply(color.set(target), 1, ss), 2, shift * bs + (entity.speedScl - 1f) / 3f); + } + + private Array findLinks(Tile tile){ + TeleporterEntity entity = tile.entity(); + + removal.clear(); + returns.clear(); + + for(Tile other : teleporters[entity.color]){ + if(other != tile){ + if(other.block() instanceof WarpGate){ + TeleporterEntity oe = other.entity(); + if(!oe.active) continue; + if(oe.color != entity.color){ + removal.add(other); + }else if(other.entity.items.total() == 0){ + returns.add(other); + } + }else{ + removal.add(other); + } + } + } + + for(Tile remove : removal){ + teleporters[entity.color].remove(remove); + } + + return returns; + } + + public static class TeleporterEntity extends TileEntity{ + public byte color = 0; + public boolean teleporting; + public boolean active; + public float activeScl = 0f; + public float speedScl = 1f; + public float powerLackScl, liquidLackScl; + public float time; + + @Override + public void write(DataOutputStream stream) throws IOException{ + stream.writeByte(color); + stream.writeBoolean(active); + stream.writeFloat(activeScl); + stream.writeFloat(speedScl); + stream.writeFloat(powerLackScl); + } + + @Override + public void read(DataInputStream stream) throws IOException{ + color = stream.readByte(); + active = stream.readBoolean(); + activeScl = stream.readFloat(); + speedScl = stream.readFloat(); + powerLackScl = stream.readFloat(); + } + } } diff --git a/core/src/io/anuke/mindustry/world/blocks/effect/EffectCore.java b/core/src/io/anuke/mindustry/world/blocks/effect/EffectCore.java index adf1a243df..9604d28d8e 100644 --- a/core/src/io/anuke/mindustry/world/blocks/effect/EffectCore.java +++ b/core/src/io/anuke/mindustry/world/blocks/effect/EffectCore.java @@ -11,14 +11,14 @@ import static io.anuke.mindustry.Vars.tilesize; public abstract class EffectCore extends Block{ protected int range = 7; - public EffectCore(String name) { + public EffectCore(String name){ super(name); update = true; solid = true; } @Override - public void drawSelect(Tile tile) { + public void drawSelect(Tile tile){ Draw.color(Palette.accent); Lines.dashCircle(tile.drawx(), tile.drawy(), range * tilesize); Draw.reset(); diff --git a/core/src/io/anuke/mindustry/world/blocks/effect/MendingCore.java b/core/src/io/anuke/mindustry/world/blocks/effect/MendingCore.java index 571cd4112d..897690610a 100644 --- a/core/src/io/anuke/mindustry/world/blocks/effect/MendingCore.java +++ b/core/src/io/anuke/mindustry/world/blocks/effect/MendingCore.java @@ -6,19 +6,21 @@ import io.anuke.ucore.util.Mathf; import static io.anuke.mindustry.Vars.world; -public class MendingCore extends EffectCore { - /**Mending speed as a percentage of block health, per frame.*/ +public class MendingCore extends EffectCore{ + /** + * Mending speed as a percentage of block health, per frame. + */ protected float mendSpeed = 0.8f; - public MendingCore(String name) { + public MendingCore(String name){ super(name); } @Override - public void update(Tile tile) { + public void update(Tile tile){ - for (int dx = Math.max(-range + tile.x, 0); dx <= Math.min(range + tile.y, world.width() - 1); dx++) { - for (int dy = Math.max(-range + tile.y, 0); dy <= Math.min(range + tile.y, world.height() - 1); dy++) { + for(int dx = Math.max(-range + tile.x, 0); dx <= Math.min(range + tile.y, world.width() - 1); dx++){ + for(int dy = Math.max(-range + tile.y, 0); dy <= Math.min(range + tile.y, world.height() - 1); dy++){ Tile other = world.tile(dx, dy); if(other.entity != null){ diff --git a/core/src/io/anuke/mindustry/world/blocks/logic/LogicBlock.java b/core/src/io/anuke/mindustry/world/blocks/logic/LogicBlock.java index 2e1a11c3b1..ccb78c365c 100644 --- a/core/src/io/anuke/mindustry/world/blocks/logic/LogicBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/logic/LogicBlock.java @@ -4,7 +4,7 @@ import io.anuke.mindustry.world.Block; public class LogicBlock extends Block{ - public LogicBlock(String name) { + public LogicBlock(String name){ super(name); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/power/BurnerGenerator.java b/core/src/io/anuke/mindustry/world/blocks/power/BurnerGenerator.java index cee1109845..91051e8a85 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/BurnerGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/BurnerGenerator.java @@ -3,19 +3,19 @@ package io.anuke.mindustry.world.blocks.power; import io.anuke.mindustry.type.Item; import io.anuke.mindustry.type.Liquid; -public class BurnerGenerator extends ItemLiquidGenerator { +public class BurnerGenerator extends ItemLiquidGenerator{ - public BurnerGenerator(String name) { + public BurnerGenerator(String name){ super(name); } @Override - protected float getLiquidEfficiency(Liquid liquid) { + protected float getLiquidEfficiency(Liquid liquid){ return liquid.flammability; } @Override - protected float getItemEfficiency(Item item) { + protected float getItemEfficiency(Item item){ return item.flammability; } } diff --git a/core/src/io/anuke/mindustry/world/blocks/power/DecayGenerator.java b/core/src/io/anuke/mindustry/world/blocks/power/DecayGenerator.java index f1481bfe23..225efd98c5 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/DecayGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/DecayGenerator.java @@ -2,16 +2,16 @@ package io.anuke.mindustry.world.blocks.power; import io.anuke.mindustry.type.Item; -public class DecayGenerator extends ItemGenerator { +public class DecayGenerator extends ItemGenerator{ - public DecayGenerator(String name) { + public DecayGenerator(String name){ super(name); hasItems = true; hasLiquids = false; } @Override - protected float getItemEfficiency(Item item) { + protected float getItemEfficiency(Item item){ return item.radioactivity; } } diff --git a/core/src/io/anuke/mindustry/world/blocks/power/FusionReactor.java b/core/src/io/anuke/mindustry/world/blocks/power/FusionReactor.java index b4c36a3f5c..d31f5a5ed7 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/FusionReactor.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/FusionReactor.java @@ -12,7 +12,7 @@ import io.anuke.ucore.core.Timers; import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.util.Mathf; -public class FusionReactor extends PowerGenerator { +public class FusionReactor extends PowerGenerator{ protected int plasmas = 4; protected float maxPowerProduced = 2f; protected float warmupSpeed = 0.001f; @@ -20,7 +20,7 @@ public class FusionReactor extends PowerGenerator { protected Color plasma1 = Color.valueOf("ffd06b"), plasma2 = Color.valueOf("ff361b"); protected Color ind1 = Color.valueOf("858585"), ind2 = Color.valueOf("fea080"); - public FusionReactor(String name) { + public FusionReactor(String name){ super(name); hasPower = true; hasLiquids = true; @@ -30,7 +30,7 @@ public class FusionReactor extends PowerGenerator { } @Override - public void setStats() { + public void setStats(){ super.setStats(); stats.add(BlockStat.maxPowerGeneration, maxPowerProduced * 60f, StatUnit.powerSecond); @@ -54,17 +54,17 @@ public class FusionReactor extends PowerGenerator { } @Override - public float handleDamage(Tile tile, float amount) { + public float handleDamage(Tile tile, float amount){ FusionReactorEntity entity = tile.entity(); if(entity.warmup < 0.4f) return amount; - float healthFract = tile.entity.health/health; + float healthFract = tile.entity.health / health; //5% chance to explode when hit at <50% HP with a normal bullet if(amount > 5f && healthFract <= 0.5f && Mathf.chance(0.05)){ return health; - //10% chance to explode when hit at <25% HP with a powerful bullet + //10% chance to explode when hit at <25% HP with a powerful bullet }else if(amount > 8f && healthFract <= 0.2f && Mathf.chance(0.1)){ return health; } @@ -73,19 +73,19 @@ public class FusionReactor extends PowerGenerator { } @Override - public void draw(Tile tile) { + public void draw(Tile tile){ FusionReactorEntity entity = tile.entity(); Draw.rect(name + "-bottom", tile.drawx(), tile.drawy()); Graphics.setAdditiveBlending(); - for(int i = 0; i < plasmas; i ++){ - float r = 29f + Mathf.absin(Timers.time(), 2f + i*1f, 5f - i*0.5f); + for(int i = 0; i < plasmas; i++){ + float r = 29f + Mathf.absin(Timers.time(), 2f + i * 1f, 5f - i * 0.5f); - Draw.color(plasma1, plasma2, (float)i/plasmas); - Draw.alpha((0.3f + Mathf.absin(Timers.time(), 2f+i*2f, 0.3f+i*0.05f)) * entity.warmup); - Draw.rect(name + "-plasma-" + i, tile.drawx(), tile.drawy(), r, r, Timers.time()*(12+i*6f) * entity.warmup); + Draw.color(plasma1, plasma2, (float) i / plasmas); + Draw.alpha((0.3f + Mathf.absin(Timers.time(), 2f + i * 2f, 0.3f + i * 0.05f)) * entity.warmup); + Draw.rect(name + "-plasma-" + i, tile.drawx(), tile.drawy(), r, r, Timers.time() * (12 + i * 6f) * entity.warmup); } Draw.color(); @@ -96,24 +96,24 @@ public class FusionReactor extends PowerGenerator { Draw.rect(name + "-top", tile.drawx(), tile.drawy()); - Draw.color(ind1, ind2, entity.warmup + Mathf.absin(entity.totalProgress, 3f, entity.warmup*0.5f)); + Draw.color(ind1, ind2, entity.warmup + Mathf.absin(entity.totalProgress, 3f, entity.warmup * 0.5f)); Draw.rect(name + "-light", tile.drawx(), tile.drawy()); Draw.color(); } @Override - public TextureRegion[] getIcon() { + public TextureRegion[] getIcon(){ return new TextureRegion[]{Draw.region(name + "-bottom"), Draw.region(name), Draw.region(name + "-top")}; } @Override - public TileEntity getEntity() { + public TileEntity getEntity(){ return new FusionReactorEntity(); } @Override - public void onDestroyed(Tile tile) { + public void onDestroyed(Tile tile){ super.onDestroyed(tile); FusionReactorEntity entity = tile.entity(); diff --git a/core/src/io/anuke/mindustry/world/blocks/power/ItemGenerator.java b/core/src/io/anuke/mindustry/world/blocks/power/ItemGenerator.java index 872f03156e..0e5ba6d68a 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/ItemGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/ItemGenerator.java @@ -23,113 +23,113 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.tilesize; -public abstract class ItemGenerator extends PowerGenerator { - protected float minItemEfficiency = 0.2f; - protected float powerOutput; - protected float itemDuration = 70f; - protected Effect generateEffect = BlockFx.generatespark, explodeEffect = - BlockFx.generatespark; - protected Color heatColor = Color.valueOf("ff9b59"); - protected TextureRegion topRegion; +public abstract class ItemGenerator extends PowerGenerator{ + protected float minItemEfficiency = 0.2f; + protected float powerOutput; + protected float itemDuration = 70f; + protected Effect generateEffect = BlockFx.generatespark, explodeEffect = + BlockFx.generatespark; + protected Color heatColor = Color.valueOf("ff9b59"); + protected TextureRegion topRegion; - public ItemGenerator(String name) { - super(name); - itemCapacity = 20; - hasItems = true; + public ItemGenerator(String name){ + super(name); + itemCapacity = 20; + hasItems = true; - consumes.add(new ConsumeItemFilter(item -> getItemEfficiency(item) >= minItemEfficiency)).update(false).optional(true); - } + consumes.add(new ConsumeItemFilter(item -> getItemEfficiency(item) >= minItemEfficiency)).update(false).optional(true); + } - @Override - public void load() { - super.load(); - topRegion = Draw.region(name + "-top"); - } + @Override + public void load(){ + super.load(); + topRegion = Draw.region(name + "-top"); + } - @Override - public void setStats() { - super.setStats(); + @Override + public void setStats(){ + super.setStats(); - stats.add(BlockStat.maxPowerGeneration, powerOutput * 60f, StatUnit.powerSecond); - } + stats.add(BlockStat.maxPowerGeneration, powerOutput * 60f, StatUnit.powerSecond); + } - @Override - public void setBars(){ - super.setBars(); - bars.replace(new BlockBar(BarType.inventory, true, tile -> (float)tile.entity.items.total() / itemCapacity)); - } - - @Override - public void draw(Tile tile){ - super.draw(tile); + @Override + public void setBars(){ + super.setBars(); + bars.replace(new BlockBar(BarType.inventory, true, tile -> (float) tile.entity.items.total() / itemCapacity)); + } - GeneratorEntity entity = tile.entity(); - - if(entity.generateTime > 0){ - Draw.color(heatColor); - float alpha = (entity.items.total() > 0 ? 1f : Mathf.clamp(entity.generateTime)); - alpha = alpha * 0.7f + Mathf.absin(Timers.time(), 12f, 0.3f) * alpha; - Draw.alpha(alpha); - Draw.rect(topRegion, tile.drawx(), tile.drawy()); - Draw.reset(); - } - } - - @Override - public boolean acceptItem(Item item, Tile tile, Tile source){ - return getItemEfficiency(item) >= minItemEfficiency && tile.entity.items.total() < itemCapacity; - } - - @Override - public void update(Tile tile){ - ItemGeneratorEntity entity = tile.entity(); - - float maxPower = Math.min(powerCapacity - entity.power.amount, powerOutput * Timers.delta()) * entity.efficiency; - float mfract = maxPower/(powerOutput); - - if(entity.generateTime > 0f){ - entity.generateTime -= 1f/itemDuration*mfract; - entity.power.amount += maxPower; - entity.generateTime = Mathf.clamp(entity.generateTime); + @Override + public void draw(Tile tile){ + super.draw(tile); - if(Mathf.chance(Timers.delta() * 0.06 * Mathf.clamp(entity.explosiveness - 0.25f))){ - entity.damage(Mathf.random(8f)); - Effects.effect(explodeEffect, tile.worldx() + Mathf.range(size * tilesize/2f), tile.worldy() + Mathf.range(size * tilesize/2f)); - } - } + GeneratorEntity entity = tile.entity(); - if (entity.generateTime <= 0f && entity.items.total() > 0) { - Effects.effect(generateEffect, tile.worldx() + Mathf.range(3f), tile.worldy() + Mathf.range(3f)); - Item item = entity.items.take(); - entity.efficiency = getItemEfficiency(item); - entity.explosiveness = item.explosiveness; - entity.generateTime = 1f; - } - - distributePower(tile); - - } + if(entity.generateTime > 0){ + Draw.color(heatColor); + float alpha = (entity.items.total() > 0 ? 1f : Mathf.clamp(entity.generateTime)); + alpha = alpha * 0.7f + Mathf.absin(Timers.time(), 12f, 0.3f) * alpha; + Draw.alpha(alpha); + Draw.rect(topRegion, tile.drawx(), tile.drawy()); + Draw.reset(); + } + } - protected abstract float getItemEfficiency(Item item); + @Override + public boolean acceptItem(Item item, Tile tile, Tile source){ + return getItemEfficiency(item) >= minItemEfficiency && tile.entity.items.total() < itemCapacity; + } - @Override - public TileEntity getEntity() { - return new ItemGeneratorEntity(); - } + @Override + public void update(Tile tile){ + ItemGeneratorEntity entity = tile.entity(); - public static class ItemGeneratorEntity extends GeneratorEntity{ - public float efficiency; - public float explosiveness; + float maxPower = Math.min(powerCapacity - entity.power.amount, powerOutput * Timers.delta()) * entity.efficiency; + float mfract = maxPower / (powerOutput); - @Override - public void write(DataOutputStream stream) throws IOException { - stream.writeFloat(efficiency); - } + if(entity.generateTime > 0f){ + entity.generateTime -= 1f / itemDuration * mfract; + entity.power.amount += maxPower; + entity.generateTime = Mathf.clamp(entity.generateTime); - @Override - public void read(DataInputStream stream) throws IOException { - efficiency = stream.readFloat(); - } - } + if(Mathf.chance(Timers.delta() * 0.06 * Mathf.clamp(entity.explosiveness - 0.25f))){ + entity.damage(Mathf.random(8f)); + Effects.effect(explodeEffect, tile.worldx() + Mathf.range(size * tilesize / 2f), tile.worldy() + Mathf.range(size * tilesize / 2f)); + } + } + + if(entity.generateTime <= 0f && entity.items.total() > 0){ + Effects.effect(generateEffect, tile.worldx() + Mathf.range(3f), tile.worldy() + Mathf.range(3f)); + Item item = entity.items.take(); + entity.efficiency = getItemEfficiency(item); + entity.explosiveness = item.explosiveness; + entity.generateTime = 1f; + } + + distributePower(tile); + + } + + protected abstract float getItemEfficiency(Item item); + + @Override + public TileEntity getEntity(){ + return new ItemGeneratorEntity(); + } + + public static class ItemGeneratorEntity extends GeneratorEntity{ + public float efficiency; + public float explosiveness; + + @Override + public void write(DataOutputStream stream) throws IOException{ + stream.writeFloat(efficiency); + } + + @Override + public void read(DataInputStream stream) throws IOException{ + efficiency = stream.readFloat(); + } + } } diff --git a/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java b/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java index 9e1efada0d..60fc48d267 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java @@ -12,13 +12,15 @@ import io.anuke.ucore.util.Mathf; import static io.anuke.mindustry.Vars.tilesize; -public abstract class ItemLiquidGenerator extends ItemGenerator { +public abstract class ItemLiquidGenerator extends ItemGenerator{ protected float minLiquidEfficiency = 0.2f; protected float powerPerLiquid = 0.13f; - /**Maximum liquid used per frame.*/ + /** + * Maximum liquid used per frame. + */ protected float maxLiquidGenerate = 0.4f; - public ItemLiquidGenerator(String name) { + public ItemLiquidGenerator(String name){ super(name); hasLiquids = true; liquidCapacity = 10f; @@ -31,7 +33,7 @@ public abstract class ItemLiquidGenerator extends ItemGenerator { ItemGeneratorEntity entity = tile.entity(); Liquid liquid = null; - for (Liquid other : Liquid.all()){ + for(Liquid other : Liquid.all()){ if(entity.liquids.get(other) >= 0.001f && getLiquidEfficiency(other) >= minLiquidEfficiency){ liquid = other; break; @@ -40,9 +42,9 @@ public abstract class ItemLiquidGenerator extends ItemGenerator { //liquid takes priority over solids if(liquid != null && entity.liquids.get(liquid) >= 0.001f && entity.cons.valid()){ - float powerPerLiquid = getLiquidEfficiency(liquid)*this.powerPerLiquid; + float powerPerLiquid = getLiquidEfficiency(liquid) * this.powerPerLiquid; float used = Math.min(entity.liquids.get(liquid), maxLiquidGenerate * Timers.delta()); - used = Math.min(used, (powerCapacity - entity.power.amount)/powerPerLiquid); + used = Math.min(used, (powerCapacity - entity.power.amount) / powerPerLiquid); entity.liquids.remove(liquid, used); entity.power.amount += used * powerPerLiquid; @@ -50,23 +52,23 @@ public abstract class ItemLiquidGenerator extends ItemGenerator { if(used > 0.001f && Mathf.chance(0.05 * Timers.delta())){ Effects.effect(generateEffect, tile.drawx() + Mathf.range(3f), tile.drawy() + Mathf.range(3f)); } - }else if (entity.cons.valid()){ + }else if(entity.cons.valid()){ float maxPower = Math.min(powerCapacity - entity.power.amount, powerOutput * Timers.delta()) * entity.efficiency; float mfract = maxPower / (powerOutput); - if (entity.generateTime > 0f) { + if(entity.generateTime > 0f){ entity.generateTime -= 1f / itemDuration * mfract; entity.power.amount += maxPower; entity.generateTime = Mathf.clamp(entity.generateTime); if(Mathf.chance(Timers.delta() * 0.06 * Mathf.clamp(entity.explosiveness - 0.25f))){ entity.damage(Mathf.random(8f)); - Effects.effect(explodeEffect, tile.worldx() + Mathf.range(size * tilesize/2f), tile.worldy() + Mathf.range(size * tilesize/2f)); + Effects.effect(explodeEffect, tile.worldx() + Mathf.range(size * tilesize / 2f), tile.worldy() + Mathf.range(size * tilesize / 2f)); } } - if (entity.generateTime <= 0f && entity.items.total() > 0) { + if(entity.generateTime <= 0f && entity.items.total() > 0){ Effects.effect(generateEffect, tile.worldx() + Mathf.range(3f), tile.worldy() + Mathf.range(3f)); Item item = entity.items.take(); entity.efficiency = getItemEfficiency(item); diff --git a/core/src/io/anuke/mindustry/world/blocks/power/LiquidGenerator.java b/core/src/io/anuke/mindustry/world/blocks/power/LiquidGenerator.java index ddb092e181..b33f2c82d3 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/LiquidGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/LiquidGenerator.java @@ -12,69 +12,73 @@ import io.anuke.ucore.core.Timers; import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.util.Mathf; -public abstract class LiquidGenerator extends PowerGenerator { - protected float minEfficiency = 0.2f; - protected float powerPerLiquid = 0.13f; - /**Maximum liquid used per frame.*/ - protected float maxLiquidGenerate = 0.4f; - protected Effect generateEffect = BlockFx.generatespark; +public abstract class LiquidGenerator extends PowerGenerator{ + protected float minEfficiency = 0.2f; + protected float powerPerLiquid = 0.13f; + /** + * Maximum liquid used per frame. + */ + protected float maxLiquidGenerate = 0.4f; + protected Effect generateEffect = BlockFx.generatespark; - public LiquidGenerator(String name) { - super(name); - liquidCapacity = 30f; - hasLiquids = true; + public LiquidGenerator(String name){ + super(name); + liquidCapacity = 30f; + hasLiquids = true; - consumes.add(new ConsumeLiquidFilter(liquid -> getEfficiency(liquid) >= minEfficiency, 0.001f)).update(false); - } + consumes.add(new ConsumeLiquidFilter(liquid -> getEfficiency(liquid) >= minEfficiency, 0.001f)).update(false); + } - @Override - public void draw(Tile tile){ - super.draw(tile); + @Override + public void draw(Tile tile){ + super.draw(tile); - TileEntity entity = tile.entity(); - - Draw.color(entity.liquids.current().color); - Draw.alpha(entity.liquids.total() / liquidCapacity); - drawLiquidCenter(tile); - Draw.color(); - } - - public void drawLiquidCenter(Tile tile){ - Draw.rect("blank", tile.drawx(), tile.drawy(), 2, 2); - } - - @Override - public void update(Tile tile){ - TileEntity entity = tile.entity(); - - if(entity.liquids.get(entity.liquids.current()) >= 0.001f){ - float powerPerLiquid = getEfficiency(entity.liquids.current())*this.powerPerLiquid; - float used = Math.min(entity.liquids.currentAmount(), maxLiquidGenerate * Timers.delta()); - used = Math.min(used, (powerCapacity - entity.power.amount)/powerPerLiquid); + TileEntity entity = tile.entity(); - entity.liquids.remove(entity.liquids.current(), used); - entity.power.amount += used * powerPerLiquid; - - if(used > 0.001f && Mathf.chance(0.05 * Timers.delta())){ - Effects.effect(generateEffect, tile.drawx() + Mathf.range(3f), tile.drawy() + Mathf.range(3f)); - } - } - - distributePower(tile); - } - - @Override - public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){ - return getEfficiency(liquid) >= minEfficiency && super.acceptLiquid(tile, source, liquid, amount); - } + Draw.color(entity.liquids.current().color); + Draw.alpha(entity.liquids.total() / liquidCapacity); + drawLiquidCenter(tile); + Draw.color(); + } - @Override - public TileEntity getEntity() { - return new ItemGeneratorEntity(); - } + public void drawLiquidCenter(Tile tile){ + Draw.rect("blank", tile.drawx(), tile.drawy(), 2, 2); + } - /**Returns an efficiency value for the specified liquid. - * Greater efficiency means more power generation. - * If a liquid's efficiency is below {@link #minEfficiency}, it is not accepted.*/ - protected abstract float getEfficiency(Liquid liquid); + @Override + public void update(Tile tile){ + TileEntity entity = tile.entity(); + + if(entity.liquids.get(entity.liquids.current()) >= 0.001f){ + float powerPerLiquid = getEfficiency(entity.liquids.current()) * this.powerPerLiquid; + float used = Math.min(entity.liquids.currentAmount(), maxLiquidGenerate * Timers.delta()); + used = Math.min(used, (powerCapacity - entity.power.amount) / powerPerLiquid); + + entity.liquids.remove(entity.liquids.current(), used); + entity.power.amount += used * powerPerLiquid; + + if(used > 0.001f && Mathf.chance(0.05 * Timers.delta())){ + Effects.effect(generateEffect, tile.drawx() + Mathf.range(3f), tile.drawy() + Mathf.range(3f)); + } + } + + distributePower(tile); + } + + @Override + public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){ + return getEfficiency(liquid) >= minEfficiency && super.acceptLiquid(tile, source, liquid, amount); + } + + @Override + public TileEntity getEntity(){ + return new ItemGeneratorEntity(); + } + + /** + * Returns an efficiency value for the specified liquid. + * Greater efficiency means more power generation. + * If a liquid's efficiency is below {@link #minEfficiency}, it is not accepted. + */ + protected abstract float getEfficiency(Liquid liquid); } diff --git a/core/src/io/anuke/mindustry/world/blocks/power/LiquidHeatGenerator.java b/core/src/io/anuke/mindustry/world/blocks/power/LiquidHeatGenerator.java index d591bea4f9..3990a6c731 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/LiquidHeatGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/LiquidHeatGenerator.java @@ -2,14 +2,14 @@ package io.anuke.mindustry.world.blocks.power; import io.anuke.mindustry.type.Liquid; -public class LiquidHeatGenerator extends LiquidGenerator { +public class LiquidHeatGenerator extends LiquidGenerator{ - public LiquidHeatGenerator(String name) { + public LiquidHeatGenerator(String name){ super(name); } @Override protected float getEfficiency(Liquid liquid){ - return liquid.temperature-0.5f; + return liquid.temperature - 0.5f; } } diff --git a/core/src/io/anuke/mindustry/world/blocks/power/NuclearReactor.java b/core/src/io/anuke/mindustry/world/blocks/power/NuclearReactor.java index dae8e33dae..9759b2e63e 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/NuclearReactor.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/NuclearReactor.java @@ -27,192 +27,192 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.tilesize; //TODO refactor to use consumers -public class NuclearReactor extends PowerGenerator { - protected final int timerFuel = timers++; +public class NuclearReactor extends PowerGenerator{ + protected final int timerFuel = timers++; - protected final Translator tr = new Translator(); + protected final Translator tr = new Translator(); - protected Color coolColor = new Color(1, 1, 1, 0f); - protected Color hotColor = Color.valueOf("ff9575a3"); - protected int fuelUseTime = 120; //time to consume 1 fuel - protected float powerMultiplier = 0.45f; //power per frame, depends on full capacity - protected float heating = 0.013f; //heating per frame - protected float coolantPower = 0.015f; //how much heat decreases per coolant unit - protected float smokeThreshold = 0.3f; //threshold at which block starts smoking - protected float maxLiquidUse = 2f; //max liquid use per frame - protected int explosionRadius = 19; - protected int explosionDamage = 135; - protected float flashThreshold = 0.46f; //heat threshold at which the lights start flashing + protected Color coolColor = new Color(1, 1, 1, 0f); + protected Color hotColor = Color.valueOf("ff9575a3"); + protected int fuelUseTime = 120; //time to consume 1 fuel + protected float powerMultiplier = 0.45f; //power per frame, depends on full capacity + protected float heating = 0.013f; //heating per frame + protected float coolantPower = 0.015f; //how much heat decreases per coolant unit + protected float smokeThreshold = 0.3f; //threshold at which block starts smoking + protected float maxLiquidUse = 2f; //max liquid use per frame + protected int explosionRadius = 19; + protected int explosionDamage = 135; + protected float flashThreshold = 0.46f; //heat threshold at which the lights start flashing - protected TextureRegion topRegion, lightsRegion; + protected TextureRegion topRegion, lightsRegion; - public NuclearReactor(String name) { - super(name); - itemCapacity = 30; - liquidCapacity = 50; - powerCapacity = 80f; - hasItems = true; - hasLiquids = true; + public NuclearReactor(String name){ + super(name); + itemCapacity = 30; + liquidCapacity = 50; + powerCapacity = 80f; + hasItems = true; + hasLiquids = true; - consumes.item(Items.thorium); - } + consumes.item(Items.thorium); + } - @Override - public void load() { - super.load(); + @Override + public void load(){ + super.load(); - topRegion = Draw.region(name + "-center"); - lightsRegion = Draw.region(name + "-lights"); - } + topRegion = Draw.region(name + "-center"); + lightsRegion = Draw.region(name + "-lights"); + } - @Override - public void setBars(){ - super.setBars(); - bars.replace(new BlockBar(BarType.inventory, true, tile -> (float)tile.entity.items.get(consumes.item()) / itemCapacity)); - bars.add(new BlockBar(BarType.heat, true, tile -> tile.entity().heat)); - } + @Override + public void setBars(){ + super.setBars(); + bars.replace(new BlockBar(BarType.inventory, true, tile -> (float) tile.entity.items.get(consumes.item()) / itemCapacity)); + bars.add(new BlockBar(BarType.heat, true, tile -> tile.entity().heat)); + } - @Override - public void setStats(){ - super.setStats(); - stats.add(BlockStat.inputLiquid, new LiquidFilterValue(liquid -> liquid.temperature <= 0.5f)); - stats.add(BlockStat.maxPowerGeneration, powerMultiplier*60f, StatUnit.powerSecond); - } - - @Override - public void update(Tile tile){ - NuclearReactorEntity entity = tile.entity(); - - int fuel = entity.items.get(consumes.item()); - float fullness = (float)fuel / itemCapacity; - - if(fuel > 0){ - entity.heat += fullness * heating * Math.min(Timers.delta(), 4f); - entity.power.amount += powerMultiplier * fullness * Timers.delta(); - entity.power.amount = Mathf.clamp(entity.power.amount, 0f, powerCapacity); - if(entity.timer.get(timerFuel, fuelUseTime)){ - entity.items.remove(consumes.item(), 1); - } - } - - if(entity.liquids.total() > 0){ - Liquid liquid = entity.liquids.current(); + @Override + public void setStats(){ + super.setStats(); + stats.add(BlockStat.inputLiquid, new LiquidFilterValue(liquid -> liquid.temperature <= 0.5f)); + stats.add(BlockStat.maxPowerGeneration, powerMultiplier * 60f, StatUnit.powerSecond); + } - if(liquid.temperature <= 0.5f){ //is coolant - float pow = coolantPower * (liquid.heatCapacity + 0.5f/liquid.temperature); //heat depleted per unit of liquid - float maxUsed = Math.min(Math.min(entity.liquids.get(liquid), entity.heat / pow), maxLiquidUse * Timers.delta()); //max that can be cooled in terms of liquid - entity.heat -= maxUsed * pow; - entity.liquids.remove(liquid, maxUsed); - }else{ //is heater - float heat = coolantPower * liquid.heatCapacity / 4f; //heat created per unit of liquid - float maxUsed = Math.min(Math.min(entity.liquids.get(liquid), (1f - entity.heat) / heat), maxLiquidUse * Timers.delta()); //max liquid used - entity.heat += maxUsed * heat; - entity.liquids.remove(liquid, maxUsed); - } - } - - if(entity.heat > smokeThreshold){ - float smoke = 1.0f + (entity.heat - smokeThreshold) / (1f - smokeThreshold); //ranges from 1.0 to 2.0 - if(Mathf.chance(smoke / 20.0 * Timers.delta())){ - Effects.effect(BlockFx.reactorsmoke, tile.worldx() + Mathf.range(size * tilesize / 2f), - tile.worldy() + Mathf.random(size * tilesize / 2f)); - } - } + @Override + public void update(Tile tile){ + NuclearReactorEntity entity = tile.entity(); - entity.heat = Mathf.clamp(entity.heat); - - if(entity.heat >= 1f){ - entity.damage((int)entity.health); - }else{ - distributePower(tile); - } - } - - @Override - public void onDestroyed(Tile tile){ - super.onDestroyed(tile); - - NuclearReactorEntity entity = tile.entity(); - - int fuel = entity.items.get(consumes.item()); - - if(fuel < 5 && entity.heat < 0.5f) return; - - Effects.shake(6f, 16f, tile.worldx(), tile.worldy()); - Effects.effect(ExplosionFx.nuclearShockwave, tile.worldx(), tile.worldy()); - for(int i = 0; i < 6; i ++){ - Timers.run(Mathf.random(40), () -> { - Effects.effect(BlockFx.nuclearcloud, tile.worldx(), tile.worldy()); - }); - } - - Damage.damage(tile.worldx(), tile.worldy(), explosionRadius * tilesize, explosionDamage * 4); - - - for(int i = 0; i < 20; i ++){ - Timers.run(Mathf.random(50), ()->{ - tr.rnd(Mathf.random(40f)); - Effects.effect(ExplosionFx.explosion, tr.x + tile.worldx(), tr.y + tile.worldy()); - }); - } - - for(int i = 0; i < 70; i ++){ - Timers.run(Mathf.random(80), ()->{ - tr.rnd(Mathf.random(120f)); - Effects.effect(BlockFx.nuclearsmoke, tr.x + tile.worldx(), tr.y + tile.worldy()); - }); - } - } + int fuel = entity.items.get(consumes.item()); + float fullness = (float) fuel / itemCapacity; - @Override - public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){ - return tile.entity.liquids.get(liquid) + amount < liquidCapacity && liquid.temperature <= 0.5f && - (tile.entity.liquids.current() == liquid || tile.entity.liquids.get(tile.entity.liquids.current()) < 0.01f); - } + if(fuel > 0){ + entity.heat += fullness * heating * Math.min(Timers.delta(), 4f); + entity.power.amount += powerMultiplier * fullness * Timers.delta(); + entity.power.amount = Mathf.clamp(entity.power.amount, 0f, powerCapacity); + if(entity.timer.get(timerFuel, fuelUseTime)){ + entity.items.remove(consumes.item(), 1); + } + } - @Override - public void draw(Tile tile){ - super.draw(tile); - - NuclearReactorEntity entity = tile.entity(); - - Draw.color(coolColor, hotColor, entity.heat); - Draw.rect("white", tile.drawx(), tile.drawy(), size * tilesize, size * tilesize); + if(entity.liquids.total() > 0){ + Liquid liquid = entity.liquids.current(); - Draw.color(entity.liquids.current().color); - Draw.alpha(entity.liquids.currentAmount() / liquidCapacity); - Draw.rect(topRegion, tile.drawx(), tile.drawy()); - - if(entity.heat > flashThreshold){ - float flash = 1f + ((entity.heat - flashThreshold) / (1f - flashThreshold)) * 5.4f; - entity.flash += flash * Timers.delta(); - Draw.color(Color.RED, Color.YELLOW, Mathf.absin(entity.flash, 9f, 1f)); - Draw.alpha(0.6f); - Draw.rect(lightsRegion, tile.drawx(), tile.drawy()); - } - - Draw.reset(); - } - - @Override - public TileEntity getEntity(){ - return new NuclearReactorEntity(); - } - - public static class NuclearReactorEntity extends GeneratorEntity { - public float heat; - public float flash; - - @Override - public void write(DataOutputStream stream) throws IOException{ - super.write(stream); - stream.writeFloat(heat); - } - - @Override - public void read(DataInputStream stream) throws IOException{ - super.read(stream); - heat = stream.readFloat(); - } - } + if(liquid.temperature <= 0.5f){ //is coolant + float pow = coolantPower * (liquid.heatCapacity + 0.5f / liquid.temperature); //heat depleted per unit of liquid + float maxUsed = Math.min(Math.min(entity.liquids.get(liquid), entity.heat / pow), maxLiquidUse * Timers.delta()); //max that can be cooled in terms of liquid + entity.heat -= maxUsed * pow; + entity.liquids.remove(liquid, maxUsed); + }else{ //is heater + float heat = coolantPower * liquid.heatCapacity / 4f; //heat created per unit of liquid + float maxUsed = Math.min(Math.min(entity.liquids.get(liquid), (1f - entity.heat) / heat), maxLiquidUse * Timers.delta()); //max liquid used + entity.heat += maxUsed * heat; + entity.liquids.remove(liquid, maxUsed); + } + } + + if(entity.heat > smokeThreshold){ + float smoke = 1.0f + (entity.heat - smokeThreshold) / (1f - smokeThreshold); //ranges from 1.0 to 2.0 + if(Mathf.chance(smoke / 20.0 * Timers.delta())){ + Effects.effect(BlockFx.reactorsmoke, tile.worldx() + Mathf.range(size * tilesize / 2f), + tile.worldy() + Mathf.random(size * tilesize / 2f)); + } + } + + entity.heat = Mathf.clamp(entity.heat); + + if(entity.heat >= 1f){ + entity.damage((int) entity.health); + }else{ + distributePower(tile); + } + } + + @Override + public void onDestroyed(Tile tile){ + super.onDestroyed(tile); + + NuclearReactorEntity entity = tile.entity(); + + int fuel = entity.items.get(consumes.item()); + + if(fuel < 5 && entity.heat < 0.5f) return; + + Effects.shake(6f, 16f, tile.worldx(), tile.worldy()); + Effects.effect(ExplosionFx.nuclearShockwave, tile.worldx(), tile.worldy()); + for(int i = 0; i < 6; i++){ + Timers.run(Mathf.random(40), () -> { + Effects.effect(BlockFx.nuclearcloud, tile.worldx(), tile.worldy()); + }); + } + + Damage.damage(tile.worldx(), tile.worldy(), explosionRadius * tilesize, explosionDamage * 4); + + + for(int i = 0; i < 20; i++){ + Timers.run(Mathf.random(50), () -> { + tr.rnd(Mathf.random(40f)); + Effects.effect(ExplosionFx.explosion, tr.x + tile.worldx(), tr.y + tile.worldy()); + }); + } + + for(int i = 0; i < 70; i++){ + Timers.run(Mathf.random(80), () -> { + tr.rnd(Mathf.random(120f)); + Effects.effect(BlockFx.nuclearsmoke, tr.x + tile.worldx(), tr.y + tile.worldy()); + }); + } + } + + @Override + public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){ + return tile.entity.liquids.get(liquid) + amount < liquidCapacity && liquid.temperature <= 0.5f && + (tile.entity.liquids.current() == liquid || tile.entity.liquids.get(tile.entity.liquids.current()) < 0.01f); + } + + @Override + public void draw(Tile tile){ + super.draw(tile); + + NuclearReactorEntity entity = tile.entity(); + + Draw.color(coolColor, hotColor, entity.heat); + Draw.rect("white", tile.drawx(), tile.drawy(), size * tilesize, size * tilesize); + + Draw.color(entity.liquids.current().color); + Draw.alpha(entity.liquids.currentAmount() / liquidCapacity); + Draw.rect(topRegion, tile.drawx(), tile.drawy()); + + if(entity.heat > flashThreshold){ + float flash = 1f + ((entity.heat - flashThreshold) / (1f - flashThreshold)) * 5.4f; + entity.flash += flash * Timers.delta(); + Draw.color(Color.RED, Color.YELLOW, Mathf.absin(entity.flash, 9f, 1f)); + Draw.alpha(0.6f); + Draw.rect(lightsRegion, tile.drawx(), tile.drawy()); + } + + Draw.reset(); + } + + @Override + public TileEntity getEntity(){ + return new NuclearReactorEntity(); + } + + public static class NuclearReactorEntity extends GeneratorEntity{ + public float heat; + public float flash; + + @Override + public void write(DataOutputStream stream) throws IOException{ + super.write(stream); + stream.writeFloat(heat); + } + + @Override + public void read(DataInputStream stream) throws IOException{ + super.read(stream); + heat = stream.readFloat(); + } + } } diff --git a/core/src/io/anuke/mindustry/world/blocks/power/PowerDistributor.java b/core/src/io/anuke/mindustry/world/blocks/power/PowerDistributor.java index bb2a5ac1f8..991bd165d2 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/PowerDistributor.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/PowerDistributor.java @@ -7,9 +7,9 @@ import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.PowerBlock; import io.anuke.ucore.core.Timers; -public class PowerDistributor extends PowerBlock { +public class PowerDistributor extends PowerBlock{ - public PowerDistributor(String name) { + public PowerDistributor(String name){ super(name); } @@ -26,7 +26,7 @@ public class PowerDistributor extends PowerBlock { for(GridPoint2 point : Edges.getEdges(size)){ Tile target = tile.getNearby(point); if(target != null && target.block().hasPower && - shouldDistribute(tile, target)) sources ++; + shouldDistribute(tile, target)) sources++; } if(sources == 0) return; @@ -39,7 +39,7 @@ public class PowerDistributor extends PowerBlock { target = target.target(); if(target.block().hasPower && shouldDistribute(tile, target)){ - float diff = (tile.entity.power.amount / powerCapacity - target.entity.power.amount / target.block().powerCapacity)/1.4f; + float diff = (tile.entity.power.amount / powerCapacity - target.entity.power.amount / target.block().powerCapacity) / 1.4f; float transmit = Math.min(result * Timers.delta(), diff * powerCapacity); if(target.block().acceptPower(target, tile, transmit)){ @@ -50,7 +50,7 @@ public class PowerDistributor extends PowerBlock { } } - protected boolean shouldDistribute(Tile tile, Tile other) { + protected boolean shouldDistribute(Tile tile, Tile other){ other = other.target(); //only generators can distribute to other generators return (!(other.block() instanceof PowerGenerator) || tile.block() instanceof PowerGenerator) @@ -60,7 +60,7 @@ public class PowerDistributor extends PowerBlock { } @Override - public void update(Tile tile) { + public void update(Tile tile){ distributePower(tile); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/power/PowerGenerator.java b/core/src/io/anuke/mindustry/world/blocks/power/PowerGenerator.java index 03a8cbf43a..5fac88bc26 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/PowerGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/PowerGenerator.java @@ -4,16 +4,16 @@ import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.world.meta.BlockFlag; import io.anuke.ucore.util.EnumSet; -public class PowerGenerator extends PowerDistributor { +public class PowerGenerator extends PowerDistributor{ - public PowerGenerator(String name) { + public PowerGenerator(String name){ super(name); baseExplosiveness = 5f; flags = EnumSet.of(BlockFlag.producer); } @Override - public TileEntity getEntity() { + public TileEntity getEntity(){ return new GeneratorEntity(); } diff --git a/core/src/io/anuke/mindustry/world/blocks/power/PowerNode.java b/core/src/io/anuke/mindustry/world/blocks/power/PowerNode.java index 84b3769fdd..b2a7646e40 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/PowerNode.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/PowerNode.java @@ -31,290 +31,291 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.*; public class PowerNode extends PowerBlock{ - public static final float thicknessScl = 0.7f; + public static final float thicknessScl = 0.7f; public static final float flashScl = 0.12f; - //last distribution block placed - private static int lastPlaced = -1; + //last distribution block placed + private static int lastPlaced = -1; - protected Translator t1 = new Translator(); - protected Translator t2 = new Translator(); + protected Translator t1 = new Translator(); + protected Translator t2 = new Translator(); - protected float laserRange = 6; - protected float powerSpeed = 0.5f; - protected int maxNodes = 3; + protected float laserRange = 6; + protected float powerSpeed = 0.5f; + protected int maxNodes = 3; - public PowerNode(String name){ - super(name); - expanded = true; - layer = Layer.power; - powerCapacity = 5f; - configurable = true; - } + public PowerNode(String name){ + super(name); + expanded = true; + layer = Layer.power; + powerCapacity = 5f; + configurable = true; + } - @Override - public void setBars(){} + @Remote(targets = Loc.both, called = Loc.server, in = In.blocks, forward = true) + public static void linkPowerDistributors(Player player, Tile tile, Tile other){ - @Override - public void placed(Tile tile) { - Tile before = world.tile(lastPlaced); - if(linkValid(tile, before) && before.block() instanceof PowerNode){ - CallBlocks.linkPowerDistributors(null, tile, before); - } + DistributorEntity entity = tile.entity(); - lastPlaced = tile.packedPosition(); - } + if(!entity.links.contains(other.packedPosition())){ + entity.links.add(other.packedPosition()); + } - @Override - public void setStats(){ - super.setStats(); + if(other.block() instanceof PowerNode){ + DistributorEntity oe = other.entity(); - stats.add(BlockStat.powerRange, laserRange, StatUnit.blocks); - stats.add(BlockStat.powerTransferSpeed, powerSpeed * 60 / 2f, StatUnit.powerSecond); //divided by 2 since passback exists - } + if(!oe.links.contains(tile.packedPosition())){ + oe.links.add(tile.packedPosition()); + } + } + } - @Override - public void update(Tile tile){ + @Remote(targets = Loc.both, called = Loc.server, in = In.blocks, forward = true) + public static void unlinkPowerDistributors(Player player, Tile tile, Tile other){ + DistributorEntity entity = tile.entity(); + + entity.links.removeValue(other.packedPosition()); + + if(other.block() instanceof PowerNode){ + DistributorEntity oe = other.entity(); + + oe.links.removeValue(tile.packedPosition()); + } + } + + @Override + public void setBars(){ + } + + @Override + public void placed(Tile tile){ + Tile before = world.tile(lastPlaced); + if(linkValid(tile, before) && before.block() instanceof PowerNode){ + CallBlocks.linkPowerDistributors(null, tile, before); + } + + lastPlaced = tile.packedPosition(); + } + + @Override + public void setStats(){ + super.setStats(); + + stats.add(BlockStat.powerRange, laserRange, StatUnit.blocks); + stats.add(BlockStat.powerTransferSpeed, powerSpeed * 60 / 2f, StatUnit.powerSecond); //divided by 2 since passback exists + } + + @Override + public void update(Tile tile){ distributeLaserPower(tile); } @Override public boolean onConfigureTileTapped(Tile tile, Tile other){ - DistributorEntity entity = tile.entity(); - other = other.target(); + DistributorEntity entity = tile.entity(); + other = other.target(); - Tile result = other; + Tile result = other; - if(linkValid(tile, other)){ - if(linked(tile, other)){ - threads.run(() -> CallBlocks.unlinkPowerDistributors(null, tile, result)); - }else if(entity.links.size < maxNodes){ - threads.run(() -> CallBlocks.linkPowerDistributors(null, tile, result)); - } - return false; - } - return true; - } + if(linkValid(tile, other)){ + if(linked(tile, other)){ + threads.run(() -> CallBlocks.unlinkPowerDistributors(null, tile, result)); + }else if(entity.links.size < maxNodes){ + threads.run(() -> CallBlocks.linkPowerDistributors(null, tile, result)); + } + return false; + } + return true; + } - @Override - public void drawSelect(Tile tile){ - super.drawSelect(tile); + @Override + public void drawSelect(Tile tile){ + super.drawSelect(tile); Draw.color(Palette.power); Lines.stroke(1f); - Lines.poly(Edges.getPixelPolygon(laserRange), tile.worldx() - tilesize/2, tile.worldy() - tilesize/2, tilesize); + Lines.poly(Edges.getPixelPolygon(laserRange), tile.worldx() - tilesize / 2, tile.worldy() - tilesize / 2, tilesize); Draw.reset(); - } + } - @Override - public void drawConfigure(Tile tile){ - DistributorEntity entity = tile.entity(); + @Override + public void drawConfigure(Tile tile){ + DistributorEntity entity = tile.entity(); - Draw.color(Palette.accent); + Draw.color(Palette.accent); - Lines.stroke(1f); - Lines.square(tile.drawx(), tile.drawy(), - tile.block().size * tilesize / 2f + 1f + Mathf.absin(Timers.time(), 4f, 1f)); + Lines.stroke(1f); + Lines.square(tile.drawx(), tile.drawy(), + tile.block().size * tilesize / 2f + 1f + Mathf.absin(Timers.time(), 4f, 1f)); - Lines.stroke(1f); + Lines.stroke(1f); - Lines.poly(Edges.getPixelPolygon(laserRange), tile.worldx() - tilesize/2, tile.worldy() - tilesize/2, tilesize); + Lines.poly(Edges.getPixelPolygon(laserRange), tile.worldx() - tilesize / 2, tile.worldy() - tilesize / 2, tilesize); - Draw.color(Palette.power); + Draw.color(Palette.power); - for(int x = (int)(tile.x - laserRange); x <= tile.x + laserRange; x ++){ - for(int y = (int)(tile.y - laserRange); y <= tile.y + laserRange; y ++){ - Tile link = world.tile(x, y); - if(link != null) link = link.target(); + for(int x = (int) (tile.x - laserRange); x <= tile.x + laserRange; x++){ + for(int y = (int) (tile.y - laserRange); y <= tile.y + laserRange; y++){ + Tile link = world.tile(x, y); + if(link != null) link = link.target(); - if(link != tile && linkValid(tile, link, false)){ - boolean linked = linked(tile, link); - Draw.color(linked ? Palette.place : Palette.breakInvalid); + if(link != tile && linkValid(tile, link, false)){ + boolean linked = linked(tile, link); + Draw.color(linked ? Palette.place : Palette.breakInvalid); - Lines.square(link.drawx(), link.drawy(), - link.block().size * tilesize / 2f + 1f + (linked ? 0f : Mathf.absin(Timers.time(), 4f, 1f))); + Lines.square(link.drawx(), link.drawy(), + link.block().size * tilesize / 2f + 1f + (linked ? 0f : Mathf.absin(Timers.time(), 4f, 1f))); - if((entity.links.size >= maxNodes || (link.block() instanceof PowerNode && ((DistributorEntity)link.entity).links.size >= ((PowerNode)link.block()).maxNodes)) && !linked){ - Draw.color(); - Draw.rect("cross-" + link.block().size, link.drawx(), link.drawy()); - } - } - } - } + if((entity.links.size >= maxNodes || (link.block() instanceof PowerNode && ((DistributorEntity) link.entity).links.size >= ((PowerNode) link.block()).maxNodes)) && !linked){ + Draw.color(); + Draw.rect("cross-" + link.block().size, link.drawx(), link.drawy()); + } + } + } + } - Draw.reset(); - } + Draw.reset(); + } - @Override - public void drawPlace(int x, int y, int rotation, boolean valid){ + @Override + public void drawPlace(int x, int y, int rotation, boolean valid){ Draw.color(Palette.placing); Lines.stroke(1f); - Lines.poly(Edges.getPixelPolygon(laserRange), x * tilesize - tilesize/2, y * tilesize - tilesize/2, tilesize); + Lines.poly(Edges.getPixelPolygon(laserRange), x * tilesize - tilesize / 2, y * tilesize - tilesize / 2, tilesize); Draw.reset(); - } + } - @Override - public void drawLayer(Tile tile){ - if(!Settings.getBool("lasers")) return; + @Override + public void drawLayer(Tile tile){ + if(!Settings.getBool("lasers")) return; - DistributorEntity entity = tile.entity(); + DistributorEntity entity = tile.entity(); - entity.laserColor = Mathf.lerpDelta(entity.laserColor, Mathf.clamp(entity.powerRecieved/(powerSpeed)), 0.08f); + entity.laserColor = Mathf.lerpDelta(entity.laserColor, Mathf.clamp(entity.powerRecieved / (powerSpeed)), 0.08f); - Draw.color(Palette.powerLaserFrom, Palette.powerLaserTo, entity.laserColor * (1f-flashScl) + Mathf.sin(Timers.time(), 1.7f, flashScl)); + Draw.color(Palette.powerLaserFrom, Palette.powerLaserTo, entity.laserColor * (1f - flashScl) + Mathf.sin(Timers.time(), 1.7f, flashScl)); - for(int i = 0; i < entity.links.size; i ++){ - Tile link = world.tile(entity.links.get(i)); - if(linkValid(tile, link)) drawLaser(tile, link); + for(int i = 0; i < entity.links.size; i++){ + Tile link = world.tile(entity.links.get(i)); + if(linkValid(tile, link)) drawLaser(tile, link); } - Draw.color(); - } + Draw.color(); + } - @Override - public float addPower(Tile tile, float amount){ - DistributorEntity entity = tile.entity(); + @Override + public float addPower(Tile tile, float amount){ + DistributorEntity entity = tile.entity(); - if(entity.lastRecieved != threads.getFrameID()){ - entity.lastRecieved = threads.getFrameID(); - entity.powerRecieved = 0f; - } + if(entity.lastRecieved != threads.getFrameID()){ + entity.lastRecieved = threads.getFrameID(); + entity.powerRecieved = 0f; + } - float canAccept = Math.min(powerCapacity * Timers.delta() - tile.entity.power.amount, amount); + float canAccept = Math.min(powerCapacity * Timers.delta() - tile.entity.power.amount, amount); - tile.entity.power.amount += canAccept; - entity.powerRecieved += canAccept; + tile.entity.power.amount += canAccept; + entity.powerRecieved += canAccept; - return canAccept; - } + return canAccept; + } - protected boolean shouldDistribute(Tile tile, Tile other) { - return other.entity.power.amount / other.block().powerCapacity <= tile.entity.power.amount / powerCapacity && - !(other.block() instanceof PowerGenerator); //do not distribute to power generators - } + protected boolean shouldDistribute(Tile tile, Tile other){ + return other.entity.power.amount / other.block().powerCapacity <= tile.entity.power.amount / powerCapacity && + !(other.block() instanceof PowerGenerator); //do not distribute to power generators + } - protected boolean shouldLeechPower(Tile tile, Tile other){ - return !(other.block() instanceof PowerNode) - && other.block() instanceof PowerDistributor //only suck power from batteries and power generators - && other.entity.power.amount / other.block().powerCapacity > tile.entity.power.amount / powerCapacity; - } + protected boolean shouldLeechPower(Tile tile, Tile other){ + return !(other.block() instanceof PowerNode) + && other.block() instanceof PowerDistributor //only suck power from batteries and power generators + && other.entity.power.amount / other.block().powerCapacity > tile.entity.power.amount / powerCapacity; + } - protected void distributeLaserPower(Tile tile){ - DistributorEntity entity = tile.entity(); + protected void distributeLaserPower(Tile tile){ + DistributorEntity entity = tile.entity(); - if(Float.isNaN(entity.power.amount)){ - entity.power.amount = 0f; - } + if(Float.isNaN(entity.power.amount)){ + entity.power.amount = 0f; + } - int targets = 0; + int targets = 0; - //validate everything first. - for(int i = 0; i < entity.links.size; i ++){ - Tile target = world.tile(entity.links.get(i)); - if(!linkValid(tile, target)) { - entity.links.removeIndex(i); - i --; - }else if(shouldDistribute(tile, target)) { - targets++; - } - } + //validate everything first. + for(int i = 0; i < entity.links.size; i++){ + Tile target = world.tile(entity.links.get(i)); + if(!linkValid(tile, target)){ + entity.links.removeIndex(i); + i--; + }else if(shouldDistribute(tile, target)){ + targets++; + } + } - float result = Math.min(entity.power.amount / targets, powerSpeed * Timers.delta()); + float result = Math.min(entity.power.amount / targets, powerSpeed * Timers.delta()); - for(int i = 0; i < entity.links.size; i ++){ - Tile target = world.tile(entity.links.get(i)); - if(shouldDistribute(tile, target)) { + for(int i = 0; i < entity.links.size; i++){ + Tile target = world.tile(entity.links.get(i)); + if(shouldDistribute(tile, target)){ - float transmit = Math.min(result, entity.power.amount); - if (target.block().acceptPower(target, tile, transmit)) { - entity.power.amount -= target.block().addPower(target, transmit); - } - }else if(shouldLeechPower(tile, target)){ - float diff = (target.entity.power.amount / target.block().powerCapacity - tile.entity.power.amount / powerCapacity)/1.4f; - float transmit = Math.min(Math.min(target.block().powerCapacity * diff, target.entity.power.amount), powerCapacity - tile.entity.power.amount); - entity.power.amount += transmit; - target.entity.power.amount -= transmit; - } - } - } + float transmit = Math.min(result, entity.power.amount); + if(target.block().acceptPower(target, tile, transmit)){ + entity.power.amount -= target.block().addPower(target, transmit); + } + }else if(shouldLeechPower(tile, target)){ + float diff = (target.entity.power.amount / target.block().powerCapacity - tile.entity.power.amount / powerCapacity) / 1.4f; + float transmit = Math.min(Math.min(target.block().powerCapacity * diff, target.entity.power.amount), powerCapacity - tile.entity.power.amount); + entity.power.amount += transmit; + target.entity.power.amount -= transmit; + } + } + } - protected boolean linked(Tile tile, Tile other){ - return tile.entity().links.contains(other.packedPosition()); - } + protected boolean linked(Tile tile, Tile other){ + return tile.entity().links.contains(other.packedPosition()); + } - protected boolean linkValid(Tile tile, Tile link){ - return linkValid(tile, link, true); - } + protected boolean linkValid(Tile tile, Tile link){ + return linkValid(tile, link, true); + } - protected boolean linkValid(Tile tile, Tile link, boolean checkMaxNodes){ - if(!(tile != link && link != null && link.block().hasPower)) return false; + protected boolean linkValid(Tile tile, Tile link, boolean checkMaxNodes){ + if(!(tile != link && link != null && link.block().hasPower)) return false; - if(link.block() instanceof PowerNode){ - DistributorEntity oe = link.entity(); + if(link.block() instanceof PowerNode){ + DistributorEntity oe = link.entity(); - return Vector2.dst(tile.drawx(), tile.drawy(), link.drawx(), link.drawy()) <= Math.max(laserRange * tilesize, - ((PowerNode)link.block()).laserRange * tilesize) - tilesize/2f - + (link.block().size-1)*tilesize/2f + (tile.block().size-1)*tilesize/2f && - (!checkMaxNodes || (oe.links.size < ((PowerNode)link.block()).maxNodes || oe.links.contains(tile.packedPosition()))); - }else{ - return Vector2.dst(tile.drawx(), tile.drawy(), link.drawx(), link.drawy()) - <= laserRange * tilesize - tilesize/2f + (link.block().size-1)*tilesize; - } - } + return Vector2.dst(tile.drawx(), tile.drawy(), link.drawx(), link.drawy()) <= Math.max(laserRange * tilesize, + ((PowerNode) link.block()).laserRange * tilesize) - tilesize / 2f + + (link.block().size - 1) * tilesize / 2f + (tile.block().size - 1) * tilesize / 2f && + (!checkMaxNodes || (oe.links.size < ((PowerNode) link.block()).maxNodes || oe.links.contains(tile.packedPosition()))); + }else{ + return Vector2.dst(tile.drawx(), tile.drawy(), link.drawx(), link.drawy()) + <= laserRange * tilesize - tilesize / 2f + (link.block().size - 1) * tilesize; + } + } - protected void drawLaser(Tile tile, Tile target){ + protected void drawLaser(Tile tile, Tile target){ float x1 = tile.drawx(), y1 = tile.drawy(), x2 = target.drawx(), y2 = target.drawy(); float angle1 = Angles.angle(x1, y1, x2, y2); float angle2 = angle1 + 180f; - t1.trns(angle1, tile.block().size * tilesize/2f + 1f); - t2.trns(angle2, target.block().size * tilesize/2f + 1f); + t1.trns(angle1, tile.block().size * tilesize / 2f + 1f); + t2.trns(angle2, target.block().size * tilesize / 2f + 1f); Shapes.laser("laser", "laser-end", x1 + t1.x, y1 + t1.y, x2 + t2.x, y2 + t2.y, thicknessScl); - } - - @Override - public TileEntity getEntity() { - return new DistributorEntity(); } - @Remote(targets = Loc.both, called = Loc.server, in = In.blocks, forward = true) - public static void linkPowerDistributors(Player player, Tile tile, Tile other){ - - DistributorEntity entity = tile.entity(); - - if(!entity.links.contains(other.packedPosition())){ - entity.links.add(other.packedPosition()); - } - - if(other.block() instanceof PowerNode){ - DistributorEntity oe = other.entity(); - - if(!oe.links.contains(tile.packedPosition()) ){ - oe.links.add(tile.packedPosition()); - } - } - } - - @Remote(targets = Loc.both, called = Loc.server, in = In.blocks, forward = true) - public static void unlinkPowerDistributors(Player player, Tile tile, Tile other){ - DistributorEntity entity = tile.entity(); - - entity.links.removeValue(other.packedPosition()); - - if(other.block() instanceof PowerNode){ - DistributorEntity oe = other.entity(); - - oe.links.removeValue(tile.packedPosition()); - } - } + @Override + public TileEntity getEntity(){ + return new DistributorEntity(); + } public static class DistributorEntity extends TileEntity{ public float laserColor = 0f; @@ -322,21 +323,21 @@ public class PowerNode extends PowerBlock{ public long lastRecieved = 0; public IntArray links = new IntArray(); - @Override - public void write(DataOutputStream stream) throws IOException { - stream.writeShort(links.size); - for(int i = 0; i < links.size; i ++){ - stream.writeInt(links.get(i)); - } - } + @Override + public void write(DataOutputStream stream) throws IOException{ + stream.writeShort(links.size); + for(int i = 0; i < links.size; i++){ + stream.writeInt(links.get(i)); + } + } - @Override - public void read(DataInputStream stream) throws IOException { - short amount = stream.readShort(); - for(int i = 0; i < amount; i ++){ - links.add(stream.readInt()); - } - } - } + @Override + public void read(DataInputStream stream) throws IOException{ + short amount = stream.readShort(); + for(int i = 0; i < amount; i++){ + links.add(stream.readInt()); + } + } + } } diff --git a/core/src/io/anuke/mindustry/world/blocks/power/SolarGenerator.java b/core/src/io/anuke/mindustry/world/blocks/power/SolarGenerator.java index 5dee77caf8..651ea6af5d 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/SolarGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/SolarGenerator.java @@ -5,8 +5,10 @@ import io.anuke.mindustry.world.meta.BlockStat; import io.anuke.mindustry.world.meta.StatUnit; import io.anuke.ucore.core.Timers; -public class SolarGenerator extends PowerGenerator { - /**power generated per frame*/ +public class SolarGenerator extends PowerGenerator{ + /** + * power generated per frame + */ protected float generation = 0.005f; public SolarGenerator(String name){ @@ -14,7 +16,7 @@ public class SolarGenerator extends PowerGenerator { } @Override - public void setStats() { + public void setStats(){ super.setStats(); stats.add(BlockStat.maxPowerGeneration, generation * 60f, StatUnit.powerSecond); diff --git a/core/src/io/anuke/mindustry/world/blocks/power/TurbineGenerator.java b/core/src/io/anuke/mindustry/world/blocks/power/TurbineGenerator.java index 99e679557a..7416986d6e 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/TurbineGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/TurbineGenerator.java @@ -5,9 +5,9 @@ import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.consumers.ConsumeLiquid; //TODO -public class TurbineGenerator extends BurnerGenerator { +public class TurbineGenerator extends BurnerGenerator{ - public TurbineGenerator(String name) { + public TurbineGenerator(String name){ super(name); singleLiquid = false; diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Compressor.java b/core/src/io/anuke/mindustry/world/blocks/production/Compressor.java index 4fde97444e..189dfd1456 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Compressor.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Compressor.java @@ -7,30 +7,30 @@ import io.anuke.mindustry.world.blocks.production.GenericCrafter.GenericCrafterE import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.util.Mathf; -public class Compressor extends PowerCrafter { +public class Compressor extends PowerCrafter{ protected TextureRegion liquidRegion, topRegion; protected TextureRegion[] frameRegions; - public Compressor(String name) { + public Compressor(String name){ super(name); hasLiquids = true; } @Override - public void load() { + public void load(){ super.load(); frameRegions = new TextureRegion[3]; - for (int i = 0; i < 3; i++) { + for(int i = 0; i < 3; i++){ frameRegions[i] = Draw.region(name + "-frame" + i); } - + liquidRegion = Draw.region(name + "-liquid"); topRegion = Draw.region(name + "-top"); } @Override - public void draw(Tile tile) { + public void draw(Tile tile){ GenericCrafterEntity entity = tile.entity(); Draw.rect(region, tile.drawx(), tile.drawy()); @@ -42,7 +42,7 @@ public class Compressor extends PowerCrafter { } @Override - public TextureRegion[] getIcon() { + public TextureRegion[] getIcon(){ return new TextureRegion[]{Draw.region(name), Draw.region(name + "-top")}; } } diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Cultivator.java b/core/src/io/anuke/mindustry/world/blocks/production/Cultivator.java index c0f473e4cd..507e1256ea 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Cultivator.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Cultivator.java @@ -19,7 +19,7 @@ import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -public class Cultivator extends Drill { +public class Cultivator extends Drill{ protected Color plantColor = Color.valueOf("648b55"); protected Color plantColorLight = Color.valueOf("73a75f"); protected Color bottomColor = Color.valueOf("474747"); @@ -30,23 +30,23 @@ public class Cultivator extends Drill { protected SeedRandom random = new SeedRandom(0); protected float recurrence = 6f; - public Cultivator(String name) { + public Cultivator(String name){ super(name); drillEffect = Fx.none; } @Override - public void setStats() { + public void setStats(){ super.setStats(); stats.remove(BlockStat.drillTier); stats.add(BlockStat.drillTier, table -> { - table.addImage("grass1").size(8*3).padBottom(3).padTop(3); + table.addImage("grass1").size(8 * 3).padBottom(3).padTop(3); }); } @Override - public void load() { + public void load(){ super.load(); middleRegion = Draw.region(name + "-middle"); @@ -54,7 +54,7 @@ public class Cultivator extends Drill { } @Override - public void update(Tile tile) { + public void update(Tile tile){ super.update(tile); CultivatorEntity entity = tile.entity(); @@ -62,7 +62,7 @@ public class Cultivator extends Drill { } @Override - public void draw(Tile tile) { + public void draw(Tile tile){ CultivatorEntity entity = tile.entity(); Draw.rect(region, tile.drawx(), tile.drawy()); @@ -74,14 +74,14 @@ public class Cultivator extends Drill { Draw.color(bottomColor, plantColorLight, entity.warmup); random.setSeed(tile.packedPosition()); - for(int i = 0; i < 12; i ++){ + for(int i = 0; i < 12; i++){ float offset = random.nextFloat() * 999999f; float x = random.range(4f), y = random.range(4f); float life = 1f - (((Timers.time() + offset) / 50f) % recurrence); if(life > 0){ - Lines.stroke(entity.warmup * (life*1f + 0.2f)); - Lines.poly(tile.drawx() + x, tile.drawy() + y, 8, (1f-life) * 3f); + Lines.stroke(entity.warmup * (life * 1f + 0.2f)); + Lines.poly(tile.drawx() + x, tile.drawy() + y, 8, (1f - life) * 3f); } } @@ -90,12 +90,12 @@ public class Cultivator extends Drill { } @Override - public TextureRegion[] getIcon() { - return new TextureRegion[]{Draw.region(name), Draw.region(name + "-top"), }; + public TextureRegion[] getIcon(){ + return new TextureRegion[]{Draw.region(name), Draw.region(name + "-top"),}; } @Override - public TileEntity getEntity() { + public TileEntity getEntity(){ return new CultivatorEntity(); } @@ -105,7 +105,7 @@ public class Cultivator extends Drill { } @Override - public Item getDrop(Tile tile) { + public Item getDrop(Tile tile){ return Items.biomatter; } @@ -113,12 +113,12 @@ public class Cultivator extends Drill { public float warmup; @Override - public void write(DataOutputStream stream) throws IOException { + public void write(DataOutputStream stream) throws IOException{ stream.writeFloat(warmup); } @Override - public void read(DataInputStream stream) throws IOException { + public void read(DataInputStream stream) throws IOException{ warmup = stream.readFloat(); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Drill.java b/core/src/io/anuke/mindustry/world/blocks/production/Drill.java index 999818157b..5d7951a5a4 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Drill.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Drill.java @@ -25,213 +25,231 @@ import static io.anuke.mindustry.Vars.control; import static io.anuke.mindustry.Vars.headless; public class Drill extends Block{ - protected final static float hardnessDrillMultiplier = 50f; - protected final int timerDump = timers++; + protected final static float hardnessDrillMultiplier = 50f; + protected final int timerDump = timers++; - protected final Array drawTiles = new Array<>(); - protected final Array toAdd = new Array<>(); + protected final Array drawTiles = new Array<>(); + protected final Array toAdd = new Array<>(); - /**Maximum tier of blocks this drill can mine.*/ - protected int tier; - /**Base time to drill one ore, in frames.*/ - protected float drillTime = 300; - /**Whether the liquid is required to drill. If false, then it will be used as a speed booster.*/ - protected boolean liquidRequired = false; - /**How many times faster the drill will progress when boosted by liquid.*/ - protected float liquidBoostIntensity = 1.6f; - /**Speed at which the drill speeds up.*/ - protected float warmupSpeed = 0.02f; + /** + * Maximum tier of blocks this drill can mine. + */ + protected int tier; + /** + * Base time to drill one ore, in frames. + */ + protected float drillTime = 300; + /** + * Whether the liquid is required to drill. If false, then it will be used as a speed booster. + */ + protected boolean liquidRequired = false; + /** + * How many times faster the drill will progress when boosted by liquid. + */ + protected float liquidBoostIntensity = 1.6f; + /** + * Speed at which the drill speeds up. + */ + protected float warmupSpeed = 0.02f; - /**Effect played when an item is produced. This is colored.*/ - protected Effect drillEffect = BlockFx.mine; - /**Speed the drill bit rotates at.*/ - protected float rotateSpeed = 2f; - /**Effect randomly played while drilling.*/ - protected Effect updateEffect = BlockFx.pulverizeSmall; - /**Chance the update effect will appear.*/ - protected float updateEffectChance = 0.02f; + /** + * Effect played when an item is produced. This is colored. + */ + protected Effect drillEffect = BlockFx.mine; + /** + * Speed the drill bit rotates at. + */ + protected float rotateSpeed = 2f; + /** + * Effect randomly played while drilling. + */ + protected Effect updateEffect = BlockFx.pulverizeSmall; + /** + * Chance the update effect will appear. + */ + protected float updateEffectChance = 0.02f; - protected boolean drawRim = false; + protected boolean drawRim = false; - protected Color heatColor = Color.valueOf("ff5512"); - protected TextureRegion rimRegion; - protected TextureRegion rotatorRegion; - protected TextureRegion topRegion; + protected Color heatColor = Color.valueOf("ff5512"); + protected TextureRegion rimRegion; + protected TextureRegion rotatorRegion; + protected TextureRegion topRegion; - public Drill(String name) { - super(name); - update = true; - solid = true; - layer = Layer.overlay; - itemCapacity = 5; - group = BlockGroup.drills; - hasLiquids = true; - liquidCapacity = 5f; - hasItems = true; + public Drill(String name){ + super(name); + update = true; + solid = true; + layer = Layer.overlay; + itemCapacity = 5; + group = BlockGroup.drills; + hasLiquids = true; + liquidCapacity = 5f; + hasItems = true; - consumes.add(new ConsumeLiquid(Liquids.water, 0.05f)).optional(true); - } + consumes.add(new ConsumeLiquid(Liquids.water, 0.05f)).optional(true); + } - @Override - public void load() { - super.load(); - rimRegion = Draw.region(name + "-rim"); - rotatorRegion = Draw.region(name + "-rotator"); - topRegion = Draw.region(name + "-top"); - } + @Override + public void load(){ + super.load(); + rimRegion = Draw.region(name + "-rim"); + rotatorRegion = Draw.region(name + "-rotator"); + topRegion = Draw.region(name + "-top"); + } - @Override - public void draw(Tile tile) { - float s = 0.3f; - float ts = 0.6f; + @Override + public void draw(Tile tile){ + float s = 0.3f; + float ts = 0.6f; - DrillEntity entity = tile.entity(); + DrillEntity entity = tile.entity(); - Draw.rect(region, tile.drawx(), tile.drawy()); + Draw.rect(region, tile.drawx(), tile.drawy()); - if(drawRim) { - Graphics.setAdditiveBlending(); - Draw.color(heatColor); - Draw.alpha(entity.warmup * ts * (1f-s + Mathf.absin(Timers.time(), 3f, s))); - Draw.rect(rimRegion, tile.drawx(), tile.drawy()); - Draw.color(); - Graphics.setNormalBlending(); - } + if(drawRim){ + Graphics.setAdditiveBlending(); + Draw.color(heatColor); + Draw.alpha(entity.warmup * ts * (1f - s + Mathf.absin(Timers.time(), 3f, s))); + Draw.rect(rimRegion, tile.drawx(), tile.drawy()); + Draw.color(); + Graphics.setNormalBlending(); + } - Draw.rect(rotatorRegion, tile.drawx(), tile.drawy(), entity.drillTime * rotateSpeed); + Draw.rect(rotatorRegion, tile.drawx(), tile.drawy(), entity.drillTime * rotateSpeed); - Draw.rect(topRegion, tile.drawx(), tile.drawy()); + Draw.rect(topRegion, tile.drawx(), tile.drawy()); - if(!isMultiblock() && isValid(tile)) { - Draw.color(tile.floor().drops.item.color); - Draw.rect("blank", tile.worldx(), tile.worldy(), 2f, 2f); - Draw.color(); - } - } + if(!isMultiblock() && isValid(tile)){ + Draw.color(tile.floor().drops.item.color); + Draw.rect("blank", tile.worldx(), tile.worldy(), 2f, 2f); + Draw.color(); + } + } - @Override - public TextureRegion[] getIcon() { - return new TextureRegion[]{Draw.region(name), Draw.region(name + "-rotator"), Draw.region(name + "-top")}; - } - - @Override - public void setStats(){ - super.setStats(); + @Override + public TextureRegion[] getIcon(){ + return new TextureRegion[]{Draw.region(name), Draw.region(name + "-rotator"), Draw.region(name + "-top")}; + } + + @Override + public void setStats(){ + super.setStats(); stats.add(BlockStat.drillTier, table -> { - Array list = new Array<>(); + Array list = new Array<>(); - for(Item item : Item.all()){ - if(tier >= item.hardness && Draw.hasRegion(item.name + "1")){ - list.add(item); - } - } + for(Item item : Item.all()){ + if(tier >= item.hardness && Draw.hasRegion(item.name + "1")){ + list.add(item); + } + } - for (int i = 0; i < list.size; i++) { - Item item = list.get(i); - table.addImage(item.name + "1").size(8*3).padRight(2).padLeft(2).padTop(3).padBottom(3); - if(i != list.size - 1){ - table.add("/"); - } - } - }); + for(int i = 0; i < list.size; i++){ + Item item = list.get(i); + table.addImage(item.name + "1").size(8 * 3).padRight(2).padLeft(2).padTop(3).padBottom(3); + if(i != list.size - 1){ + table.add("/"); + } + } + }); - stats.add(BlockStat.drillSpeed, 60f/drillTime, StatUnit.itemsSecond); - } - - @Override - public void update(Tile tile){ - toAdd.clear(); + stats.add(BlockStat.drillSpeed, 60f / drillTime, StatUnit.itemsSecond); + } - DrillEntity entity = tile.entity(); + @Override + public void update(Tile tile){ + toAdd.clear(); - float multiplier = 0f; - float totalHardness = 0f; + DrillEntity entity = tile.entity(); - for(Tile other : tile.getLinkedTiles(tempTiles)){ - if(isValid(other)){ - Item drop = getDrop(other); - toAdd.add(drop); - totalHardness += drop.hardness; - multiplier += 1f; - } - } + float multiplier = 0f; + float totalHardness = 0f; - if(entity.timer.get(timerDump, 15)){ - tryDump(tile); - } + for(Tile other : tile.getLinkedTiles(tempTiles)){ + if(isValid(other)){ + Item drop = getDrop(other); + toAdd.add(drop); + totalHardness += drop.hardness; + multiplier += 1f; + } + } - entity.drillTime += entity.warmup * Timers.delta(); + if(entity.timer.get(timerDump, 15)){ + tryDump(tile); + } - if(entity.items.total() < itemCapacity && toAdd.size > 0 && entity.cons.valid()){ + entity.drillTime += entity.warmup * Timers.delta(); - float speed = 1f; + if(entity.items.total() < itemCapacity && toAdd.size > 0 && entity.cons.valid()){ - if(entity.consumed(ConsumeLiquid.class) && !liquidRequired){ - speed = liquidBoostIntensity; - } + float speed = 1f; - entity.warmup = Mathf.lerpDelta(entity.warmup, speed, warmupSpeed); - entity.progress += Timers.delta() * multiplier * speed * entity.warmup; + if(entity.consumed(ConsumeLiquid.class) && !liquidRequired){ + speed = liquidBoostIntensity; + } - if(Mathf.chance(Timers.delta() * updateEffectChance * entity.warmup)) - Effects.effect(updateEffect, entity.x + Mathf.range(size*2f), entity.y + Mathf.range(size*2f)); - }else{ - entity.warmup = Mathf.lerpDelta(entity.warmup, 0f, warmupSpeed); - return; - } + entity.warmup = Mathf.lerpDelta(entity.warmup, speed, warmupSpeed); + entity.progress += Timers.delta() * multiplier * speed * entity.warmup; - if(toAdd.size > 0 && entity.progress >= drillTime + hardnessDrillMultiplier*Math.max(totalHardness, 1f)/multiplier - && tile.entity.items.total() < itemCapacity){ + if(Mathf.chance(Timers.delta() * updateEffectChance * entity.warmup)) + Effects.effect(updateEffect, entity.x + Mathf.range(size * 2f), entity.y + Mathf.range(size * 2f)); + }else{ + entity.warmup = Mathf.lerpDelta(entity.warmup, 0f, warmupSpeed); + return; + } - int index = entity.index % toAdd.size; - offloadNear(tile, toAdd.get(index)); + if(toAdd.size > 0 && entity.progress >= drillTime + hardnessDrillMultiplier * Math.max(totalHardness, 1f) / multiplier + && tile.entity.items.total() < itemCapacity){ - //unlock item content - if(!headless){ - control.database().unlockContent(toAdd.get(index)); - } + int index = entity.index % toAdd.size; + offloadNear(tile, toAdd.get(index)); - entity.index ++; - entity.progress = 0f; + //unlock item content + if(!headless){ + control.database().unlockContent(toAdd.get(index)); + } - Effects.effect(drillEffect, toAdd.get(index).color, - entity.x + Mathf.range(size), entity.y + Mathf.range(size)); - } - } + entity.index++; + entity.progress = 0f; - @Override - public boolean canPlaceOn(Tile tile) { - if(isMultiblock()){ - for(Tile other : tile.getLinkedTilesAs(this, drawTiles)){ - if(isValid(other)){ - return true; - } - } - return false; - }else{ - return isValid(tile); - } - } + Effects.effect(drillEffect, toAdd.get(index).color, + entity.x + Mathf.range(size), entity.y + Mathf.range(size)); + } + } - @Override - public TileEntity getEntity() { - return new DrillEntity(); - } + @Override + public boolean canPlaceOn(Tile tile){ + if(isMultiblock()){ + for(Tile other : tile.getLinkedTilesAs(this, drawTiles)){ + if(isValid(other)){ + return true; + } + } + return false; + }else{ + return isValid(tile); + } + } - public Item getDrop(Tile tile){ - return tile.floor().drops.item; - } + @Override + public TileEntity getEntity(){ + return new DrillEntity(); + } - protected boolean isValid(Tile tile){ - return tile.floor().drops != null && tile.floor().drops.item.hardness <= tier; - } + public Item getDrop(Tile tile){ + return tile.floor().drops.item; + } - public static class DrillEntity extends TileEntity{ - public float progress; - public int index; - public float warmup; - public float drillTime; - } + protected boolean isValid(Tile tile){ + return tile.floor().drops != null && tile.floor().drops.item.hardness <= tier; + } + + public static class DrillEntity extends TileEntity{ + public float progress; + public int index; + public float warmup; + public float drillTime; + } } diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Fracker.java b/core/src/io/anuke/mindustry/world/blocks/production/Fracker.java index 25507dcb32..3f3e9675ae 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Fracker.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Fracker.java @@ -8,14 +8,14 @@ import io.anuke.mindustry.world.consumers.ConsumeItem; import io.anuke.ucore.core.Timers; import io.anuke.ucore.graphics.Draw; -public class Fracker extends SolidPump { +public class Fracker extends SolidPump{ protected float itemUseTime = 100f; protected TextureRegion liquidRegion; protected TextureRegion rotatorRegion; protected TextureRegion topRegion; - public Fracker(String name) { + public Fracker(String name){ super(name); hasItems = true; itemCapacity = 20; @@ -25,7 +25,7 @@ public class Fracker extends SolidPump { } @Override - public void load() { + public void load(){ super.load(); liquidRegion = Draw.region(name + "-liquid"); @@ -34,18 +34,18 @@ public class Fracker extends SolidPump { } @Override - public void setStats() { + public void setStats(){ super.setStats(); } @Override - public void draw(Tile tile) { + public void draw(Tile tile){ FrackerEntity entity = tile.entity(); Draw.rect(region, tile.drawx(), tile.drawy()); Draw.color(result.color); - Draw.alpha(tile.entity.liquids.get(result)/liquidCapacity); + Draw.alpha(tile.entity.liquids.get(result) / liquidCapacity); Draw.rect(liquidRegion, tile.drawx(), tile.drawy()); Draw.color(); @@ -54,12 +54,12 @@ public class Fracker extends SolidPump { } @Override - public TextureRegion[] getIcon() { + public TextureRegion[] getIcon(){ return new TextureRegion[]{Draw.region(name), Draw.region(name + "-rotator"), Draw.region(name + "-top")}; } @Override - public void update(Tile tile) { + public void update(Tile tile){ FrackerEntity entity = tile.entity(); Item item = consumes.item(); @@ -77,12 +77,12 @@ public class Fracker extends SolidPump { } @Override - public TileEntity getEntity() { + public TileEntity getEntity(){ return new FrackerEntity(); } @Override - public float typeLiquid(Tile tile) { + public float typeLiquid(Tile tile){ return tile.entity.liquids.get(result); } diff --git a/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java b/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java index 8a6a67c2fd..9c0f60ba75 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java @@ -26,108 +26,108 @@ import static io.anuke.mindustry.Vars.control; import static io.anuke.mindustry.Vars.headless; public class GenericCrafter extends Block{ - protected final int timerDump = timers++; + protected final int timerDump = timers++; - protected Item output; - protected float craftTime = 80; - protected Effect craftEffect = BlockFx.purify; - protected Effect updateEffect = Fx.none; - protected float updateEffectChance = 0.04f; + protected Item output; + protected float craftTime = 80; + protected Effect craftEffect = BlockFx.purify; + protected Effect updateEffect = Fx.none; + protected float updateEffectChance = 0.04f; - public GenericCrafter(String name) { - super(name); - update = true; - solid = true; - health = 60; - } + public GenericCrafter(String name){ + super(name); + update = true; + solid = true; + health = 60; + } - @Override - public void setBars(){ - super.setBars(); + @Override + public void setBars(){ + super.setBars(); - if(consumes.has(ConsumeItem.class)) bars.replace(new BlockBar(BarType.inventory, true, - tile -> (float)tile.entity.items.get(consumes.item()) / itemCapacity)); - } - - @Override - public void setStats(){ - super.setStats(); - stats.add(BlockStat.craftSpeed, 60f/craftTime, StatUnit.itemsSecond); - stats.add(BlockStat.outputItem, output); - } - - @Override - public void draw(Tile tile){ - Draw.rect(name(), tile.drawx(), tile.drawy()); - - if(!hasLiquids) return; - - Draw.color(tile.entity.liquids.current().color); - Draw.alpha(tile.entity.liquids.total() / liquidCapacity); - Draw.rect("blank", tile.drawx(), tile.drawy(), 2, 2); - Draw.color(); - } + if(consumes.has(ConsumeItem.class)) bars.replace(new BlockBar(BarType.inventory, true, + tile -> (float) tile.entity.items.get(consumes.item()) / itemCapacity)); + } - @Override - public TextureRegion[] getIcon(){ - return new TextureRegion[]{Draw.region(name)}; - } - - @Override - public void update(Tile tile){ - GenericCrafterEntity entity = tile.entity(); + @Override + public void setStats(){ + super.setStats(); + stats.add(BlockStat.craftSpeed, 60f / craftTime, StatUnit.itemsSecond); + stats.add(BlockStat.outputItem, output); + } - if(entity.cons.valid()){ + @Override + public void draw(Tile tile){ + Draw.rect(name(), tile.drawx(), tile.drawy()); - entity.progress += 1f / craftTime * Timers.delta(); - entity.totalProgress += Timers.delta(); - entity.warmup = Mathf.lerp(entity.warmup, 1f, 0.02f); + if(!hasLiquids) return; - if(Mathf.chance(Timers.delta() * updateEffectChance)) - Effects.effect(updateEffect, entity.x + Mathf.range(size*4f), entity.y + Mathf.range(size*4)); - }else{ - entity.warmup = Mathf.lerp(entity.warmup, 0f, 0.02f); - } + Draw.color(tile.entity.liquids.current().color); + Draw.alpha(tile.entity.liquids.total() / liquidCapacity); + Draw.rect("blank", tile.drawx(), tile.drawy(), 2, 2); + Draw.color(); + } - if(entity.progress >= 1f){ - - if(consumes.has(ConsumeItem.class)) tile.entity.items.remove(consumes.item(), consumes.itemAmount()); + @Override + public TextureRegion[] getIcon(){ + return new TextureRegion[]{Draw.region(name)}; + } - //unlock output item - if(!headless){ - control.database().unlockContent(output); - } + @Override + public void update(Tile tile){ + GenericCrafterEntity entity = tile.entity(); - offloadNear(tile, output); - Effects.effect(craftEffect, tile.drawx(), tile.drawy()); - entity.progress = 0f; - } - - if(tile.entity.timer.get(timerDump, 5)){ - tryDump(tile, output); - } - } + if(entity.cons.valid()){ - @Override - public TileEntity getEntity() { - return new GenericCrafterEntity(); - } + entity.progress += 1f / craftTime * Timers.delta(); + entity.totalProgress += Timers.delta(); + entity.warmup = Mathf.lerp(entity.warmup, 1f, 0.02f); - public static class GenericCrafterEntity extends TileEntity{ - public float progress; - public float totalProgress; - public float warmup; + if(Mathf.chance(Timers.delta() * updateEffectChance)) + Effects.effect(updateEffect, entity.x + Mathf.range(size * 4f), entity.y + Mathf.range(size * 4)); + }else{ + entity.warmup = Mathf.lerp(entity.warmup, 0f, 0.02f); + } - @Override - public void write(DataOutputStream stream) throws IOException { - stream.writeFloat(progress); - stream.writeFloat(warmup); - } + if(entity.progress >= 1f){ - @Override - public void read(DataInputStream stream) throws IOException { - progress = stream.readFloat(); - warmup = stream.readFloat(); - } - } + if(consumes.has(ConsumeItem.class)) tile.entity.items.remove(consumes.item(), consumes.itemAmount()); + + //unlock output item + if(!headless){ + control.database().unlockContent(output); + } + + offloadNear(tile, output); + Effects.effect(craftEffect, tile.drawx(), tile.drawy()); + entity.progress = 0f; + } + + if(tile.entity.timer.get(timerDump, 5)){ + tryDump(tile, output); + } + } + + @Override + public TileEntity getEntity(){ + return new GenericCrafterEntity(); + } + + public static class GenericCrafterEntity extends TileEntity{ + public float progress; + public float totalProgress; + public float warmup; + + @Override + public void write(DataOutputStream stream) throws IOException{ + stream.writeFloat(progress); + stream.writeFloat(warmup); + } + + @Override + public void read(DataInputStream stream) throws IOException{ + progress = stream.readFloat(); + warmup = stream.readFloat(); + } + } } diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Incinerator.java b/core/src/io/anuke/mindustry/world/blocks/production/Incinerator.java index 00b8997d07..ed8697ad2a 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Incinerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Incinerator.java @@ -15,11 +15,11 @@ import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.graphics.Fill; import io.anuke.ucore.util.Mathf; -public class Incinerator extends Block { +public class Incinerator extends Block{ protected Effect effect = BlockFx.fuelburn; protected Color flameColor = Color.valueOf("ffad9d"); - public Incinerator(String name) { + public Incinerator(String name){ super(name); hasPower = true; hasLiquids = true; @@ -30,13 +30,13 @@ public class Incinerator extends Block { } @Override - public void setBars() { + public void setBars(){ super.setBars(); bars.remove(BarType.liquid); } @Override - public void update(Tile tile) { + public void update(Tile tile){ IncineratorEntity entity = tile.entity(); if(entity.cons.valid()){ @@ -47,7 +47,7 @@ public class Incinerator extends Block { } @Override - public void draw(Tile tile) { + public void draw(Tile tile){ super.draw(tile); IncineratorEntity entity = tile.entity(); @@ -56,7 +56,7 @@ public class Incinerator extends Block { float g = 0.3f; float r = 0.06f; - Draw.alpha(((1f-g) + Mathf.absin(Timers.time(), 8f, g) + Mathf.random(r) - r) * entity.heat); + Draw.alpha(((1f - g) + Mathf.absin(Timers.time(), 8f, g) + Mathf.random(r) - r) * entity.heat); Draw.tint(flameColor); Fill.circle(tile.drawx(), tile.drawy(), 2f); @@ -68,33 +68,33 @@ public class Incinerator extends Block { } @Override - public void handleItem(Item item, Tile tile, Tile source) { + public void handleItem(Item item, Tile tile, Tile source){ if(Mathf.chance(0.3)){ Effects.effect(effect, tile.drawx(), tile.drawy()); } } @Override - public boolean acceptItem(Item item, Tile tile, Tile source) { + public boolean acceptItem(Item item, Tile tile, Tile source){ IncineratorEntity entity = tile.entity(); return entity.heat > 0.5f; } @Override - public void handleLiquid(Tile tile, Tile source, Liquid liquid, float amount) { + public void handleLiquid(Tile tile, Tile source, Liquid liquid, float amount){ if(Mathf.chance(0.02)){ Effects.effect(effect, tile.drawx(), tile.drawy()); } } @Override - public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount) { + public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){ IncineratorEntity entity = tile.entity(); return entity.heat > 0.5f; } @Override - public TileEntity getEntity() { + public TileEntity getEntity(){ return new IncineratorEntity(); } diff --git a/core/src/io/anuke/mindustry/world/blocks/production/LiquidMixer.java b/core/src/io/anuke/mindustry/world/blocks/production/LiquidMixer.java index e99fc47c36..11733cf392 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/LiquidMixer.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/LiquidMixer.java @@ -16,7 +16,7 @@ public class LiquidMixer extends LiquidBlock{ protected Liquid outputLiquid; protected float liquidPerItem = 50f; - public LiquidMixer(String name) { + public LiquidMixer(String name){ super(name); hasItems = true; rotate = false; @@ -25,14 +25,14 @@ public class LiquidMixer extends LiquidBlock{ } @Override - public void setStats() { + public void setStats(){ super.setStats(); stats.add(BlockStat.liquidOutput, outputLiquid); } @Override - public void setBars() { + public void setBars(){ super.setBars(); bars.remove(BarType.liquid); @@ -53,7 +53,7 @@ public class LiquidMixer extends LiquidBlock{ float use = Math.min(consumes.get(ConsumeLiquid.class).used() * Timers.delta(), liquidCapacity - entity.liquids.get(outputLiquid)); entity.accumulator += use; entity.liquids.add(outputLiquid, use); - for (int i = 0; i < (int)(entity.accumulator / liquidPerItem); i++) { + for(int i = 0; i < (int) (entity.accumulator / liquidPerItem); i++){ if(!entity.items.has(consumes.item())) break; entity.items.remove(consumes.item(), 1); entity.accumulator -= liquidPerItem; @@ -82,11 +82,11 @@ public class LiquidMixer extends LiquidBlock{ } @Override - public TileEntity getEntity() { + public TileEntity getEntity(){ return new LiquidMixerEntity(); } - static class LiquidMixerEntity extends TileEntity { + static class LiquidMixerEntity extends TileEntity{ float accumulator; } } diff --git a/core/src/io/anuke/mindustry/world/blocks/production/PhaseWeaver.java b/core/src/io/anuke/mindustry/world/blocks/production/PhaseWeaver.java index d58545f44f..5a7369d1aa 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/PhaseWeaver.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/PhaseWeaver.java @@ -14,12 +14,12 @@ public class PhaseWeaver extends PowerSmelter{ protected TextureRegion bottomRegion; protected TextureRegion weaveRegion; - public PhaseWeaver(String name) { + public PhaseWeaver(String name){ super(name); } @Override - public void load() { + public void load(){ super.load(); bottomRegion = Draw.region(name + "-bottom"); @@ -27,7 +27,7 @@ public class PhaseWeaver extends PowerSmelter{ } @Override - public TextureRegion[] getIcon() { + public TextureRegion[] getIcon(){ if(icon == null){ icon = new TextureRegion[]{Draw.region(name + "-bottom"), Draw.region(name)}; } @@ -35,7 +35,7 @@ public class PhaseWeaver extends PowerSmelter{ } @Override - public void draw(Tile tile) { + public void draw(Tile tile){ PowerSmelterEntity entity = tile.entity(); Draw.rect(bottomRegion, tile.drawx(), tile.drawy()); @@ -59,7 +59,7 @@ public class PhaseWeaver extends PowerSmelter{ tile.drawx() + Mathf.sin(entity.time, 6f, Vars.tilesize / 3f * size), tile.drawy(), 90, - size * Vars.tilesize /2f); + size * Vars.tilesize / 2f); Draw.reset(); } diff --git a/core/src/io/anuke/mindustry/world/blocks/production/PlastaniumCompressor.java b/core/src/io/anuke/mindustry/world/blocks/production/PlastaniumCompressor.java index faf664d788..0a8c5fda3f 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/PlastaniumCompressor.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/PlastaniumCompressor.java @@ -4,14 +4,14 @@ import io.anuke.mindustry.world.Tile; import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.util.Mathf; -public class PlastaniumCompressor extends GenericCrafter { +public class PlastaniumCompressor extends GenericCrafter{ - public PlastaniumCompressor(String name) { + public PlastaniumCompressor(String name){ super(name); } @Override - public void draw(Tile tile) { + public void draw(Tile tile){ super.draw(tile); GenericCrafterEntity entity = tile.entity(); diff --git a/core/src/io/anuke/mindustry/world/blocks/production/PowerCrafter.java b/core/src/io/anuke/mindustry/world/blocks/production/PowerCrafter.java index 18639a72e1..c43860227f 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/PowerCrafter.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/PowerCrafter.java @@ -12,14 +12,18 @@ import io.anuke.ucore.core.Timers; public class PowerCrafter extends Block{ protected final int timerDump = timers++; - /**Optional.*/ + /** + * Optional. + */ protected Item outputItem; - /**Optional. Set hasLiquids to true when using.*/ + /** + * Optional. Set hasLiquids to true when using. + */ protected Liquid outputLiquid; protected float outputLiquidAmount; protected float craftTime; - public PowerCrafter(String name) { + public PowerCrafter(String name){ super(name); solid = true; update = true; @@ -28,7 +32,7 @@ public class PowerCrafter extends Block{ } @Override - public void init() { + public void init(){ super.init(); if(outputLiquid != null){ @@ -37,7 +41,7 @@ public class PowerCrafter extends Block{ } @Override - public void setStats() { + public void setStats(){ super.setStats(); if(outputItem != null){ @@ -50,11 +54,11 @@ public class PowerCrafter extends Block{ } @Override - public void update(Tile tile) { + public void update(Tile tile){ GenericCrafterEntity entity = tile.entity(); if(entity.cons.valid()){ - entity.progress += 1f/craftTime; + entity.progress += 1f / craftTime; entity.totalProgress += Timers.delta(); } @@ -75,7 +79,7 @@ public class PowerCrafter extends Block{ } @Override - public TileEntity getEntity() { + public TileEntity getEntity(){ return new GenericCrafterEntity(); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/production/PowerSmelter.java b/core/src/io/anuke/mindustry/world/blocks/production/PowerSmelter.java index 67b18228b8..ebb561aa12 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/PowerSmelter.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/PowerSmelter.java @@ -23,7 +23,7 @@ import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -public class PowerSmelter extends PowerBlock { +public class PowerSmelter extends PowerBlock{ protected final int timerDump = timers++; protected final int timerCraft = timers++; @@ -45,7 +45,7 @@ public class PowerSmelter extends PowerBlock { protected TextureRegion topRegion; - public PowerSmelter(String name) { + public PowerSmelter(String name){ super(name); hasItems = true; update = true; @@ -54,7 +54,7 @@ public class PowerSmelter extends PowerBlock { } @Override - public void load() { + public void load(){ super.load(); topRegion = Draw.region(name + "-top"); } @@ -74,7 +74,7 @@ public class PowerSmelter extends PowerBlock { super.setStats(); stats.add(BlockStat.outputItem, result); - stats.add(BlockStat.craftSpeed, 60f/craftTime, StatUnit.itemsSecond); + stats.add(BlockStat.craftSpeed, 60f / craftTime, StatUnit.itemsSecond); stats.add(BlockStat.inputItemCapacity, itemCapacity, StatUnit.items); stats.add(BlockStat.outputItemCapacity, itemCapacity, StatUnit.items); } @@ -92,7 +92,7 @@ public class PowerSmelter extends PowerBlock { if(entity.cons.valid()){ entity.heat += 1f / heatUpTime * Timers.delta(); if(Mathf.chance(Timers.delta() * burnEffectChance)) - Effects.effect(burnEffect, entity.x + Mathf.range(size*4f), entity.y + Mathf.range(size*4)); + Effects.effect(burnEffect, entity.x + Mathf.range(size * 4f), entity.y + Mathf.range(size * 4)); }else{ entity.heat -= 1f / heatUpTime * Timers.delta(); } @@ -125,8 +125,8 @@ public class PowerSmelter extends PowerBlock { } } - if(consumeInputs) { - for (ItemStack item : consumes.items()) { + if(consumeInputs){ + for(ItemStack item : consumes.items()){ entity.items.remove(item.item, item.amount); } } @@ -152,7 +152,7 @@ public class PowerSmelter extends PowerBlock { } @Override - public int getMaximumAccepted(Tile tile, Item item) { + public int getMaximumAccepted(Tile tile, Item item){ return itemCapacity - tile.entity.items.get(item); } @@ -168,7 +168,7 @@ public class PowerSmelter extends PowerBlock { float r = 0.06f; float cr = Mathf.random(0.1f); - Draw.alpha(((1f-g) + Mathf.absin(Timers.time(), 8f, g) + Mathf.random(r) - r) * entity.heat); + Draw.alpha(((1f - g) + Mathf.absin(Timers.time(), 8f, g) + Mathf.random(r) - r) * entity.heat); Draw.tint(flameColor); Fill.circle(tile.drawx(), tile.drawy(), 3f + Mathf.absin(Timers.time(), 5f, 2f) + cr); @@ -181,7 +181,7 @@ public class PowerSmelter extends PowerBlock { } @Override - public TileEntity getEntity() { + public TileEntity getEntity(){ return new PowerSmelterEntity(); } @@ -190,12 +190,12 @@ public class PowerSmelter extends PowerBlock { public float time; @Override - public void write(DataOutputStream stream) throws IOException { + public void write(DataOutputStream stream) throws IOException{ stream.writeFloat(heat); } @Override - public void read(DataInputStream stream) throws IOException { + public void read(DataInputStream stream) throws IOException{ heat = stream.readFloat(); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Pulverizer.java b/core/src/io/anuke/mindustry/world/blocks/production/Pulverizer.java index c96c0ec054..72f80e138b 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Pulverizer.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Pulverizer.java @@ -4,23 +4,23 @@ import com.badlogic.gdx.graphics.g2d.TextureRegion; import io.anuke.mindustry.world.Tile; import io.anuke.ucore.graphics.Draw; -public class Pulverizer extends GenericCrafter { +public class Pulverizer extends GenericCrafter{ protected TextureRegion rotatorRegion; - public Pulverizer(String name) { + public Pulverizer(String name){ super(name); hasItems = true; } @Override - public void load() { + public void load(){ super.load(); rotatorRegion = Draw.region(name + "-rotator"); } @Override - public void draw(Tile tile) { + public void draw(Tile tile){ GenericCrafterEntity entity = tile.entity(); Draw.rect(region, tile.drawx(), tile.drawy()); @@ -28,7 +28,7 @@ public class Pulverizer extends GenericCrafter { } @Override - public TextureRegion[] getIcon() { + public TextureRegion[] getIcon(){ return new TextureRegion[]{Draw.region(name), Draw.region(name + "-rotator")}; } } diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Pump.java b/core/src/io/anuke/mindustry/world/blocks/production/Pump.java index 97c6129e32..448f840801 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Pump.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Pump.java @@ -13,97 +13,101 @@ import io.anuke.ucore.core.Timers; import io.anuke.ucore.graphics.Draw; public class Pump extends LiquidBlock{ - protected final Array drawTiles = new Array<>(); - protected final Array updateTiles = new Array<>(); + protected final Array drawTiles = new Array<>(); + protected final Array updateTiles = new Array<>(); - /**Pump amount per tile this block is on.*/ - protected float pumpAmount = 1f; - /**Maximum liquid tier this pump can use.*/ - protected int tier = 0; + /** + * Pump amount per tile this block is on. + */ + protected float pumpAmount = 1f; + /** + * Maximum liquid tier this pump can use. + */ + protected int tier = 0; - public Pump(String name) { - super(name); - layer = Layer.overlay; - liquidFlowFactor = 3f; - group = BlockGroup.liquids; - floating = true; - } + public Pump(String name){ + super(name); + layer = Layer.overlay; + liquidFlowFactor = 3f; + group = BlockGroup.liquids; + floating = true; + } - @Override - public void load() { - super.load(); + @Override + public void load(){ + super.load(); - liquidRegion = Draw.region("pump-liquid"); - } + liquidRegion = Draw.region("pump-liquid"); + } - @Override - public void setStats(){ - super.setStats(); - stats.add(BlockStat.liquidOutput, 60f*pumpAmount, StatUnit.liquidSecond); - } - - @Override - public void draw(Tile tile){ - Draw.rect(name(), tile.drawx(), tile.drawy()); - - Draw.color(tile.entity.liquids.current().color); - Draw.alpha(tile.entity.liquids.total() / liquidCapacity); - Draw.rect(liquidRegion, tile.drawx(), tile.drawy()); - Draw.color(); - } + @Override + public void setStats(){ + super.setStats(); + stats.add(BlockStat.liquidOutput, 60f * pumpAmount, StatUnit.liquidSecond); + } - @Override - public TextureRegion[] getIcon(){ - return new TextureRegion[]{Draw.region(name)}; - } + @Override + public void draw(Tile tile){ + Draw.rect(name(), tile.drawx(), tile.drawy()); - @Override - public boolean canPlaceOn(Tile tile) { - if(isMultiblock()){ - Liquid last = null; - for(Tile other : tile.getLinkedTilesAs(this, drawTiles)){ - //can't place pump on block with multiple liquids - if(last != null && other.floor().liquidDrop != last){ - return false; - } + Draw.color(tile.entity.liquids.current().color); + Draw.alpha(tile.entity.liquids.total() / liquidCapacity); + Draw.rect(liquidRegion, tile.drawx(), tile.drawy()); + Draw.color(); + } - if(isValid(other)){ - last = other.floor().liquidDrop; - } - } - return last != null; - }else{ - return isValid(tile); - } - } - - @Override - public void update(Tile tile){ - float tiles = 0f; - Liquid liquidDrop = null; + @Override + public TextureRegion[] getIcon(){ + return new TextureRegion[]{Draw.region(name)}; + } - if(isMultiblock()){ - for(Tile other : tile.getLinkedTiles(updateTiles)){ - if(isValid(other)){ - liquidDrop = other.floor().liquidDrop; - tiles ++; - } - } - }else{ - tiles = 1f; - liquidDrop = tile.floor().liquidDrop; - } + @Override + public boolean canPlaceOn(Tile tile){ + if(isMultiblock()){ + Liquid last = null; + for(Tile other : tile.getLinkedTilesAs(this, drawTiles)){ + //can't place pump on block with multiple liquids + if(last != null && other.floor().liquidDrop != last){ + return false; + } - if(tile.entity.cons.valid() && liquidDrop != null){ - float maxPump = Math.min(liquidCapacity - tile.entity.liquids.total(), tiles * pumpAmount * Timers.delta()); - tile.entity.liquids.add(liquidDrop, maxPump); - } + if(isValid(other)){ + last = other.floor().liquidDrop; + } + } + return last != null; + }else{ + return isValid(tile); + } + } - tryDumpLiquid(tile, tile.entity.liquids.current()); - } + @Override + public void update(Tile tile){ + float tiles = 0f; + Liquid liquidDrop = null; - protected boolean isValid(Tile tile){ - return tile.floor().liquidDrop != null && tier >= tile.floor().liquidDrop.tier; - } + if(isMultiblock()){ + for(Tile other : tile.getLinkedTiles(updateTiles)){ + if(isValid(other)){ + liquidDrop = other.floor().liquidDrop; + tiles++; + } + } + }else{ + tiles = 1f; + liquidDrop = tile.floor().liquidDrop; + } + + if(tile.entity.cons.valid() && liquidDrop != null){ + float maxPump = Math.min(liquidCapacity - tile.entity.liquids.total(), tiles * pumpAmount * Timers.delta()); + tile.entity.liquids.add(liquidDrop, maxPump); + } + + tryDumpLiquid(tile, tile.entity.liquids.current()); + } + + protected boolean isValid(Tile tile){ + return tile.floor().liquidDrop != null && tier >= tile.floor().liquidDrop.tier; + } } diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Separator.java b/core/src/io/anuke/mindustry/world/blocks/production/Separator.java index 9ba2cb2d7c..583885d450 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Separator.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Separator.java @@ -16,9 +16,11 @@ import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.graphics.Lines; import io.anuke.ucore.util.Mathf; -/**Extracts a random list of items from an input item and an input liquid.*/ -public class Separator extends Block { - protected final int timerDump = timers ++; +/** + * Extracts a random list of items from an input item and an input liquid. + */ +public class Separator extends Block{ + protected final int timerDump = timers++; protected Item[] results; protected float filterTime; @@ -32,7 +34,7 @@ public class Separator extends Block { protected boolean offloading = false; - public Separator(String name) { + public Separator(String name){ super(name); update = true; solid = true; @@ -44,14 +46,14 @@ public class Separator extends Block { } @Override - public void load() { + public void load(){ super.load(); liquidRegion = Draw.region(name + "-liquid"); } @Override - public void setStats() { + public void setStats(){ super.setStats(); stats.add(BlockStat.outputItem, new ItemFilterValue(item -> { @@ -63,7 +65,7 @@ public class Separator extends Block { } @Override - public void draw(Tile tile) { + public void draw(Tile tile){ super.draw(tile); GenericCrafterEntity entity = tile.entity(); @@ -74,18 +76,18 @@ public class Separator extends Block { Draw.color(color); Lines.stroke(spinnerThickness); - Lines.spikes(tile.drawx(), tile.drawy(), spinnerRadius, spinnerLength, 3, entity.totalProgress*spinnerSpeed); + Lines.spikes(tile.drawx(), tile.drawy(), spinnerRadius, spinnerLength, 3, entity.totalProgress * spinnerSpeed); Draw.reset(); } @Override - public void update(Tile tile) { + public void update(Tile tile){ GenericCrafterEntity entity = tile.entity(); - entity.totalProgress += entity.warmup*Timers.delta(); + entity.totalProgress += entity.warmup * Timers.delta(); if(entity.cons.valid()){ - entity.progress += 1f/filterTime; + entity.progress += 1f / filterTime; entity.warmup = Mathf.lerpDelta(entity.warmup, 1f, 0.02f); }else{ entity.warmup = Mathf.lerpDelta(entity.warmup, 0f, 0.02f); @@ -108,12 +110,12 @@ public class Separator extends Block { } @Override - public boolean canDump(Tile tile, Tile to, Item item) { + public boolean canDump(Tile tile, Tile to, Item item){ return offloading || item != consumes.item(); } @Override - public TileEntity getEntity() { + public TileEntity getEntity(){ return new GenericCrafterEntity(); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Smelter.java b/core/src/io/anuke/mindustry/world/blocks/production/Smelter.java index b28847fb7e..1f347070d9 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Smelter.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Smelter.java @@ -21,169 +21,169 @@ import io.anuke.ucore.graphics.Fill; import io.anuke.ucore.util.Mathf; public class Smelter extends Block{ - protected final int timerDump = timers++; - protected final int timerCraft = timers++; + protected final int timerDump = timers++; + protected final int timerCraft = timers++; - protected Item result; + protected Item result; - protected float minFlux = 0.2f; - protected float baseFluxChance = 0.15f; - protected boolean useFlux = false; + protected float minFlux = 0.2f; + protected float baseFluxChance = 0.15f; + protected boolean useFlux = false; - protected float craftTime = 20f; - protected float burnDuration = 50f; - protected Effect craftEffect = BlockFx.smelt, burnEffect = BlockFx.fuelburn; - protected Color flameColor = Color.valueOf("ffb879"); + protected float craftTime = 20f; + protected float burnDuration = 50f; + protected Effect craftEffect = BlockFx.smelt, burnEffect = BlockFx.fuelburn; + protected Color flameColor = Color.valueOf("ffb879"); - public Smelter(String name) { - super(name); - update = true; - hasItems = true; - solid = true; - itemCapacity = 20; + public Smelter(String name){ + super(name); + update = true; + hasItems = true; + solid = true; + itemCapacity = 20; - consumes.require(ConsumeItems.class); - consumes.require(ConsumeItem.class); - } + consumes.require(ConsumeItems.class); + consumes.require(ConsumeItem.class); + } - @Override - public void setBars(){ - for(ItemStack item : consumes.items()){ - bars.add(new BlockBar(BarType.inventory, true, tile -> (float)tile.entity.items.get(item.item)/itemCapacity)); - } - } - - @Override - public void setStats(){ - super.setStats(); + @Override + public void setBars(){ + for(ItemStack item : consumes.items()){ + bars.add(new BlockBar(BarType.inventory, true, tile -> (float) tile.entity.items.get(item.item) / itemCapacity)); + } + } - //TODO - //stats.add(BlockStat.inputFuel, fuel); - stats.add(BlockStat.fuelBurnTime, burnDuration/60f, StatUnit.seconds); - stats.add(BlockStat.outputItem, result); - stats.add(BlockStat.craftSpeed, 60f/craftTime, StatUnit.itemsSecond); - stats.add(BlockStat.inputItemCapacity, itemCapacity, StatUnit.items); - stats.add(BlockStat.outputItemCapacity, itemCapacity, StatUnit.items); - } + @Override + public void setStats(){ + super.setStats(); - @Override - public void init() { - super.init(); + //TODO + //stats.add(BlockStat.inputFuel, fuel); + stats.add(BlockStat.fuelBurnTime, burnDuration / 60f, StatUnit.seconds); + stats.add(BlockStat.outputItem, result); + stats.add(BlockStat.craftSpeed, 60f / craftTime, StatUnit.itemsSecond); + stats.add(BlockStat.inputItemCapacity, itemCapacity, StatUnit.items); + stats.add(BlockStat.outputItemCapacity, itemCapacity, StatUnit.items); + } - for(ItemStack item : consumes.items()){ - if(item.item.fluxiness >= minFlux && useFlux){ - throw new IllegalArgumentException("'" + name + "' has input item '" + item.item.name + "', which is a flux, when useFlux is enabled. To prevent ambiguous item use, either remove this flux item from the inputs, or set useFlux to false."); - } - } - } + @Override + public void init(){ + super.init(); - @Override - public void update(Tile tile){ - SmelterEntity entity = tile.entity(); - - if(entity.timer.get(timerDump, 5) && entity.items.has(result)){ - tryDump(tile, result); - } + for(ItemStack item : consumes.items()){ + if(item.item.fluxiness >= minFlux && useFlux){ + throw new IllegalArgumentException("'" + name + "' has input item '" + item.item.name + "', which is a flux, when useFlux is enabled. To prevent ambiguous item use, either remove this flux item from the inputs, or set useFlux to false."); + } + } + } - //add fuel - if(entity.consumed(ConsumeItem.class) && entity.burnTime <= 0f){ - entity.items.remove(consumes.item(), 1); - entity.burnTime += burnDuration; - Effects.effect(burnEffect, entity.x + Mathf.range(2f), entity.y + Mathf.range(2f)); - } + @Override + public void update(Tile tile){ + SmelterEntity entity = tile.entity(); - //decrement burntime - if(entity.burnTime > 0){ - entity.burnTime -= Timers.delta(); - entity.heat = Mathf.lerp(entity.heat, 1f, 0.02f); - }else{ - entity.heat = Mathf.lerp(entity.heat, 0f, 0.02f); - } + if(entity.timer.get(timerDump, 5) && entity.items.has(result)){ + tryDump(tile, result); + } - //make sure it has all the items - if(!entity.cons.valid()){ - return; - } + //add fuel + if(entity.consumed(ConsumeItem.class) && entity.burnTime <= 0f){ + entity.items.remove(consumes.item(), 1); + entity.burnTime += burnDuration; + Effects.effect(burnEffect, entity.x + Mathf.range(2f), entity.y + Mathf.range(2f)); + } - if(entity.items.get(result) >= itemCapacity //output full - || entity.burnTime <= 0 //not burning - || !entity.timer.get(timerCraft, craftTime)){ //not yet time - return; - } + //decrement burntime + if(entity.burnTime > 0){ + entity.burnTime -= Timers.delta(); + entity.heat = Mathf.lerp(entity.heat, 1f, 0.02f); + }else{ + entity.heat = Mathf.lerp(entity.heat, 0f, 0.02f); + } - boolean consumeInputs = true; + //make sure it has all the items + if(!entity.cons.valid()){ + return; + } - if(useFlux){ - //remove flux materials if present - for(Item item : Item.all()){ - if(item.fluxiness >= minFlux && tile.entity.items.get(item) > 0){ - tile.entity.items.remove(item, 1); + if(entity.items.get(result) >= itemCapacity //output full + || entity.burnTime <= 0 //not burning + || !entity.timer.get(timerCraft, craftTime)){ //not yet time + return; + } - //chance of not consuming inputs if flux material present - consumeInputs = !Mathf.chance(item.fluxiness * baseFluxChance); - break; - } - } - } + boolean consumeInputs = true; - if(consumeInputs) { - for (ItemStack item : consumes.items()) { - entity.items.remove(item.item, item.amount); - } - } - - offloadNear(tile, result); - Effects.effect(craftEffect, flameColor, tile.drawx(), tile.drawy()); - } + if(useFlux){ + //remove flux materials if present + for(Item item : Item.all()){ + if(item.fluxiness >= minFlux && tile.entity.items.get(item) > 0){ + tile.entity.items.remove(item, 1); - @Override - public int getMaximumAccepted(Tile tile, Item item) { - return itemCapacity - tile.entity.items.get(item); - } + //chance of not consuming inputs if flux material present + consumeInputs = !Mathf.chance(item.fluxiness * baseFluxChance); + break; + } + } + } - @Override - public boolean acceptItem(Item item, Tile tile, Tile source){ - boolean isInput = false; + if(consumeInputs){ + for(ItemStack item : consumes.items()){ + entity.items.remove(item.item, item.amount); + } + } - for(ItemStack req : consumes.items()){ - if(req.item == item){ - isInput = true; - break; - } - } + offloadNear(tile, result); + Effects.effect(craftEffect, flameColor, tile.drawx(), tile.drawy()); + } - return (isInput && tile.entity.items.get(item) < itemCapacity) || (item == consumes.item() && tile.entity.items.get(consumes.item()) < itemCapacity) || - (useFlux && item.fluxiness >= minFlux && tile.entity.items.get(item) < itemCapacity); - } + @Override + public int getMaximumAccepted(Tile tile, Item item){ + return itemCapacity - tile.entity.items.get(item); + } - @Override - public void draw(Tile tile){ - super.draw(tile); + @Override + public boolean acceptItem(Item item, Tile tile, Tile source){ + boolean isInput = false; + + for(ItemStack req : consumes.items()){ + if(req.item == item){ + isInput = true; + break; + } + } + + return (isInput && tile.entity.items.get(item) < itemCapacity) || (item == consumes.item() && tile.entity.items.get(consumes.item()) < itemCapacity) || + (useFlux && item.fluxiness >= minFlux && tile.entity.items.get(item) < itemCapacity); + } + + @Override + public void draw(Tile tile){ + super.draw(tile); SmelterEntity entity = tile.entity(); //draw glowing center - if(entity.heat > 0f){ - float g = 0.1f; + if(entity.heat > 0f){ + float g = 0.1f; - Draw.alpha(((1f-g) + Mathf.absin(Timers.time(), 8f, g)) * entity.heat); + Draw.alpha(((1f - g) + Mathf.absin(Timers.time(), 8f, g)) * entity.heat); - Draw.tint(flameColor); - Fill.circle(tile.drawx(), tile.drawy(), 2f + Mathf.absin(Timers.time(), 5f, 0.8f)); - Draw.color(1f, 1f, 1f, entity.heat); - Fill.circle(tile.drawx(), tile.drawy(), 1f + Mathf.absin(Timers.time(), 5f, 0.7f)); + Draw.tint(flameColor); + Fill.circle(tile.drawx(), tile.drawy(), 2f + Mathf.absin(Timers.time(), 5f, 0.8f)); + Draw.color(1f, 1f, 1f, entity.heat); + Fill.circle(tile.drawx(), tile.drawy(), 1f + Mathf.absin(Timers.time(), 5f, 0.7f)); - Draw.color(); - } + Draw.color(); + } } - @Override - public TileEntity getEntity() { - return new SmelterEntity(); - } + @Override + public TileEntity getEntity(){ + return new SmelterEntity(); + } - public class SmelterEntity extends TileEntity{ - public float burnTime; - public float heat; - } + public class SmelterEntity extends TileEntity{ + public float burnTime; + public float heat; + } } diff --git a/core/src/io/anuke/mindustry/world/blocks/production/SolidPump.java b/core/src/io/anuke/mindustry/world/blocks/production/SolidPump.java index 76993b79ad..49523b350b 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/SolidPump.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/SolidPump.java @@ -13,8 +13,10 @@ import io.anuke.ucore.core.Timers; import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.util.Mathf; -/**Pump that makes liquid from solids and takes in power. Only works on solid floor blocks.*/ -public class SolidPump extends Pump { +/** + * Pump that makes liquid from solids and takes in power. Only works on solid floor blocks. + */ +public class SolidPump extends Pump{ protected Liquid result = Liquids.water; protected Effect updateEffect = Fx.none; protected float updateEffectChance = 0.02f; @@ -26,14 +28,14 @@ public class SolidPump extends Pump { } @Override - public void load() { + public void load(){ super.load(); liquidRegion = Draw.region(name + "-liquid"); } @Override - public void setStats() { + public void setStats(){ super.setStats(); stats.remove(BlockStat.liquidOutput); @@ -41,7 +43,7 @@ public class SolidPump extends Pump { } @Override - public void draw(Tile tile) { + public void draw(Tile tile){ SolidPumpEntity entity = tile.entity(); Draw.rect(region, tile.drawx(), tile.drawy()); @@ -54,7 +56,7 @@ public class SolidPump extends Pump { } @Override - public TextureRegion[] getIcon() { + public TextureRegion[] getIcon(){ return new TextureRegion[]{Draw.region(name), Draw.region(name + "-rotator"), Draw.region(name + "-top")}; } @@ -67,7 +69,7 @@ public class SolidPump extends Pump { if(isMultiblock()){ for(Tile other : tile.getLinkedTiles(tempTiles)){ if(isValid(other)){ - fraction += 1f/ size; + fraction += 1f / size; } } }else{ @@ -79,7 +81,7 @@ public class SolidPump extends Pump { tile.entity.liquids.add(result, maxPump); entity.warmup = Mathf.lerpDelta(entity.warmup, 1f, 0.02f); if(Mathf.chance(Timers.delta() * updateEffectChance)) - Effects.effect(updateEffect, entity.x + Mathf.range(size*2f), entity.y + Mathf.range(size*2f)); + Effects.effect(updateEffect, entity.x + Mathf.range(size * 2f), entity.y + Mathf.range(size * 2f)); }else{ entity.warmup = Mathf.lerpDelta(entity.warmup, 0f, 0.02f); } @@ -90,7 +92,7 @@ public class SolidPump extends Pump { } @Override - public boolean canPlaceOn(Tile tile) { + public boolean canPlaceOn(Tile tile){ if(isMultiblock()){ for(Tile other : tile.getLinkedTilesAs(this, drawTiles)){ if(isValid(other)){ @@ -109,7 +111,7 @@ public class SolidPump extends Pump { } @Override - public TileEntity getEntity() { + public TileEntity getEntity(){ return new SolidPumpEntity(); } diff --git a/core/src/io/anuke/mindustry/world/blocks/storage/CoreBlock.java b/core/src/io/anuke/mindustry/world/blocks/storage/CoreBlock.java index de992e1dcc..9d921ccc5a 100644 --- a/core/src/io/anuke/mindustry/world/blocks/storage/CoreBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/storage/CoreBlock.java @@ -39,20 +39,20 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.*; -public class CoreBlock extends StorageBlock { +public class CoreBlock extends StorageBlock{ private static Rectangle rect = new Rectangle(); - protected int timerSupply = timers ++; + protected int timerSupply = timers++; protected float supplyRadius = 50f; protected float supplyInterval = 5f; - protected float droneRespawnDuration = 60*6; + protected float droneRespawnDuration = 60 * 6; protected UnitType droneType = UnitTypes.drone; protected TextureRegion openRegion; protected TextureRegion topRegion; - public CoreBlock(String name) { + public CoreBlock(String name){ super(name); solid = false; @@ -66,15 +66,37 @@ public class CoreBlock extends StorageBlock { flags = EnumSet.of(BlockFlag.resupplyPoint, BlockFlag.target); } + @Remote(called = Loc.server, in = In.blocks) + public static void onUnitRespawn(Tile tile, Unit player){ + if(player == null) return; + + CoreEntity entity = tile.entity(); + Effects.effect(Fx.spawn, entity); + entity.solid = false; + entity.progress = 0; + entity.currentUnit = player; + entity.currentUnit.heal(); + entity.currentUnit.rotation = 90f; + entity.currentUnit.setNet(tile.drawx(), tile.drawy()); + entity.currentUnit.add(); + entity.currentUnit = null; + } + + @Remote(called = Loc.server, in = In.blocks) + public static void setCoreSolid(Tile tile, boolean solid){ + CoreEntity entity = tile.entity(); + entity.solid = solid; + } + @Override - public void setBars() { + public void setBars(){ super.setBars(); bars.remove(BarType.inventory); } @Override - public void load() { + public void load(){ super.load(); openRegion = Draw.region(name + "-open"); @@ -82,12 +104,12 @@ public class CoreBlock extends StorageBlock { } @Override - public float handleDamage(Tile tile, float amount) { + public float handleDamage(Tile tile, float amount){ return debug ? 0 : amount; } @Override - public void draw(Tile tile) { + public void draw(Tile tile){ CoreEntity entity = tile.entity(); Draw.rect(entity.solid ? Draw.region(name) : openRegion, tile.drawx(), tile.drawy()); @@ -96,7 +118,7 @@ public class CoreBlock extends StorageBlock { Draw.rect(topRegion, tile.drawx(), tile.drawy()); Draw.color(); - if(entity.currentUnit != null) { + if(entity.currentUnit != null){ Unit player = entity.currentUnit; TextureRegion region = player.getIconRegion(); @@ -117,14 +139,14 @@ public class CoreBlock extends StorageBlock { tile.drawx() + Mathf.sin(entity.time, 6f, Vars.tilesize / 3f * size), tile.drawy(), 90, - size * Vars.tilesize /2f); + size * Vars.tilesize / 2f); Draw.reset(); } } @Override - public boolean isSolidFor(Tile tile) { + public boolean isSolidFor(Tile tile){ CoreEntity entity = tile.entity(); return entity.solid; @@ -140,7 +162,7 @@ public class CoreBlock extends StorageBlock { } @Override - public boolean acceptItem(Item item, Tile tile, Tile source) { + public boolean acceptItem(Item item, Tile tile, Tile source){ return tile.entity.items.get(item) < itemCapacity && item.type == ItemType.material; } @@ -167,7 +189,7 @@ public class CoreBlock extends StorageBlock { } @Override - public void update(Tile tile) { + public void update(Tile tile){ CoreEntity entity = tile.entity(); if(!entity.solid && !Units.anyEntities(tile)){ @@ -215,12 +237,13 @@ public class CoreBlock extends StorageBlock { } if(entity.solid && tile.entity.timer.get(timerSupply, supplyInterval)){ - rect.setSize(supplyRadius*2).setCenter(tile.drawx(), tile.drawy()); + rect.setSize(supplyRadius * 2).setCenter(tile.drawx(), tile.drawy()); Units.getNearby(tile.getTeam(), rect, unit -> { - if(unit.isDead() || unit.distanceTo(tile.drawx(), tile.drawy()) > supplyRadius || unit.getGroup() == null) return; + if(unit.isDead() || unit.distanceTo(tile.drawx(), tile.drawy()) > supplyRadius || unit.getGroup() == null) + return; - for(int i = 0; i < Item.all().size; i ++){ + for(int i = 0; i < Item.all().size; i++){ Item item = Item.getByID(i); if(tile.entity.items.get(item) > 0 && unit.acceptsAmmo(item)){ tile.entity.items.remove(item, 1); @@ -234,44 +257,23 @@ public class CoreBlock extends StorageBlock { } @Override - public TileEntity getEntity() { + public TileEntity getEntity(){ return new CoreEntity(); } - @Remote(called = Loc.server, in = In.blocks) - public static void onUnitRespawn(Tile tile, Unit player){ - if(player == null) return; + /* + @Remote(called = Loc.server, in = In.blocks) + public static void onCoreUnitSet(Tile tile, Unit player){ + CoreEntity entity = tile.entity(); + entity.currentUnit = player; + entity.progress = 0f; + player.set(tile.drawx(), tile.drawy()); - CoreEntity entity = tile.entity(); - Effects.effect(Fx.spawn, entity); - entity.solid = false; - entity.progress = 0; - entity.currentUnit = player; - entity.currentUnit.heal(); - entity.currentUnit.rotation = 90f; - entity.currentUnit.setNet(tile.drawx(), tile.drawy()); - entity.currentUnit.add(); - entity.currentUnit = null; - } - - @Remote(called = Loc.server, in = In.blocks) - public static void setCoreSolid(Tile tile, boolean solid){ - CoreEntity entity = tile.entity(); - entity.solid = solid; - } -/* - @Remote(called = Loc.server, in = In.blocks) - public static void onCoreUnitSet(Tile tile, Unit player){ - CoreEntity entity = tile.entity(); - entity.currentUnit = player; - entity.progress = 0f; - player.set(tile.drawx(), tile.drawy()); - - if(player instanceof Player){ - ((Player) player).setRespawning(true); + if(player instanceof Player){ + ((Player) player).setRespawning(true); + } } - } -*/ + */ public class CoreEntity extends TileEntity implements SpawnerTrait{ public Unit currentUnit; int droneID = -1; @@ -282,7 +284,7 @@ public class CoreBlock extends StorageBlock { float heat; @Override - public void updateSpawning(Unit unit) { + public void updateSpawning(Unit unit){ if(currentUnit == null){ currentUnit = unit; progress = 0f; @@ -291,18 +293,18 @@ public class CoreBlock extends StorageBlock { } @Override - public float getSpawnProgress() { + public float getSpawnProgress(){ return progress; } @Override - public void write(DataOutputStream stream) throws IOException { + public void write(DataOutputStream stream) throws IOException{ stream.writeBoolean(solid); stream.writeInt(droneID); } @Override - public void read(DataInputStream stream) throws IOException { + public void read(DataInputStream stream) throws IOException{ solid = stream.readBoolean(); droneID = stream.readInt(); } diff --git a/core/src/io/anuke/mindustry/world/blocks/storage/SortedUnloader.java b/core/src/io/anuke/mindustry/world/blocks/storage/SortedUnloader.java index de4c79d10c..00cf65918c 100644 --- a/core/src/io/anuke/mindustry/world/blocks/storage/SortedUnloader.java +++ b/core/src/io/anuke/mindustry/world/blocks/storage/SortedUnloader.java @@ -26,6 +26,12 @@ public class SortedUnloader extends Unloader implements SelectionTrait{ //TODO call event + @Remote(targets = Loc.both, called = Loc.both, in = In.blocks, forward = true) + public static void setSortedUnloaderItem(Player player, Tile tile, Item item){ + SortedUnloaderEntity entity = tile.entity(); + entity.sortItem = item; + } + @Override public void update(Tile tile){ SortedUnloaderEntity entity = tile.entity(); @@ -33,8 +39,8 @@ public class SortedUnloader extends Unloader implements SelectionTrait{ if(entity.items.total() == 0 && entity.timer.get(timerUnload, speed)){ tile.allNearby(other -> { if(other.block() instanceof StorageBlock && entity.items.total() == 0 && - ((StorageBlock)other.block()).hasItem(other, entity.sortItem)){ - offloadNear(tile, ((StorageBlock)other.block()).removeItem(other, entity.sortItem)); + ((StorageBlock) other.block()).hasItem(other, entity.sortItem)){ + offloadNear(tile, ((StorageBlock) other.block()).removeItem(other, entity.sortItem)); } }); } @@ -66,17 +72,11 @@ public class SortedUnloader extends Unloader implements SelectionTrait{ return new SortedUnloaderEntity(); } - @Remote(targets = Loc.both, called = Loc.both, in = In.blocks, forward = true) - public static void setSortedUnloaderItem(Player player, Tile tile, Item item){ - SortedUnloaderEntity entity = tile.entity(); - entity.sortItem = item; - } - public static class SortedUnloaderEntity extends TileEntity{ public Item sortItem = Items.tungsten; @Override - public void write(DataOutputStream stream) throws IOException { + public void write(DataOutputStream stream) throws IOException{ stream.writeByte(sortItem.id); } diff --git a/core/src/io/anuke/mindustry/world/blocks/storage/StorageBlock.java b/core/src/io/anuke/mindustry/world/blocks/storage/StorageBlock.java index 31d24e1190..b5e4c83a8c 100644 --- a/core/src/io/anuke/mindustry/world/blocks/storage/StorageBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/storage/StorageBlock.java @@ -5,15 +5,17 @@ import io.anuke.mindustry.type.Item; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; -public abstract class StorageBlock extends Block { +public abstract class StorageBlock extends Block{ public StorageBlock(String name){ super(name); hasItems = true; } - /**Removes an item and returns it. If item is not null, it should return the item. - * Returns null if no items are there.*/ + /** + * Removes an item and returns it. If item is not null, it should return the item. + * Returns null if no items are there. + */ public Item removeItem(Tile tile, Item item){ TileEntity entity = tile.entity; @@ -29,8 +31,10 @@ public abstract class StorageBlock extends Block { } } - /**Returns whether this storage block has the specified item. - * If the item is null, it should return whether it has ANY items.*/ + /** + * Returns whether this storage block has the specified item. + * If the item is null, it should return whether it has ANY items. + */ public boolean hasItem(Tile tile, Item item){ TileEntity entity = tile.entity; if(item == null){ diff --git a/core/src/io/anuke/mindustry/world/blocks/storage/Unloader.java b/core/src/io/anuke/mindustry/world/blocks/storage/Unloader.java index aa59e769c7..6e7d32403c 100644 --- a/core/src/io/anuke/mindustry/world/blocks/storage/Unloader.java +++ b/core/src/io/anuke/mindustry/world/blocks/storage/Unloader.java @@ -2,10 +2,10 @@ package io.anuke.mindustry.world.blocks.storage; import io.anuke.mindustry.type.Item; import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.meta.BlockGroup; import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.meta.BlockGroup; -public class Unloader extends Block { +public class Unloader extends Block{ protected final int timerUnload = timers++; protected int speed = 5; @@ -23,8 +23,8 @@ public class Unloader extends Block { if(tile.entity.items.total() == 0 && tile.entity.timer.get(timerUnload, speed)){ tile.allNearby(other -> { if(other.block() instanceof StorageBlock && tile.entity.items.total() == 0 && - ((StorageBlock)other.block()).hasItem(other, null)){ - offloadNear(tile, ((StorageBlock)other.block()).removeItem(other, null)); + ((StorageBlock) other.block()).hasItem(other, null)){ + offloadNear(tile, ((StorageBlock) other.block()).removeItem(other, null)); } }); } @@ -35,11 +35,12 @@ public class Unloader extends Block { } @Override - public boolean canDump(Tile tile, Tile to, Item item) { + public boolean canDump(Tile tile, Tile to, Item item){ Block block = to.target().block(); return !(block instanceof StorageBlock); } @Override - public void setBars(){} + public void setBars(){ + } } diff --git a/core/src/io/anuke/mindustry/world/blocks/storage/Vault.java b/core/src/io/anuke/mindustry/world/blocks/storage/Vault.java index 54a9bffd28..16e23306ea 100644 --- a/core/src/io/anuke/mindustry/world/blocks/storage/Vault.java +++ b/core/src/io/anuke/mindustry/world/blocks/storage/Vault.java @@ -4,7 +4,7 @@ import io.anuke.mindustry.type.Item; import io.anuke.mindustry.world.Tile; import io.anuke.ucore.core.Timers; -public class Vault extends StorageBlock { +public class Vault extends StorageBlock{ public Vault(String name){ super(name); @@ -17,8 +17,8 @@ public class Vault extends StorageBlock { public void update(Tile tile){ int iterations = Math.max(1, (int) (Timers.delta() + 0.4f)); - for(int i = 0; i < iterations; i ++) { - if (tile.entity.items.total() > 0) { + for(int i = 0; i < iterations; i++){ + if(tile.entity.items.total() > 0){ tryDump(tile); } } @@ -30,14 +30,14 @@ public class Vault extends StorageBlock { } @Override - public boolean acceptItem(Item item, Tile tile, Tile source) { + public boolean acceptItem(Item item, Tile tile, Tile source){ return tile.entity.items.total() < itemCapacity; } @Override - public boolean canDump(Tile tile, Tile to, Item item) { + public boolean canDump(Tile tile, Tile to, Item item){ to = to.target(); - if (!(to.block() instanceof StorageBlock)) return false; + if(!(to.block() instanceof StorageBlock)) return false; return !(to.block() instanceof Vault) || (float) to.entity.items.total() / to.block().itemCapacity < (float) tile.entity.items.total() / itemCapacity; diff --git a/core/src/io/anuke/mindustry/world/blocks/units/CommandCenter.java b/core/src/io/anuke/mindustry/world/blocks/units/CommandCenter.java index 830b215b2d..c4a5ca3859 100644 --- a/core/src/io/anuke/mindustry/world/blocks/units/CommandCenter.java +++ b/core/src/io/anuke/mindustry/world/blocks/units/CommandCenter.java @@ -2,8 +2,8 @@ package io.anuke.mindustry.world.blocks.units; import io.anuke.mindustry.world.Block; -public class CommandCenter extends Block { - public CommandCenter(String name) { +public class CommandCenter extends Block{ + public CommandCenter(String name){ super(name); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/units/DropPoint.java b/core/src/io/anuke/mindustry/world/blocks/units/DropPoint.java index b6a294465e..8acdf20be9 100644 --- a/core/src/io/anuke/mindustry/world/blocks/units/DropPoint.java +++ b/core/src/io/anuke/mindustry/world/blocks/units/DropPoint.java @@ -4,9 +4,9 @@ import io.anuke.mindustry.type.Item; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; -public class DropPoint extends Block { +public class DropPoint extends Block{ - public DropPoint(String name) { + public DropPoint(String name){ super(name); hasItems = true; @@ -15,13 +15,13 @@ public class DropPoint extends Block { } @Override - public boolean acceptItem(Item item, Tile tile, Tile source) { + public boolean acceptItem(Item item, Tile tile, Tile source){ return false; } @Override - public void update(Tile tile) { - if (tile.entity.items.total() > 0) { + public void update(Tile tile){ + if(tile.entity.items.total() > 0){ tryDump(tile); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/units/MechFactory.java b/core/src/io/anuke/mindustry/world/blocks/units/MechFactory.java index deff90de86..df2c2db165 100644 --- a/core/src/io/anuke/mindustry/world/blocks/units/MechFactory.java +++ b/core/src/io/anuke/mindustry/world/blocks/units/MechFactory.java @@ -33,7 +33,7 @@ import static io.anuke.mindustry.Vars.tilesize; public class MechFactory extends Block{ protected Mech mech; - protected float buildTime = 60*5; + protected float buildTime = 60 * 5; protected TextureRegion openRegion; @@ -44,14 +44,52 @@ public class MechFactory extends Block{ solidifes = true; } + @Remote(targets = Loc.both, called = Loc.server, in = In.blocks) + public static void onMechFactoryTap(Player player, Tile tile){ + if(!checkValidTap(tile, player)) return; + + MechFactoryEntity entity = tile.entity(); + player.beginRespawning(entity); + } + + @Remote(called = Loc.server, in = In.blocks) + public static void onMechFactoryDone(Tile tile){ + MechFactoryEntity entity = tile.entity(); + + Effects.effect(Fx.spawn, entity); + + if(entity.player == null) return; + + Mech result = ((MechFactory) tile.block()).mech; + + if(entity.player.mech == result){ + entity.player.mech = (entity.player.isMobile ? Mechs.starterMobile : Mechs.starterDesktop); + }else{ + entity.player.mech = result; + } + + entity.progress = 0; + entity.player.heal(); + entity.open = true; + entity.player.setDead(false); + entity.player.inventory.clear(); + entity.player = null; + } + + protected static boolean checkValidTap(Tile tile, Player player){ + MechFactoryEntity entity = tile.entity(); + return Math.abs(player.x - tile.drawx()) <= tile.block().size * tilesize / 2f && + Math.abs(player.y - tile.drawy()) <= tile.block().size * tilesize / 2f && entity.player == null; + } + @Override - public boolean isSolidFor(Tile tile) { + public boolean isSolidFor(Tile tile){ MechFactoryEntity entity = tile.entity(); return !entity.open; } @Override - public void tapped(Tile tile, Player player) { + public void tapped(Tile tile, Player player){ if(mobile && !mech.flying) return; if(checkValidTap(tile, player)){ @@ -62,18 +100,18 @@ public class MechFactory extends Block{ } @Override - public void load() { + public void load(){ super.load(); openRegion = Draw.region(name + "-open"); } @Override - public void draw(Tile tile) { + public void draw(Tile tile){ MechFactoryEntity entity = tile.entity(); Draw.rect(entity.open ? openRegion : Draw.region(name), tile.drawx(), tile.drawy()); - if(entity.player != null) { + if(entity.player != null){ TextureRegion region = mech.iconRegion; if(entity.player.mech == mech){ @@ -103,7 +141,7 @@ public class MechFactory extends Block{ } @Override - public void update(Tile tile) { + public void update(Tile tile){ MechFactoryEntity entity = tile.entity(); if(entity.open){ @@ -133,48 +171,10 @@ public class MechFactory extends Block{ } @Override - public TileEntity getEntity() { + public TileEntity getEntity(){ return new MechFactoryEntity(); } - @Remote(targets = Loc.both, called = Loc.server, in = In.blocks) - public static void onMechFactoryTap(Player player, Tile tile){ - if(!checkValidTap(tile, player)) return; - - MechFactoryEntity entity = tile.entity(); - player.beginRespawning(entity); - } - - @Remote(called = Loc.server, in = In.blocks) - public static void onMechFactoryDone(Tile tile){ - MechFactoryEntity entity = tile.entity(); - - Effects.effect(Fx.spawn, entity); - - if(entity.player == null) return; - - Mech result = ((MechFactory)tile.block()).mech; - - if(entity.player.mech == result){ - entity.player.mech = (entity.player.isMobile ? Mechs.starterMobile : Mechs.starterDesktop); - }else{ - entity.player.mech = result; - } - - entity.progress = 0; - entity.player.heal(); - entity.open = true; - entity.player.setDead(false); - entity.player.inventory.clear(); - entity.player = null; - } - - protected static boolean checkValidTap(Tile tile, Player player){ - MechFactoryEntity entity = tile.entity(); - return Math.abs(player.x - tile.drawx()) <= tile.block().size * tilesize / 2f && - Math.abs(player.y - tile.drawy()) <= tile.block().size * tilesize / 2f && entity.player == null; - } - public class MechFactoryEntity extends TileEntity implements SpawnerTrait{ Player player; float progress; @@ -183,12 +183,13 @@ public class MechFactory extends Block{ boolean open; @Override - public void updateSpawning(Unit unit) { - if(!(unit instanceof Player)) throw new IllegalArgumentException("Mech factories only accept player respawners."); + public void updateSpawning(Unit unit){ + if(!(unit instanceof Player)) + throw new IllegalArgumentException("Mech factories only accept player respawners."); if(player == null){ progress = 0f; - player = (Player)unit; + player = (Player) unit; player.rotation = 90f; player.baseRotation = 90f; @@ -198,19 +199,19 @@ public class MechFactory extends Block{ } @Override - public float getSpawnProgress() { + public float getSpawnProgress(){ return progress; } @Override - public void write(DataOutputStream stream) throws IOException { + public void write(DataOutputStream stream) throws IOException{ stream.writeFloat(progress); stream.writeFloat(time); stream.writeFloat(heat); } @Override - public void read(DataInputStream stream) throws IOException { + public void read(DataInputStream stream) throws IOException{ progress = stream.readFloat(); time = stream.readFloat(); heat = stream.readFloat(); diff --git a/core/src/io/anuke/mindustry/world/blocks/units/OverdriveProjector.java b/core/src/io/anuke/mindustry/world/blocks/units/OverdriveProjector.java index 7acecd1414..740f631053 100644 --- a/core/src/io/anuke/mindustry/world/blocks/units/OverdriveProjector.java +++ b/core/src/io/anuke/mindustry/world/blocks/units/OverdriveProjector.java @@ -2,9 +2,9 @@ package io.anuke.mindustry.world.blocks.units; import io.anuke.mindustry.content.StatusEffects; -public class OverdriveProjector extends Projector { +public class OverdriveProjector extends Projector{ - public OverdriveProjector(String name) { + public OverdriveProjector(String name){ super(name); status = StatusEffects.overdrive; diff --git a/core/src/io/anuke/mindustry/world/blocks/units/Projector.java b/core/src/io/anuke/mindustry/world/blocks/units/Projector.java index 55ddcd1e78..0b05f77f7e 100644 --- a/core/src/io/anuke/mindustry/world/blocks/units/Projector.java +++ b/core/src/io/anuke/mindustry/world/blocks/units/Projector.java @@ -11,7 +11,7 @@ import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.graphics.Lines; import io.anuke.ucore.util.Mathf; -public abstract class Projector extends Block { +public abstract class Projector extends Block{ protected final int timerApply = timers++; protected final float applyTime = 4f; @@ -20,7 +20,7 @@ public abstract class Projector extends Block { protected StatusEffect status; protected float intensity = 1f; - public Projector(String name) { + public Projector(String name){ super(name); hasPower = true; update = true; @@ -35,7 +35,7 @@ public abstract class Projector extends Block { } @Override - public void update(Tile tile) { + public void update(Tile tile){ ProjectorEntity entity = tile.entity(); if(entity.cons.valid()){ @@ -44,7 +44,7 @@ public abstract class Projector extends Block { entity.heat = Mathf.lerpDelta(entity.heat, 0f, 0.01f); } - if(entity.heat > 0.6f && Timers.get(timerApply, applyTime)) { + if(entity.heat > 0.6f && Timers.get(timerApply, applyTime)){ Units.getNearby(tile.getTeam(), tile.drawx(), tile.drawy(), range, unit -> { unit.applyEffect(status, intensity); }); @@ -52,7 +52,7 @@ public abstract class Projector extends Block { } @Override - public TileEntity getEntity() { + public TileEntity getEntity(){ return new ProjectorEntity(); } diff --git a/core/src/io/anuke/mindustry/world/blocks/units/Reconstructor.java b/core/src/io/anuke/mindustry/world/blocks/units/Reconstructor.java index d893038086..02e74a5f1e 100644 --- a/core/src/io/anuke/mindustry/world/blocks/units/Reconstructor.java +++ b/core/src/io/anuke/mindustry/world/blocks/units/Reconstructor.java @@ -38,7 +38,7 @@ public class Reconstructor extends Block{ protected Effect arriveEffect = Fx.spawn; protected TextureRegion openRegion; - public Reconstructor(String name) { + public Reconstructor(String name){ super(name); update = true; solidifes = true; @@ -46,191 +46,11 @@ public class Reconstructor extends Block{ configurable = true; } - @Override - public void load() { - super.load(); - openRegion = Draw.region(name + "-open"); - } - - @Override - public boolean isSolidFor(Tile tile) { - ReconstructorEntity entity = tile.entity(); - - return entity.solid; - } - - @Override - public void drawConfigure(Tile tile) { - super.drawConfigure(tile); - - ReconstructorEntity entity = tile.entity(); - - if(validLink(tile, entity.link)){ - Tile target = world.tile(entity.link); - - Draw.color(Palette.place); - Lines.square(target.drawx(), target.drawy(), - target.block().size * tilesize / 2f + 1f); - Draw.reset(); - } - - Draw.color(Palette.accent); - Draw.color(); - } - - @Override - public boolean onConfigureTileTapped(Tile tile, Tile other){ - if(tile == other) return false; - - ReconstructorEntity entity = tile.entity(); - - if(entity.link == other.packedPosition()) { - CallBlocks.unlinkReconstructor(null, tile, other); - return false; - }else if(other.block() instanceof Reconstructor){ - CallBlocks.linkReconstructor(null, tile, other); - return false; - } - - return true; - } - - @Override - public boolean shouldShowConfigure(Tile tile, Player player) { - ReconstructorEntity entity = tile.entity(); - return !checkValidTap(tile, entity, player); - } - - @Override - public boolean shouldHideConfigure(Tile tile, Player player){ - ReconstructorEntity entity = tile.entity(); - return checkValidTap(tile, entity, player); - } - - @Override - public void draw(Tile tile) { - ReconstructorEntity entity = tile.entity(); - - if(entity.solid){ - Draw.rect(region, tile.drawx(), tile.drawy()); - }else{ - Draw.rect(openRegion, tile.drawx(), tile.drawy()); - } - - if(entity.current != null){ - float progress = entity.departing ? entity.updateTime : (1f - entity.updateTime); - - //Player player = entity.current; - - TextureRegion region = entity.current.getIconRegion(); - - Shaders.build.region = region; - Shaders.build.progress = progress; - Shaders.build.color.set(Palette.accent); - Shaders.build.time = -entity.time / 10f; - - Graphics.shader(Shaders.build, false); - Shaders.build.apply(); - Draw.rect(region, tile.drawx(), tile.drawy()); - Graphics.shader(); - - Draw.color(Palette.accent); - - Lines.lineAngleCenter( - tile.drawx() + Mathf.sin(entity.time, 6f, Vars.tilesize / 3f * size), - tile.drawy(), - 90, - size * Vars.tilesize /2f); - - Draw.reset(); - } - } - - @Override - public void update(Tile tile) { - ReconstructorEntity entity = tile.entity(); - - boolean stayOpen = false; - - if(entity.current != null){ - entity.time += Timers.delta(); - - entity.solid = true; - - if(entity.departing){ - //force respawn if there's suddenly nothing to link to - if(!validLink(tile, entity.link)){ - //entity.current.setRespawning(false); - return; - } - - ReconstructorEntity other = world.tile(entity.link).entity(); - - entity.updateTime -= Timers.delta()/departTime; - if(entity.updateTime <= 0f){ - //no power? death. - if(other.power.amount < powerPerTeleport){ - entity.current.setDead(true); - //entity.current.setRespawning(false); - entity.current = null; - return; - } - other.power.amount -= powerPerTeleport; - other.current = entity.current; - other.departing = false; - other.current.set(other.x, other.y); - other.updateTime = 1f; - entity.current = null; - } - }else{ //else, arriving - entity.updateTime -= Timers.delta()/arriveTime; - - if(entity.updateTime <= 0f){ - entity.solid = false; - entity.current.setDead(false); - - Effects.effect(arriveEffect, entity.current); - - entity.current = null; - } - } - - }else{ - - if (validLink(tile, entity.link)) { - Tile other = world.tile(entity.link); - if (other.entity.power.amount >= powerPerTeleport && Units.anyEntities(tile, 4f, unit -> unit.getTeam() == entity.getTeam() && unit instanceof Player) && - entity.power.amount >= powerPerTeleport) { - entity.solid = false; - stayOpen = true; - } - } - - if (!stayOpen && !entity.solid && !Units.anyEntities(tile)) { - entity.solid = true; - } - } - } - - @Override - public void tapped(Tile tile, Player player) { - ReconstructorEntity entity = tile.entity(); - - if(!checkValidTap(tile, entity, player)) return; - - CallBlocks.reconstructPlayer(player, tile); - } - - @Override - public TileEntity getEntity() { - return new ReconstructorEntity(); - } - protected static boolean checkValidTap(Tile tile, ReconstructorEntity entity, Player player){ return validLink(tile, entity.link) && Math.abs(player.x - tile.drawx()) <= tile.block().size * tilesize / 2f && Math.abs(player.y - tile.drawy()) <= tile.block().size * tilesize / 2f && - entity.current == null && entity.power.amount >= ((Reconstructor)tile.block()).powerPerTeleport; + entity.current == null && entity.power.amount >= ((Reconstructor) tile.block()).powerPerTeleport; } protected static boolean validLink(Tile tile, int position){ @@ -255,18 +75,19 @@ public class Reconstructor extends Block{ public static void reconstructPlayer(Player player, Tile tile){ ReconstructorEntity entity = tile.entity(); - if(!checkValidTap(tile, entity, player) || entity.power.amount < ((Reconstructor)tile.block()).powerPerTeleport) return; + if(!checkValidTap(tile, entity, player) || entity.power.amount < ((Reconstructor) tile.block()).powerPerTeleport) + return; entity.departing = true; entity.current = player; entity.solid = false; - entity.power.amount -= ((Reconstructor)tile.block()).powerPerTeleport; + entity.power.amount -= ((Reconstructor) tile.block()).powerPerTeleport; entity.updateTime = 1f; entity.set(tile.drawx(), tile.drawy()); player.rotation = 90f; player.baseRotation = 90f; player.setDead(true); - // player.setRespawning(true); + // player.setRespawning(true); //player.setRespawning(); } @@ -303,6 +124,186 @@ public class Reconstructor extends Block{ }); } + @Override + public void load(){ + super.load(); + openRegion = Draw.region(name + "-open"); + } + + @Override + public boolean isSolidFor(Tile tile){ + ReconstructorEntity entity = tile.entity(); + + return entity.solid; + } + + @Override + public void drawConfigure(Tile tile){ + super.drawConfigure(tile); + + ReconstructorEntity entity = tile.entity(); + + if(validLink(tile, entity.link)){ + Tile target = world.tile(entity.link); + + Draw.color(Palette.place); + Lines.square(target.drawx(), target.drawy(), + target.block().size * tilesize / 2f + 1f); + Draw.reset(); + } + + Draw.color(Palette.accent); + Draw.color(); + } + + @Override + public boolean onConfigureTileTapped(Tile tile, Tile other){ + if(tile == other) return false; + + ReconstructorEntity entity = tile.entity(); + + if(entity.link == other.packedPosition()){ + CallBlocks.unlinkReconstructor(null, tile, other); + return false; + }else if(other.block() instanceof Reconstructor){ + CallBlocks.linkReconstructor(null, tile, other); + return false; + } + + return true; + } + + @Override + public boolean shouldShowConfigure(Tile tile, Player player){ + ReconstructorEntity entity = tile.entity(); + return !checkValidTap(tile, entity, player); + } + + @Override + public boolean shouldHideConfigure(Tile tile, Player player){ + ReconstructorEntity entity = tile.entity(); + return checkValidTap(tile, entity, player); + } + + @Override + public void draw(Tile tile){ + ReconstructorEntity entity = tile.entity(); + + if(entity.solid){ + Draw.rect(region, tile.drawx(), tile.drawy()); + }else{ + Draw.rect(openRegion, tile.drawx(), tile.drawy()); + } + + if(entity.current != null){ + float progress = entity.departing ? entity.updateTime : (1f - entity.updateTime); + + //Player player = entity.current; + + TextureRegion region = entity.current.getIconRegion(); + + Shaders.build.region = region; + Shaders.build.progress = progress; + Shaders.build.color.set(Palette.accent); + Shaders.build.time = -entity.time / 10f; + + Graphics.shader(Shaders.build, false); + Shaders.build.apply(); + Draw.rect(region, tile.drawx(), tile.drawy()); + Graphics.shader(); + + Draw.color(Palette.accent); + + Lines.lineAngleCenter( + tile.drawx() + Mathf.sin(entity.time, 6f, Vars.tilesize / 3f * size), + tile.drawy(), + 90, + size * Vars.tilesize / 2f); + + Draw.reset(); + } + } + + @Override + public void update(Tile tile){ + ReconstructorEntity entity = tile.entity(); + + boolean stayOpen = false; + + if(entity.current != null){ + entity.time += Timers.delta(); + + entity.solid = true; + + if(entity.departing){ + //force respawn if there's suddenly nothing to link to + if(!validLink(tile, entity.link)){ + //entity.current.setRespawning(false); + return; + } + + ReconstructorEntity other = world.tile(entity.link).entity(); + + entity.updateTime -= Timers.delta() / departTime; + if(entity.updateTime <= 0f){ + //no power? death. + if(other.power.amount < powerPerTeleport){ + entity.current.setDead(true); + //entity.current.setRespawning(false); + entity.current = null; + return; + } + other.power.amount -= powerPerTeleport; + other.current = entity.current; + other.departing = false; + other.current.set(other.x, other.y); + other.updateTime = 1f; + entity.current = null; + } + }else{ //else, arriving + entity.updateTime -= Timers.delta() / arriveTime; + + if(entity.updateTime <= 0f){ + entity.solid = false; + entity.current.setDead(false); + + Effects.effect(arriveEffect, entity.current); + + entity.current = null; + } + } + + }else{ + + if(validLink(tile, entity.link)){ + Tile other = world.tile(entity.link); + if(other.entity.power.amount >= powerPerTeleport && Units.anyEntities(tile, 4f, unit -> unit.getTeam() == entity.getTeam() && unit instanceof Player) && + entity.power.amount >= powerPerTeleport){ + entity.solid = false; + stayOpen = true; + } + } + + if(!stayOpen && !entity.solid && !Units.anyEntities(tile)){ + entity.solid = true; + } + } + } + + @Override + public void tapped(Tile tile, Player player){ + ReconstructorEntity entity = tile.entity(); + + if(!checkValidTap(tile, entity, player)) return; + + CallBlocks.reconstructPlayer(player, tile); + } + + @Override + public TileEntity getEntity(){ + return new ReconstructorEntity(); + } + public class ReconstructorEntity extends TileEntity implements SpawnerTrait{ Unit current; float updateTime; @@ -311,22 +312,22 @@ public class Reconstructor extends Block{ boolean solid = true, departing; @Override - public void updateSpawning(Unit unit) { + public void updateSpawning(Unit unit){ } @Override - public float getSpawnProgress() { + public float getSpawnProgress(){ return 0; } @Override - public void write(DataOutputStream stream) throws IOException { + public void write(DataOutputStream stream) throws IOException{ stream.writeInt(link); } @Override - public void read(DataInputStream stream) throws IOException { + public void read(DataInputStream stream) throws IOException{ link = stream.readInt(); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/units/RepairPoint.java b/core/src/io/anuke/mindustry/world/blocks/units/RepairPoint.java index d592f7817e..e2e152509e 100644 --- a/core/src/io/anuke/mindustry/world/blocks/units/RepairPoint.java +++ b/core/src/io/anuke/mindustry/world/blocks/units/RepairPoint.java @@ -9,8 +9,8 @@ import io.anuke.mindustry.entities.Units; import io.anuke.mindustry.graphics.Layer; import io.anuke.mindustry.graphics.Palette; import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.meta.BlockFlag; import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.meta.BlockFlag; import io.anuke.ucore.core.Timers; import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.graphics.Lines; @@ -22,14 +22,14 @@ import io.anuke.ucore.util.Mathf; public class RepairPoint extends Block{ private static Rectangle rect = new Rectangle(); - protected int timerTarget = timers ++; + protected int timerTarget = timers++; protected float repairRadius = 50f; protected float repairSpeed = 0.3f; protected TextureRegion topRegion; - public RepairPoint(String name) { + public RepairPoint(String name){ super(name); update = true; solid = true; @@ -42,7 +42,7 @@ public class RepairPoint extends Block{ } @Override - public void load() { + public void load(){ super.load(); topRegion = Draw.region(name + "-turret"); @@ -56,14 +56,14 @@ public class RepairPoint extends Block{ } @Override - public void drawLayer(Tile tile) { + public void drawLayer(Tile tile){ RepairPointEntity entity = tile.entity(); Draw.rect(topRegion, tile.drawx(), tile.drawy(), entity.rotation - 90); } @Override - public void drawLayer2(Tile tile) { + public void drawLayer2(Tile tile){ RepairPointEntity entity = tile.entity(); if(entity.target != null && @@ -80,11 +80,11 @@ public class RepairPoint extends Block{ } @Override - public void update(Tile tile) { + public void update(Tile tile){ RepairPointEntity entity = tile.entity(); if(entity.target != null && (entity.target.isDead() || entity.target.distanceTo(tile) > repairRadius || - entity.target.health >= entity.target.maxHealth())){ + entity.target.health >= entity.target.maxHealth())){ entity.target = null; }else if(entity.target != null){ entity.target.health += repairSpeed * Timers.delta() * entity.strength; @@ -98,7 +98,7 @@ public class RepairPoint extends Block{ entity.strength = Mathf.lerpDelta(entity.strength, 0f, 0.07f * Timers.delta()); } - if(entity.timer.get(timerTarget, 20)) { + if(entity.timer.get(timerTarget, 20)){ rect.setSize(repairRadius * 2).setCenter(tile.drawx(), tile.drawy()); entity.target = Units.getClosest(tile.getTeam(), tile.drawx(), tile.drawy(), repairRadius, unit -> unit.health < unit.maxHealth()); @@ -106,14 +106,14 @@ public class RepairPoint extends Block{ } @Override - public boolean shouldConsume(Tile tile) { + public boolean shouldConsume(Tile tile){ RepairPointEntity entity = tile.entity(); return entity.target != null; } @Override - public TileEntity getEntity() { + public TileEntity getEntity(){ return new RepairPointEntity(); } diff --git a/core/src/io/anuke/mindustry/world/blocks/units/ResupplyPoint.java b/core/src/io/anuke/mindustry/world/blocks/units/ResupplyPoint.java index adaaeddb34..12aa4b3ce6 100644 --- a/core/src/io/anuke/mindustry/world/blocks/units/ResupplyPoint.java +++ b/core/src/io/anuke/mindustry/world/blocks/units/ResupplyPoint.java @@ -9,8 +9,8 @@ import io.anuke.mindustry.graphics.Layer; import io.anuke.mindustry.graphics.Palette; import io.anuke.mindustry.type.Item; import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.meta.BlockFlag; import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.meta.BlockFlag; import io.anuke.ucore.core.Timers; import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.graphics.Lines; @@ -22,13 +22,13 @@ import io.anuke.ucore.util.Mathf; public class ResupplyPoint extends Block{ private static Rectangle rect = new Rectangle(); - protected int timerSupply = timers ++; - protected int timerTarget = timers ++; + protected int timerSupply = timers++; + protected int timerTarget = timers++; protected float supplyRadius = 50f; protected float supplyInterval = 10f; - public ResupplyPoint(String name) { + public ResupplyPoint(String name){ super(name); update = true; solid = true; @@ -49,7 +49,7 @@ public class ResupplyPoint extends Block{ } @Override - public void drawLayer(Tile tile) { + public void drawLayer(Tile tile){ ResupplyPointEntity entity = tile.entity(); if(entity.strength > 0f){ @@ -65,11 +65,11 @@ public class ResupplyPoint extends Block{ x1, y1, entity.lastx, entity.lasty, entity.strength); Draw.color(Palette.accent); - for(int i = 0; i < dstTo/space-1; i ++){ - float fract = (i * space) / dstTo + ((Timers.time()/90f) % (space/dstTo)); - Draw.alpha(Mathf.clamp(fract*1.5f)); - Draw.rect("transfer-arrow", x1 + fract*xf, y1 + fract*yf, - 8, 8*entity.strength, ang); + for(int i = 0; i < dstTo / space - 1; i++){ + float fract = (i * space) / dstTo + ((Timers.time() / 90f) % (space / dstTo)); + Draw.alpha(Mathf.clamp(fract * 1.5f)); + Draw.rect("transfer-arrow", x1 + fract * xf, y1 + fract * yf, + 8, 8 * entity.strength, ang); } Draw.color(); @@ -78,17 +78,17 @@ public class ResupplyPoint extends Block{ } @Override - public void update(Tile tile) { + public void update(Tile tile){ ResupplyPointEntity entity = tile.entity(); if(!validTarget(entity, entity.target) || entity.target.distanceTo(tile) > supplyRadius){ entity.target = null; }else if(entity.target != null && entity.strength > 0.5f){ - if(entity.timer.get(timerSupply, supplyInterval)) { - for (int i = 0; i < Item.all().size; i++) { + if(entity.timer.get(timerSupply, supplyInterval)){ + for(int i = 0; i < Item.all().size; i++){ Item item = Item.getByID(i); - if (tile.entity.items.has(item) && entity.target.acceptsAmmo(item)) { + if(tile.entity.items.has(item) && entity.target.acceptsAmmo(item)){ tile.entity.items.remove(item, 1); entity.target.addAmmo(item); break; @@ -107,7 +107,7 @@ public class ResupplyPoint extends Block{ entity.strength = Mathf.lerpDelta(entity.strength, 0f, 0.08f * Timers.delta()); } - if(entity.timer.get(timerTarget, 20)) { + if(entity.timer.get(timerTarget, 20)){ rect.setSize(supplyRadius * 2).setCenter(tile.drawx(), tile.drawy()); entity.target = Units.getClosest(tile.getTeam(), tile.drawx(), tile.drawy(), supplyRadius, unit -> validTarget(entity, unit)); @@ -115,12 +115,12 @@ public class ResupplyPoint extends Block{ } @Override - public boolean acceptItem(Item item, Tile tile, Tile source) { + public boolean acceptItem(Item item, Tile tile, Tile source){ return tile.entity.items.total() < itemCapacity; } @Override - public TileEntity getEntity() { + public TileEntity getEntity(){ return new ResupplyPointEntity(); } @@ -128,9 +128,9 @@ public class ResupplyPoint extends Block{ if(unit == null || unit.inventory.totalAmmo() >= unit.inventory.ammoCapacity() || unit.isDead()) return false; - for (int i = 0; i < Item.all().size; i++) { + for(int i = 0; i < Item.all().size; i++){ Item item = Item.getByID(i); - if (entity.items.has(item) && unit.acceptsAmmo(item)) { + if(entity.items.has(item) && unit.acceptsAmmo(item)){ return true; } } diff --git a/core/src/io/anuke/mindustry/world/blocks/units/ShieldProjector.java b/core/src/io/anuke/mindustry/world/blocks/units/ShieldProjector.java index 7f171fe193..aab518b7b1 100644 --- a/core/src/io/anuke/mindustry/world/blocks/units/ShieldProjector.java +++ b/core/src/io/anuke/mindustry/world/blocks/units/ShieldProjector.java @@ -2,9 +2,9 @@ package io.anuke.mindustry.world.blocks.units; import io.anuke.mindustry.content.StatusEffects; -public class ShieldProjector extends Projector { +public class ShieldProjector extends Projector{ - public ShieldProjector(String name) { + public ShieldProjector(String name){ super(name); status = StatusEffects.shielded; diff --git a/core/src/io/anuke/mindustry/world/blocks/units/UnitFactory.java b/core/src/io/anuke/mindustry/world/blocks/units/UnitFactory.java index a68d7562b5..98142a6972 100644 --- a/core/src/io/anuke/mindustry/world/blocks/units/UnitFactory.java +++ b/core/src/io/anuke/mindustry/world/blocks/units/UnitFactory.java @@ -35,14 +35,14 @@ import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -public class UnitFactory extends Block { +public class UnitFactory extends Block{ protected UnitType type; protected float produceTime = 1000f; protected float openDuration = 50f; protected float launchVelocity = 0f; protected String unitRegion; - public UnitFactory(String name) { + public UnitFactory(String name){ super(name); update = true; hasPower = true; @@ -52,21 +52,41 @@ public class UnitFactory extends Block { consumes.require(ConsumeItems.class); } - @Override - public void setStats() { - super.setStats(); + @Remote(called = Loc.server, in = In.blocks) + public static void onUnitFactorySpawn(Tile tile){ + UnitFactoryEntity entity = tile.entity(); + UnitFactory factory = (UnitFactory) tile.block(); - stats.add(BlockStat.craftSpeed, produceTime/60f, StatUnit.seconds); + entity.buildTime = 0f; + entity.hasSpawned = true; + + Effects.shake(2f, 3f, entity); + Effects.effect(BlockFx.producesmoke, tile.drawx(), tile.drawy()); + + if(!Net.client()){ + BaseUnit unit = factory.type.create(tile.getTeam()); + unit.setSpawner(tile); + unit.set(tile.drawx(), tile.drawy()); + unit.add(); + unit.getVelocity().y = factory.launchVelocity; + } } @Override - public boolean isSolidFor(Tile tile) { + public void setStats(){ + super.setStats(); + + stats.add(BlockStat.craftSpeed, produceTime / 60f, StatUnit.seconds); + } + + @Override + public boolean isSolidFor(Tile tile){ UnitFactoryEntity entity = tile.entity(); return type.isFlying || !entity.open; } @Override - public void setBars() { + public void setBars(){ super.setBars(); bars.add(new BlockBar(BarType.production, true, tile -> tile.entity().buildTime / produceTime)); @@ -74,22 +94,22 @@ public class UnitFactory extends Block { } @Override - public TextureRegion[] getIcon() { + public TextureRegion[] getIcon(){ return new TextureRegion[]{ - Draw.region(name), - Draw.region(name + "-top") + Draw.region(name), + Draw.region(name + "-top") }; } @Override - public void draw(Tile tile) { + public void draw(Tile tile){ UnitFactoryEntity entity = tile.entity(); TextureRegion region = Draw.region(unitRegion == null ? type.name : unitRegion); Draw.rect(name(), tile.drawx(), tile.drawy()); Shaders.build.region = region; - Shaders.build.progress = entity.buildTime/produceTime; + Shaders.build.progress = entity.buildTime / produceTime; Shaders.build.color.set(Palette.accent); Shaders.build.color.a = entity.speedScl; Shaders.build.time = -entity.time / 10f; @@ -103,7 +123,7 @@ public class UnitFactory extends Block { Draw.alpha(entity.speedScl); Lines.lineAngleCenter( - tile.drawx() + Mathf.sin(entity.time, 6f, Vars.tilesize/2f*size - 2f), + tile.drawx() + Mathf.sin(entity.time, 6f, Vars.tilesize / 2f * size - 2f), tile.drawy(), 90, size * Vars.tilesize - 4f); @@ -114,7 +134,7 @@ public class UnitFactory extends Block { } @Override - public void update(Tile tile) { + public void update(Tile tile){ UnitFactoryEntity entity = tile.entity(); entity.time += Timers.delta() * entity.speedScl; @@ -123,7 +143,7 @@ public class UnitFactory extends Block { if(entity.openCountdown > Timers.delta()){ entity.openCountdown -= Timers.delta(); }else{ - if(type.isFlying || !Units.anyEntities(tile)) { + if(type.isFlying || !Units.anyEntities(tile)){ entity.open = false; entity.openCountdown = -1; }else{ @@ -143,7 +163,7 @@ public class UnitFactory extends Block { } }*/ - if(!entity.hasSpawned && hasRequirements(entity.items, entity.buildTime/produceTime) && + if(!entity.hasSpawned && hasRequirements(entity.items, entity.buildTime / produceTime) && entity.cons.valid() && !entity.open){ entity.buildTime += Timers.delta(); @@ -166,9 +186,9 @@ public class UnitFactory extends Block { } @Override - public boolean acceptItem(Item item, Tile tile, Tile source) { + public boolean acceptItem(Item item, Tile tile, Tile source){ for(ItemStack stack : consumes.items()){ - if(item == stack.item && tile.entity.items.get(item) <= stack.amount*2){ + if(item == stack.item && tile.entity.items.get(item) <= stack.amount * 2){ return true; } } @@ -176,39 +196,19 @@ public class UnitFactory extends Block { } @Override - public TileEntity getEntity() { + public TileEntity getEntity(){ return new UnitFactoryEntity(); } protected boolean hasRequirements(InventoryModule inv, float fraction){ for(ItemStack stack : consumes.items()){ - if(!inv.has(stack.item, (int)(fraction * stack.amount))){ + if(!inv.has(stack.item, (int) (fraction * stack.amount))){ return false; } } return true; } - @Remote(called = Loc.server, in = In.blocks) - public static void onUnitFactorySpawn(Tile tile){ - UnitFactoryEntity entity = tile.entity(); - UnitFactory factory = (UnitFactory)tile.block(); - - entity.buildTime = 0f; - entity.hasSpawned = true; - - Effects.shake(2f, 3f, entity); - Effects.effect(BlockFx.producesmoke, tile.drawx(), tile.drawy()); - - if(!Net.client()) { - BaseUnit unit = factory.type.create(tile.getTeam()); - unit.setSpawner(tile); - unit.set(tile.drawx(), tile.drawy()); - unit.add(); - unit.getVelocity().y = factory.launchVelocity; - } - } - public static class UnitFactoryEntity extends TileEntity{ public float buildTime; public boolean open; @@ -218,13 +218,13 @@ public class UnitFactory extends Block { public boolean hasSpawned; @Override - public void write(DataOutputStream stream) throws IOException { + public void write(DataOutputStream stream) throws IOException{ stream.writeFloat(buildTime); stream.writeBoolean(hasSpawned); } @Override - public void read(DataInputStream stream) throws IOException { + public void read(DataInputStream stream) throws IOException{ buildTime = stream.readFloat(); hasSpawned = stream.readBoolean(); } diff --git a/core/src/io/anuke/mindustry/world/blocks/units/UnloadPoint.java b/core/src/io/anuke/mindustry/world/blocks/units/UnloadPoint.java index 6d2ef8d2d6..b7dfd4ae7e 100644 --- a/core/src/io/anuke/mindustry/world/blocks/units/UnloadPoint.java +++ b/core/src/io/anuke/mindustry/world/blocks/units/UnloadPoint.java @@ -1,4 +1,4 @@ package io.anuke.mindustry.world.blocks.units; -public class UnloadPoint { +public class UnloadPoint{ } diff --git a/core/src/io/anuke/mindustry/world/consumers/Consume.java b/core/src/io/anuke/mindustry/world/consumers/Consume.java index c26cd66e27..8408389933 100644 --- a/core/src/io/anuke/mindustry/world/consumers/Consume.java +++ b/core/src/io/anuke/mindustry/world/consumers/Consume.java @@ -10,11 +10,11 @@ import io.anuke.ucore.scene.ui.layout.Table; import static io.anuke.mindustry.Vars.mobile; -public abstract class Consume { +public abstract class Consume{ private boolean optional; private boolean update = true; - public Consume optional(boolean optional) { + public Consume optional(boolean optional){ this.optional = optional; return this; } @@ -24,11 +24,11 @@ public abstract class Consume { return this; } - public boolean isOptional() { + public boolean isOptional(){ return optional; } - public boolean isUpdate() { + public boolean isUpdate(){ return update; } @@ -40,16 +40,19 @@ public abstract class Consume { int scale = mobile ? 4 : 3; table.table(out -> { - out.addImage(getIcon()).size(10*scale).color(Color.DARK_GRAY).padRight(-10*scale).padBottom(-scale*2); - out.addImage(getIcon()).size(10*scale).color(Palette.accent); - out.addImage("icon-missing").size(10*scale).color(Palette.remove).padLeft(-10*scale); - }).size(10*scale).get().addListener(new Tooltip<>(t)); + out.addImage(getIcon()).size(10 * scale).color(Color.DARK_GRAY).padRight(-10 * scale).padBottom(-scale * 2); + out.addImage(getIcon()).size(10 * scale).color(Palette.accent); + out.addImage("icon-missing").size(10 * scale).color(Palette.remove).padLeft(-10 * scale); + }).size(10 * scale).get().addListener(new Tooltip<>(t)); } public abstract void buildTooltip(Table table); public abstract String getIcon(); + public abstract void update(Block block, TileEntity entity); + public abstract boolean valid(Block block, TileEntity entity); + public abstract void display(BlockStats stats); } diff --git a/core/src/io/anuke/mindustry/world/consumers/ConsumeItem.java b/core/src/io/anuke/mindustry/world/consumers/ConsumeItem.java index 91041bec16..61be341aa9 100644 --- a/core/src/io/anuke/mindustry/world/consumers/ConsumeItem.java +++ b/core/src/io/anuke/mindustry/world/consumers/ConsumeItem.java @@ -9,50 +9,50 @@ import io.anuke.mindustry.world.meta.BlockStat; import io.anuke.mindustry.world.meta.BlockStats; import io.anuke.ucore.scene.ui.layout.Table; -public class ConsumeItem extends Consume { +public class ConsumeItem extends Consume{ private final Item item; private final int amount; - public ConsumeItem(Item item) { + public ConsumeItem(Item item){ this.item = item; this.amount = 1; } - public ConsumeItem(Item item, int amount) { + public ConsumeItem(Item item, int amount){ this.item = item; this.amount = amount; } - public int getAmount() { + public int getAmount(){ return amount; } - public Item get() { + public Item get(){ return item; } @Override - public void buildTooltip(Table table) { - table.add(new ItemImage(new ItemStack(item, amount))).size(8*4); + public void buildTooltip(Table table){ + table.add(new ItemImage(new ItemStack(item, amount))).size(8 * 4); } @Override - public String getIcon() { + public String getIcon(){ return "icon-item"; } @Override - public void update(Block block, TileEntity entity) { + public void update(Block block, TileEntity entity){ //doesn't update because consuming items is very specific } @Override - public boolean valid(Block block, TileEntity entity) { + public boolean valid(Block block, TileEntity entity){ return entity.items.has(item, amount); } @Override - public void display(BlockStats stats) { + public void display(BlockStats stats){ stats.add(BlockStat.inputItem, item); } } diff --git a/core/src/io/anuke/mindustry/world/consumers/ConsumeItemFilter.java b/core/src/io/anuke/mindustry/world/consumers/ConsumeItemFilter.java index e226ad49b9..6860fee801 100644 --- a/core/src/io/anuke/mindustry/world/consumers/ConsumeItemFilter.java +++ b/core/src/io/anuke/mindustry/world/consumers/ConsumeItemFilter.java @@ -13,21 +13,21 @@ import io.anuke.ucore.scene.ui.layout.Table; public class ConsumeItemFilter extends Consume{ private final Predicate filter; - public ConsumeItemFilter(Predicate item) { + public ConsumeItemFilter(Predicate item){ this.filter = item; } @Override - public void buildTooltip(Table table) { + public void buildTooltip(Table table){ Array list = new Array<>(); for(Item item : Item.all()){ if(filter.test(item)) list.add(item); } - for (int i = 0; i < list.size; i++) { + for(int i = 0; i < list.size; i++){ Item item = list.get(i); - table.addImage(item.region).size(8*4).padRight(2).padLeft(2); + table.addImage(item.region).size(8 * 4).padRight(2).padLeft(2); if(i != list.size - 1){ table.add("/"); } @@ -35,18 +35,18 @@ public class ConsumeItemFilter extends Consume{ } @Override - public String getIcon() { + public String getIcon(){ return "icon-item"; } @Override - public void update(Block block, TileEntity entity) { + public void update(Block block, TileEntity entity){ } @Override - public boolean valid(Block block, TileEntity entity) { - for(int i = 0; i < Item.all().size; i ++){ + public boolean valid(Block block, TileEntity entity){ + for(int i = 0; i < Item.all().size; i++){ Item item = Item.getByID(i); if(entity.items.has(item) && this.filter.test(item)){ return true; @@ -56,7 +56,7 @@ public class ConsumeItemFilter extends Consume{ } @Override - public void display(BlockStats stats) { + public void display(BlockStats stats){ stats.add(BlockStat.inputItems, new ItemFilterValue(filter)); } } diff --git a/core/src/io/anuke/mindustry/world/consumers/ConsumeItems.java b/core/src/io/anuke/mindustry/world/consumers/ConsumeItems.java index c6336105f3..4215ac2fc2 100644 --- a/core/src/io/anuke/mindustry/world/consumers/ConsumeItems.java +++ b/core/src/io/anuke/mindustry/world/consumers/ConsumeItems.java @@ -9,41 +9,41 @@ import io.anuke.mindustry.world.meta.BlockStats; import io.anuke.mindustry.world.meta.values.ItemListValue; import io.anuke.ucore.scene.ui.layout.Table; -public class ConsumeItems extends Consume { +public class ConsumeItems extends Consume{ private ItemStack[] items; - public ConsumeItems(ItemStack[] items) { + public ConsumeItems(ItemStack[] items){ this.items = items; } - public ItemStack[] getItems() { + public ItemStack[] getItems(){ return items; } @Override - public void buildTooltip(Table table) { + public void buildTooltip(Table table){ for(ItemStack stack : items){ - table.add(new ItemImage(stack)).size(8*4).padRight(5); + table.add(new ItemImage(stack)).size(8 * 4).padRight(5); } } @Override - public String getIcon() { + public String getIcon(){ return "icon-item"; } @Override - public void update(Block block, TileEntity entity) { + public void update(Block block, TileEntity entity){ } @Override - public boolean valid(Block block, TileEntity entity) { + public boolean valid(Block block, TileEntity entity){ return entity.items.has(items); } @Override - public void display(BlockStats stats) { + public void display(BlockStats stats){ stats.add(BlockStat.inputItems, new ItemListValue(items)); } } diff --git a/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquid.java b/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquid.java index 084e7cbe60..f8962ae760 100644 --- a/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquid.java +++ b/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquid.java @@ -9,50 +9,50 @@ import io.anuke.mindustry.world.meta.StatUnit; import io.anuke.ucore.core.Timers; import io.anuke.ucore.scene.ui.layout.Table; -public class ConsumeLiquid extends Consume { +public class ConsumeLiquid extends Consume{ protected final float use; protected final Liquid liquid; - public ConsumeLiquid(Liquid liquid, float use) { + public ConsumeLiquid(Liquid liquid, float use){ this.liquid = liquid; this.use = use; } - public float used() { + public float used(){ return use; } - public Liquid get() { + public Liquid get(){ return liquid; } @Override - public void buildTooltip(Table table) { - table.addImage(liquid.getContentIcon()).size(8*3); + public void buildTooltip(Table table){ + table.addImage(liquid.getContentIcon()).size(8 * 3); } @Override - public String getIcon() { + public String getIcon(){ return "icon-liquid"; } @Override - public void update(Block block, TileEntity entity) { + public void update(Block block, TileEntity entity){ entity.liquids.remove(liquid, Math.min(use(block), entity.liquids.get(liquid))); } @Override - public boolean valid(Block block, TileEntity entity) { + public boolean valid(Block block, TileEntity entity){ return entity.liquids.get(liquid) >= use(block); } @Override - public void display(BlockStats stats) { + public void display(BlockStats stats){ stats.add(BlockStat.liquidUse, use * 60f, StatUnit.liquidSecond); stats.add(BlockStat.inputLiquid, liquid); } - float use(Block block) { + float use(Block block){ return Math.min(use * Timers.delta(), block.liquidCapacity); } } diff --git a/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquidFilter.java b/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquidFilter.java index c6e818dbdc..abbd36df25 100644 --- a/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquidFilter.java +++ b/core/src/io/anuke/mindustry/world/consumers/ConsumeLiquidFilter.java @@ -17,7 +17,7 @@ public class ConsumeLiquidFilter extends Consume{ private final float use; private final boolean isFuel; - public ConsumeLiquidFilter(Predicate liquid, float amount, boolean isFuel) { + public ConsumeLiquidFilter(Predicate liquid, float amount, boolean isFuel){ this.filter = liquid; this.use = amount; this.isFuel = isFuel; @@ -28,16 +28,16 @@ public class ConsumeLiquidFilter extends Consume{ } @Override - public void buildTooltip(Table table) { + public void buildTooltip(Table table){ Array list = new Array<>(); for(Liquid item : Liquid.all()){ if(!item.isHidden() && filter.test(item)) list.add(item); } - for (int i = 0; i < list.size; i++) { + for(int i = 0; i < list.size; i++){ Liquid item = list.get(i); - table.addImage(item.getContentIcon()).size(8*3).padRight(2).padLeft(2).padTop(2).padBottom(2); + table.addImage(item.getContentIcon()).size(8 * 3).padRight(2).padLeft(2).padTop(2).padBottom(2); if(i != list.size - 1){ table.add("/"); } @@ -45,32 +45,32 @@ public class ConsumeLiquidFilter extends Consume{ } @Override - public String getIcon() { + public String getIcon(){ return "icon-liquid"; } @Override - public void update(Block block, TileEntity entity) { + public void update(Block block, TileEntity entity){ entity.liquids.remove(entity.liquids.current(), use(block)); } @Override - public boolean valid(Block block, TileEntity entity) { + public boolean valid(Block block, TileEntity entity){ return filter.test(entity.liquids.current()) && entity.liquids.currentAmount() >= use(block); } @Override - public void display(BlockStats stats) { + public void display(BlockStats stats){ if(isFuel){ stats.add(BlockStat.inputLiquidFuel, new LiquidFilterValue(filter)); stats.add(BlockStat.liquidFuelUse, 60f * use, StatUnit.liquidSecond); - }else { + }else{ stats.add(BlockStat.inputLiquid, new LiquidFilterValue(filter)); stats.add(BlockStat.liquidUse, 60f * use, StatUnit.liquidSecond); } } - float use(Block block) { + float use(Block block){ return Math.min(use * Timers.delta(), block.liquidCapacity); } } diff --git a/core/src/io/anuke/mindustry/world/consumers/ConsumePower.java b/core/src/io/anuke/mindustry/world/consumers/ConsumePower.java index 482c53916f..82bd1e56e0 100644 --- a/core/src/io/anuke/mindustry/world/consumers/ConsumePower.java +++ b/core/src/io/anuke/mindustry/world/consumers/ConsumePower.java @@ -8,35 +8,35 @@ import io.anuke.mindustry.world.meta.StatUnit; import io.anuke.ucore.core.Timers; import io.anuke.ucore.scene.ui.layout.Table; -public class ConsumePower extends Consume { +public class ConsumePower extends Consume{ private final float use; - public ConsumePower(float use) { + public ConsumePower(float use){ this.use = use; } @Override - public void buildTooltip(Table table) { + public void buildTooltip(Table table){ } @Override - public String getIcon() { + public String getIcon(){ return "icon-power"; } @Override - public void update(Block block, TileEntity entity) { + public void update(Block block, TileEntity entity){ entity.power.amount -= Math.min(use(block), entity.power.amount); } @Override - public boolean valid(Block block, TileEntity entity) { + public boolean valid(Block block, TileEntity entity){ return entity.power.amount >= use(block); } @Override - public void display(BlockStats stats) { + public void display(BlockStats stats){ stats.add(BlockStat.powerUse, use * 60f, StatUnit.powerSecond); } diff --git a/core/src/io/anuke/mindustry/world/consumers/Consumers.java b/core/src/io/anuke/mindustry/world/consumers/Consumers.java index 8bb729054f..a0ea86d201 100644 --- a/core/src/io/anuke/mindustry/world/consumers/Consumers.java +++ b/core/src/io/anuke/mindustry/world/consumers/Consumers.java @@ -10,7 +10,7 @@ import io.anuke.mindustry.world.Block; import io.anuke.ucore.function.Consumer; import io.anuke.ucore.util.ThreadArray; -public class Consumers { +public class Consumers{ private ObjectMap, Consume> map = new ObjectMap<>(); private ObjectSet> required = new ObjectSet<>(); private ThreadArray results = new ThreadArray<>(); @@ -20,7 +20,7 @@ public class Consumers { } public void checkRequired(Block block){ - for (Class c : required){ + for(Class c : required){ if(!map.containsKey(c)){ throw new RuntimeException("Missing required consumer of type \"" + ClassReflection.getSimpleName(c) + "\" in block \"" + block.name + "\"!"); } @@ -92,14 +92,14 @@ public class Consumers { if(!map.containsKey(type)){ throw new IllegalArgumentException("Block does not contain consumer of type '" + type + "'!"); } - return (T)map.get(type); + return (T) map.get(type); } - public Iterable all() { + public Iterable all(){ return map.values(); } - public ThreadArray array() { + public ThreadArray array(){ return results; } diff --git a/core/src/io/anuke/mindustry/world/mapgen/GenProperties.java b/core/src/io/anuke/mindustry/world/mapgen/GenProperties.java index 88a00e6709..b11aca6b8c 100644 --- a/core/src/io/anuke/mindustry/world/mapgen/GenProperties.java +++ b/core/src/io/anuke/mindustry/world/mapgen/GenProperties.java @@ -1,6 +1,6 @@ package io.anuke.mindustry.world.mapgen; -public class GenProperties { +public class GenProperties{ public long seed; public MapStyle maps; public OreStyle ores; @@ -11,24 +11,40 @@ public class GenProperties { public EnvironmentStyle environment; enum MapStyle{ - /**256x512*/ + /** + * 256x512 + */ longY, - /**128x256*/ + /** + * 128x256 + */ smallY, - /**128x128*/ + /** + * 128x128 + */ small, - /**256x256*/ + /** + * 256x256 + */ normal } enum OreStyle{ - /**'vanilla' noise-distributed ores*/ + /** + * 'vanilla' noise-distributed ores + */ normal, - /**ores hug the walls*/ + /** + * ores hug the walls + */ nearWalls, - /**ores hug all liquid rivers*/ + /** + * ores hug all liquid rivers + */ nearRivers, - /**large veins*/ + /** + * large veins + */ largeVeins } @@ -40,22 +56,36 @@ public class GenProperties { } enum RiverStyle{ - /**long thin river spanning entire map*/ + /** + * long thin river spanning entire map + */ longThin, - /**long river branching into many others*/ + /** + * long river branching into many others + */ longBranch, - /**one long, thick river*/ + /** + * one long, thick river + */ longThick, - /**short, thick river that ends in a lake*/ + /** + * short, thick river that ends in a lake + */ shortLake } enum TerrainStyle{ - /**bordered around by the normal material*/ + /** + * bordered around by the normal material + */ normal, - /**everything is islands*/ + /** + * everything is islands + */ waterIslands, - /**everything is islands: lava edition*/ + /** + * everything is islands: lava edition + */ lavaIslands } diff --git a/core/src/io/anuke/mindustry/world/mapgen/WorldGenerator.java b/core/src/io/anuke/mindustry/world/mapgen/WorldGenerator.java index 8d53a33c0d..6db249a9f1 100644 --- a/core/src/io/anuke/mindustry/world/mapgen/WorldGenerator.java +++ b/core/src/io/anuke/mindustry/world/mapgen/WorldGenerator.java @@ -27,234 +27,236 @@ import static io.anuke.mindustry.Vars.state; import static io.anuke.mindustry.Vars.world; -public class WorldGenerator { - static int oreIndex = 0; - - /**Should fill spawns with the correct spawnpoints.*/ - public static void loadTileData(Tile[][] tiles, MapTileData data, boolean genOres, int seed){ - data.position(0, 0); - TileDataMarker marker = data.newDataMarker(); +public class WorldGenerator{ + static int oreIndex = 0; - for(int y = 0; y < data.height(); y ++){ - for(int x = 0; x < data.width(); x ++){ - data.read(marker); - - tiles[x][y] = new Tile(x, y, marker.floor, marker.wall == Blocks.blockpart.id ? 0 : marker.wall, marker.rotation, marker.team, marker.elevation); - } - } + /** + * Should fill spawns with the correct spawnpoints. + */ + public static void loadTileData(Tile[][] tiles, MapTileData data, boolean genOres, int seed){ + data.position(0, 0); + TileDataMarker marker = data.newDataMarker(); - prepareTiles(tiles, seed, genOres); - } + for(int y = 0; y < data.height(); y++){ + for(int x = 0; x < data.width(); x++){ + data.read(marker); - public static void prepareTiles(Tile[][] tiles, int seed, boolean genOres){ - - //find multiblocks - IntArray multiblocks = new IntArray(); + tiles[x][y] = new Tile(x, y, marker.floor, marker.wall == Blocks.blockpart.id ? 0 : marker.wall, marker.rotation, marker.team, marker.elevation); + } + } - for(int x = 0; x < tiles.length; x ++) { - for (int y = 0; y < tiles[0].length; y++) { - Tile tile = tiles[x][y]; - - Team team = tile.getTeam(); + prepareTiles(tiles, seed, genOres); + } - if(tile.block() == StorageBlocks.core && - state.teams.has(team)){ - state.teams.get(team).cores.add(tile); - } - - if(tiles[x][y].block().isMultiblock()){ - multiblocks.add(tiles[x][y].packedPosition()); - } - } - } + public static void prepareTiles(Tile[][] tiles, int seed, boolean genOres){ - //place multiblocks now - for(int i = 0; i < multiblocks.size; i ++){ - int pos = multiblocks.get(i); + //find multiblocks + IntArray multiblocks = new IntArray(); - int x = pos % tiles.length; - int y = pos / tiles[0].length; + for(int x = 0; x < tiles.length; x++){ + for(int y = 0; y < tiles[0].length; y++){ + Tile tile = tiles[x][y]; - Block result = tiles[x][y].block(); - Team team = tiles[x][y].getTeam(); + Team team = tile.getTeam(); - int offsetx = -(result.size-1)/2; - int offsety = -(result.size-1)/2; + if(tile.block() == StorageBlocks.core && + state.teams.has(team)){ + state.teams.get(team).cores.add(tile); + } - for(int dx = 0; dx < result.size; dx ++){ - for(int dy = 0; dy < result.size; dy ++){ - int worldx = dx + offsetx + x; - int worldy = dy + offsety + y; - if(!(worldx == x && worldy == y)){ - Tile toplace = world.tile(worldx, worldy); - if(toplace != null) { - toplace.setLinked((byte) (dx + offsetx), (byte) (dy + offsety)); - toplace.setTeam(team); - } - } - } - } - } + if(tiles[x][y].block().isMultiblock()){ + multiblocks.add(tiles[x][y].packedPosition()); + } + } + } - //update cliffs, occlusion data - for(int x = 0; x < tiles.length; x ++){ - for(int y = 0; y < tiles[0].length; y ++) { - Tile tile = tiles[x][y]; + //place multiblocks now + for(int i = 0; i < multiblocks.size; i++){ + int pos = multiblocks.get(i); - tile.updateOcclusion(); + int x = pos % tiles.length; + int y = pos / tiles[0].length; - //fix things on cliffs that shouldn't be - if(tile.block() != Blocks.air && tile.cliffs != 0){ - tile.setBlock(Blocks.air); - } - } - } + Block result = tiles[x][y].block(); + Team team = tiles[x][y].getTeam(); - oreIndex = 0; + int offsetx = -(result.size - 1) / 2; + int offsety = -(result.size - 1) / 2; - if(genOres) { - Array ores = Array.with( - new OreEntry(Items.tungsten, 0.3f, seed), - new OreEntry(Items.coal, 0.284f, seed), - new OreEntry(Items.lead, 0.28f, seed), - new OreEntry(Items.titanium, 0.27f, seed), - new OreEntry(Items.thorium, 0.26f, seed) - ); + for(int dx = 0; dx < result.size; dx++){ + for(int dy = 0; dy < result.size; dy++){ + int worldx = dx + offsetx + x; + int worldy = dy + offsety + y; + if(!(worldx == x && worldy == y)){ + Tile toplace = world.tile(worldx, worldy); + if(toplace != null){ + toplace.setLinked((byte) (dx + offsetx), (byte) (dy + offsety)); + toplace.setTeam(team); + } + } + } + } + } - for (int x = 0; x < tiles.length; x++) { - for (int y = 0; y < tiles[0].length; y++) { + //update cliffs, occlusion data + for(int x = 0; x < tiles.length; x++){ + for(int y = 0; y < tiles[0].length; y++){ + Tile tile = tiles[x][y]; - Tile tile = tiles[x][y]; + tile.updateOcclusion(); - if(!tile.floor().hasOres || tile.cliffs != 0 || tile.block() != Blocks.air){ - continue; - } + //fix things on cliffs that shouldn't be + if(tile.block() != Blocks.air && tile.cliffs != 0){ + tile.setBlock(Blocks.air); + } + } + } - for(int i = ores.size-1; i >= 0; i --){ - OreEntry entry = ores.get(i); - if(entry.noise.octaveNoise2D(1, 0.7, 1f / (4 + i*2), x, y)/4f + - Math.abs(0.5f-entry.noise.octaveNoise2D(2, 0.7, 1f / (50 + i*2), x, y)) > 0.48f && - Math.abs(0.5f-entry.noise.octaveNoise2D(1, 1, 1f / (55 + i*4), x, y)) > 0.22f){ - tile.setFloor((Floor) OreBlocks.get(tile.floor(), entry.item)); - break; - } - } - } - } - } - } + oreIndex = 0; - public static void generateMap(Tile[][] tiles, int seed){ - MathUtils.random.setSeed((long)(Math.random() * 99999999)); - Simplex sim = new Simplex(Mathf.random(99999)); - Simplex sim2 = new Simplex(Mathf.random(99999)); - Simplex sim3 = new Simplex(Mathf.random(99999)); + if(genOres){ + Array ores = Array.with( + new OreEntry(Items.tungsten, 0.3f, seed), + new OreEntry(Items.coal, 0.284f, seed), + new OreEntry(Items.lead, 0.28f, seed), + new OreEntry(Items.titanium, 0.27f, seed), + new OreEntry(Items.thorium, 0.26f, seed) + ); - SeedRandom random = new SeedRandom(Mathf.random(99999)); + for(int x = 0; x < tiles.length; x++){ + for(int y = 0; y < tiles[0].length; y++){ - int width = tiles.length, height = tiles[0].length; + Tile tile = tiles[x][y]; - ObjectMap decoration = new ObjectMap<>(); + if(!tile.floor().hasOres || tile.cliffs != 0 || tile.block() != Blocks.air){ + continue; + } - decoration.put(Blocks.grass, Blocks.shrub); - decoration.put(Blocks.stone, Blocks.rock); - decoration.put(Blocks.ice, Blocks.icerock); - decoration.put(Blocks.snow, Blocks.icerock); - decoration.put(Blocks.blackstone, Blocks.blackrock); + for(int i = ores.size - 1; i >= 0; i--){ + OreEntry entry = ores.get(i); + if(entry.noise.octaveNoise2D(1, 0.7, 1f / (4 + i * 2), x, y) / 4f + + Math.abs(0.5f - entry.noise.octaveNoise2D(2, 0.7, 1f / (50 + i * 2), x, y)) > 0.48f && + Math.abs(0.5f - entry.noise.octaveNoise2D(1, 1, 1f / (55 + i * 4), x, y)) > 0.22f){ + tile.setFloor((Floor) OreBlocks.get(tile.floor(), entry.item)); + break; + } + } + } + } + } + } - for (int x = 0; x < width; x++) { - for (int y = 0; y < height; y++) { - Block floor = Blocks.stone; - Block wall = Blocks.air; + public static void generateMap(Tile[][] tiles, int seed){ + MathUtils.random.setSeed((long) (Math.random() * 99999999)); + Simplex sim = new Simplex(Mathf.random(99999)); + Simplex sim2 = new Simplex(Mathf.random(99999)); + Simplex sim3 = new Simplex(Mathf.random(99999)); - double elevation = sim.octaveNoise2D(3, 0.5, 1f/100, x, y) * 4.1 - 1; - double temp = sim3.octaveNoise2D(7, 0.54, 1f/320f, x, y); + SeedRandom random = new SeedRandom(Mathf.random(99999)); - double r = sim2.octaveNoise2D(1, 0.6, 1f/70, x, y); - double edgeDist = Math.max(width/2, height/2) - Math.max(Math.abs(x - width/2), Math.abs(y - height/2)); - double dst = Vector2.dst(width/2, height/2, x, y); - double elevDip = 30; + int width = tiles.length, height = tiles[0].length; - double border = 14; + ObjectMap decoration = new ObjectMap<>(); - if(edgeDist < border){ - elevation += (border - edgeDist)/6.0; - } + decoration.put(Blocks.grass, Blocks.shrub); + decoration.put(Blocks.stone, Blocks.rock); + decoration.put(Blocks.ice, Blocks.icerock); + decoration.put(Blocks.snow, Blocks.icerock); + decoration.put(Blocks.blackstone, Blocks.blackrock); - if(temp < 0.35){ - floor = Blocks.snow; - }else if(temp < 0.45){ - floor = Blocks.stone; - }else if(temp < 0.65){ - floor = Blocks.grass; - }else if(temp < 0.8){ - floor = Blocks.sand; - }else if(temp < 0.9){ - floor = Blocks.blackstone; - elevation = 0f; - }else{ - floor = Blocks.lava; - } + for(int x = 0; x < width; x++){ + for(int y = 0; y < height; y++){ + Block floor = Blocks.stone; + Block wall = Blocks.air; - if(dst < elevDip){ - elevation -= (elevDip - dst)/elevDip * 3.0; - }else if(r > 0.9){ - floor = Blocks.water; - elevation = 0; + double elevation = sim.octaveNoise2D(3, 0.5, 1f / 100, x, y) * 4.1 - 1; + double temp = sim3.octaveNoise2D(7, 0.54, 1f / 320f, x, y); - if(r > 0.94){ - floor = Blocks.deepwater; - } - } + double r = sim2.octaveNoise2D(1, 0.6, 1f / 70, x, y); + double edgeDist = Math.max(width / 2, height / 2) - Math.max(Math.abs(x - width / 2), Math.abs(y - height / 2)); + double dst = Vector2.dst(width / 2, height / 2, x, y); + double elevDip = 30; - if(wall == Blocks.air && decoration.containsKey(floor) && random.chance(0.03)){ - wall = decoration.get(floor); - } + double border = 14; - Tile tile = new Tile(x, y, (byte)floor.id, (byte)wall.id); - tile.elevation = (byte)Math.max(elevation, 0); - tiles[x][y] = tile; - } - } + if(edgeDist < border){ + elevation += (border - edgeDist) / 6.0; + } - for (int x = 0; x < width; x++) { - for (int y = 0; y < height; y++) { - Tile tile = tiles[x][y]; + if(temp < 0.35){ + floor = Blocks.snow; + }else if(temp < 0.45){ + floor = Blocks.stone; + }else if(temp < 0.65){ + floor = Blocks.grass; + }else if(temp < 0.8){ + floor = Blocks.sand; + }else if(temp < 0.9){ + floor = Blocks.blackstone; + elevation = 0f; + }else{ + floor = Blocks.lava; + } - byte elevation = tile.elevation; + if(dst < elevDip){ + elevation -= (elevDip - dst) / elevDip * 3.0; + }else if(r > 0.9){ + floor = Blocks.water; + elevation = 0; - for(GridPoint2 point : Geometry.d4){ - if(!Mathf.inBounds(x + point.x, y + point.y, width, height)) continue; - if(tiles[x + point.x][y + point.y].elevation < elevation){ + if(r > 0.94){ + floor = Blocks.deepwater; + } + } - if(Mathf.chance(0.05)){ - tile.elevation = -1; - } - break; - } - } - } - } + if(wall == Blocks.air && decoration.containsKey(floor) && random.chance(0.03)){ + wall = decoration.get(floor); + } - tiles[width/2][height/2].setBlock(StorageBlocks.core); - tiles[width/2][height/2].setTeam(Team.blue); - - prepareTiles(tiles, seed, true); - } + Tile tile = new Tile(x, y, (byte) floor.id, (byte) wall.id); + tile.elevation = (byte) Math.max(elevation, 0); + tiles[x][y] = tile; + } + } - public static class OreEntry{ - final float frequency; - final Item item; - final Simplex noise; - final RidgedPerlin ridge; - final int index; + for(int x = 0; x < width; x++){ + for(int y = 0; y < height; y++){ + Tile tile = tiles[x][y]; - OreEntry(Item item, float frequency, int seed) { - this.frequency = frequency; - this.item = item; - this.noise = new Simplex(seed + oreIndex); - this.ridge = new RidgedPerlin(seed + oreIndex, 2); - this.index = oreIndex ++; - } - } + byte elevation = tile.elevation; + + for(GridPoint2 point : Geometry.d4){ + if(!Mathf.inBounds(x + point.x, y + point.y, width, height)) continue; + if(tiles[x + point.x][y + point.y].elevation < elevation){ + + if(Mathf.chance(0.05)){ + tile.elevation = -1; + } + break; + } + } + } + } + + tiles[width / 2][height / 2].setBlock(StorageBlocks.core); + tiles[width / 2][height / 2].setTeam(Team.blue); + + prepareTiles(tiles, seed, true); + } + + public static class OreEntry{ + final float frequency; + final Item item; + final Simplex noise; + final RidgedPerlin ridge; + final int index; + + OreEntry(Item item, float frequency, int seed){ + this.frequency = frequency; + this.item = item; + this.noise = new Simplex(seed + oreIndex); + this.ridge = new RidgedPerlin(seed + oreIndex, 2); + this.index = oreIndex++; + } + } } diff --git a/core/src/io/anuke/mindustry/world/meta/BlockBar.java b/core/src/io/anuke/mindustry/world/meta/BlockBar.java index d322dffa5f..d692e152b5 100644 --- a/core/src/io/anuke/mindustry/world/meta/BlockBar.java +++ b/core/src/io/anuke/mindustry/world/meta/BlockBar.java @@ -3,12 +3,12 @@ package io.anuke.mindustry.world.meta; import io.anuke.mindustry.world.BarType; import io.anuke.mindustry.world.Tile; -public class BlockBar { +public class BlockBar{ public final ValueSupplier value; public final BarType type; public final boolean top; - public BlockBar(BarType type, boolean top, ValueSupplier value) { + public BlockBar(BarType type, boolean top, ValueSupplier value){ this.value = value; this.type = type; this.top = top; diff --git a/core/src/io/anuke/mindustry/world/meta/BlockBars.java b/core/src/io/anuke/mindustry/world/meta/BlockBars.java index 6b02067976..35507b616b 100644 --- a/core/src/io/anuke/mindustry/world/meta/BlockBars.java +++ b/core/src/io/anuke/mindustry/world/meta/BlockBars.java @@ -3,8 +3,8 @@ package io.anuke.mindustry.world.meta; import com.badlogic.gdx.utils.Array; import io.anuke.mindustry.world.BarType; -public class BlockBars { - private Array list = Array.with(new BlockBar(BarType.health, false, tile -> tile.entity.health / (float)tile.block().health)); +public class BlockBars{ + private Array list = Array.with(new BlockBar(BarType.health, false, tile -> tile.entity.health / (float) tile.block().health)); public void add(BlockBar bar){ list.add(bar); @@ -36,7 +36,7 @@ public class BlockBars { list.removeAll(removals, true); } - public Array list() { + public Array list(){ return list; } } diff --git a/core/src/io/anuke/mindustry/world/meta/BlockFlag.java b/core/src/io/anuke/mindustry/world/meta/BlockFlag.java index 9fb0025c0c..44820e6646 100644 --- a/core/src/io/anuke/mindustry/world/meta/BlockFlag.java +++ b/core/src/io/anuke/mindustry/world/meta/BlockFlag.java @@ -1,17 +1,29 @@ package io.anuke.mindustry.world.meta; -public enum BlockFlag { - /**General important target for all types of units.*/ +public enum BlockFlag{ + /** + * General important target for all types of units. + */ target(0), - /**Point to resupply resources.*/ + /** + * Point to resupply resources. + */ resupplyPoint(Float.MAX_VALUE), - /**Point to drop off resources.*/ + /** + * Point to drop off resources. + */ dropPoint(Float.MAX_VALUE), - /**Producer of important goods.*/ + /** + * Producer of important goods. + */ producer(20), - /**Producer or storage unit of volatile materials.*/ + /** + * Producer or storage unit of volatile materials. + */ explosive(10), - /**Repair point.*/ + /** + * Repair point. + */ repair(Float.MAX_VALUE); public final float cost; diff --git a/core/src/io/anuke/mindustry/world/meta/BlockGroup.java b/core/src/io/anuke/mindustry/world/meta/BlockGroup.java index e9795a956a..dd90943ea0 100644 --- a/core/src/io/anuke/mindustry/world/meta/BlockGroup.java +++ b/core/src/io/anuke/mindustry/world/meta/BlockGroup.java @@ -1,5 +1,5 @@ package io.anuke.mindustry.world.meta; -public enum BlockGroup { +public enum BlockGroup{ none, walls, turrets, transportation, power, liquids, drills } diff --git a/core/src/io/anuke/mindustry/world/meta/BlockStat.java b/core/src/io/anuke/mindustry/world/meta/BlockStat.java index 03e715abdb..97f17c6c25 100644 --- a/core/src/io/anuke/mindustry/world/meta/BlockStat.java +++ b/core/src/io/anuke/mindustry/world/meta/BlockStat.java @@ -2,8 +2,10 @@ package io.anuke.mindustry.world.meta; import io.anuke.ucore.util.Bundles; -/**Describes one type of stat for a block.*/ -public enum BlockStat { +/** + * Describes one type of stat for a block. + */ +public enum BlockStat{ health(StatCategory.general), size(StatCategory.general), @@ -41,14 +43,12 @@ public enum BlockStat { shots(StatCategory.shooting), reload(StatCategory.shooting), powerShot(StatCategory.shooting), - targetsAir(StatCategory.shooting) -, - ; + targetsAir(StatCategory.shooting),; public final StatCategory category; - BlockStat(StatCategory category) { + BlockStat(StatCategory category){ this.category = category; } diff --git a/core/src/io/anuke/mindustry/world/meta/BlockStats.java b/core/src/io/anuke/mindustry/world/meta/BlockStats.java index e697c0255f..c4f9698616 100644 --- a/core/src/io/anuke/mindustry/world/meta/BlockStats.java +++ b/core/src/io/anuke/mindustry/world/meta/BlockStats.java @@ -10,50 +10,66 @@ import io.anuke.mindustry.world.meta.values.*; import io.anuke.ucore.util.Bundles; import io.anuke.ucore.util.Log; -/**Hold and organizes a list of block stats.*/ -public class BlockStats { +/** + * Hold and organizes a list of block stats. + */ +public class BlockStats{ private static final boolean errorWhenMissing = true; private final OrderedMap> map = new OrderedMap<>(); private final Block block; private boolean dirty; - public BlockStats(Block block) { + public BlockStats(Block block){ this.block = block; } - /**Adds a single float value with this stat, formatted to 2 decimal places.*/ + /** + * Adds a single float value with this stat, formatted to 2 decimal places. + */ public void add(BlockStat stat, float value, StatUnit unit){ add(stat, new NumberValue(value, unit)); } - /**Adds a single y/n boolean value.*/ + /** + * Adds a single y/n boolean value. + */ public void add(BlockStat stat, boolean value){ add(stat, new BooleanValue(value)); } - /**Adds an item value.*/ + /** + * Adds an item value. + */ public void add(BlockStat stat, Item item){ add(stat, new ItemValue(new ItemStack(item, 1))); } - /**Adds a liquid value.*/ + /** + * Adds a liquid value. + */ public void add(BlockStat stat, Liquid liquid){ add(stat, new LiquidValue(liquid)); } - /**Adds an item value.*/ + /** + * Adds an item value. + */ public void add(BlockStat stat, ItemStack item){ add(stat, new ItemValue(item)); } - /**Adds a single string value with this stat.*/ + /** + * Adds a single string value with this stat. + */ public void add(BlockStat stat, String format, Object... args){ add(stat, new StringValue(format, args)); } - /**Adds a stat value.*/ + /** + * Adds a stat value. + */ public void add(BlockStat stat, StatValue value){ if(!Bundles.has("text.blocks." + stat.name().toLowerCase())){ if(!errorWhenMissing){ @@ -72,7 +88,7 @@ public class BlockStats { } if(map.containsKey(stat.category) && map.get(stat.category).containsKey(stat)){ - throw new RuntimeException("Duplicate stat entry: \"" +stat + "\" in block '" + block.name + "'"); + throw new RuntimeException("Duplicate stat entry: \"" + stat + "\" in block '" + block.name + "'"); } if(!map.containsKey(stat.category)){ @@ -94,11 +110,11 @@ public class BlockStats { dirty = true; } - public OrderedMap> toMap() { + public OrderedMap> toMap(){ //sort stats by index if they've been modified - if(dirty) { + if(dirty){ map.orderedKeys().sort(); - for (Entry> entry : map.entries()) { + for(Entry> entry : map.entries()){ entry.value.orderedKeys().sort(); } diff --git a/core/src/io/anuke/mindustry/world/meta/ContentStatValue.java b/core/src/io/anuke/mindustry/world/meta/ContentStatValue.java index f3aca0b83d..6a8375522e 100644 --- a/core/src/io/anuke/mindustry/world/meta/ContentStatValue.java +++ b/core/src/io/anuke/mindustry/world/meta/ContentStatValue.java @@ -2,6 +2,6 @@ package io.anuke.mindustry.world.meta; import io.anuke.mindustry.game.UnlockableContent; -public interface ContentStatValue extends StatValue { +public interface ContentStatValue extends StatValue{ UnlockableContent[] getValueContent(); } diff --git a/core/src/io/anuke/mindustry/world/meta/StatCategory.java b/core/src/io/anuke/mindustry/world/meta/StatCategory.java index 4932b136fc..ce1ef5b6f2 100644 --- a/core/src/io/anuke/mindustry/world/meta/StatCategory.java +++ b/core/src/io/anuke/mindustry/world/meta/StatCategory.java @@ -1,7 +1,9 @@ package io.anuke.mindustry.world.meta; -/**A specific category for a stat.*/ -public enum StatCategory { +/** + * A specific category for a stat. + */ +public enum StatCategory{ general, power, liquids, diff --git a/core/src/io/anuke/mindustry/world/meta/StatUnit.java b/core/src/io/anuke/mindustry/world/meta/StatUnit.java index c093d31fb3..781f18806f 100644 --- a/core/src/io/anuke/mindustry/world/meta/StatUnit.java +++ b/core/src/io/anuke/mindustry/world/meta/StatUnit.java @@ -2,8 +2,10 @@ package io.anuke.mindustry.world.meta; import io.anuke.ucore.util.Bundles; -/**Defines a unit of measurement for block stats.*/ -public enum StatUnit { +/** + * Defines a unit of measurement for block stats. + */ +public enum StatUnit{ blocks, powerSecond, liquidSecond, diff --git a/core/src/io/anuke/mindustry/world/meta/StatValue.java b/core/src/io/anuke/mindustry/world/meta/StatValue.java index 7a6784ca88..5b95ff6e4d 100644 --- a/core/src/io/anuke/mindustry/world/meta/StatValue.java +++ b/core/src/io/anuke/mindustry/world/meta/StatValue.java @@ -2,9 +2,13 @@ package io.anuke.mindustry.world.meta; import io.anuke.ucore.scene.ui.layout.Table; -/**A base interface for a value of a stat that is displayed.*/ -public interface StatValue { - /**This method should all elements necessary to display this stat to the specified table. - * For example, a stat that is just text would add label to the table. */ +/** + * A base interface for a value of a stat that is displayed. + */ +public interface StatValue{ + /** + * This method should all elements necessary to display this stat to the specified table. + * For example, a stat that is just text would add label to the table. + */ void display(Table table); } diff --git a/core/src/io/anuke/mindustry/world/meta/values/BooleanValue.java b/core/src/io/anuke/mindustry/world/meta/values/BooleanValue.java index ace31e66ce..7adb9bb0b5 100644 --- a/core/src/io/anuke/mindustry/world/meta/values/BooleanValue.java +++ b/core/src/io/anuke/mindustry/world/meta/values/BooleanValue.java @@ -3,15 +3,15 @@ package io.anuke.mindustry.world.meta.values; import io.anuke.mindustry.world.meta.StatValue; import io.anuke.ucore.scene.ui.layout.Table; -public class BooleanValue implements StatValue { +public class BooleanValue implements StatValue{ private final boolean value; - public BooleanValue(boolean value) { + public BooleanValue(boolean value){ this.value = value; } @Override - public void display(Table table) { + public void display(Table table){ table.add(!value ? "$text.no" : "$text.yes"); } } diff --git a/core/src/io/anuke/mindustry/world/meta/values/ItemFilterValue.java b/core/src/io/anuke/mindustry/world/meta/values/ItemFilterValue.java index 60ac578f41..f0b4708968 100644 --- a/core/src/io/anuke/mindustry/world/meta/values/ItemFilterValue.java +++ b/core/src/io/anuke/mindustry/world/meta/values/ItemFilterValue.java @@ -6,24 +6,24 @@ import io.anuke.mindustry.world.meta.StatValue; import io.anuke.ucore.function.Predicate; import io.anuke.ucore.scene.ui.layout.Table; -public class ItemFilterValue implements StatValue { +public class ItemFilterValue implements StatValue{ private final Predicate filter; - public ItemFilterValue(Predicate filter) { + public ItemFilterValue(Predicate filter){ this.filter = filter; } @Override - public void display(Table table) { + public void display(Table table){ Array list = new Array<>(); for(Item item : Item.all()){ if(filter.test(item)) list.add(item); } - for (int i = 0; i < list.size; i++) { + for(int i = 0; i < list.size; i++){ Item item = list.get(i); - table.addImage(item.region).size(8*3).padRight(2).padLeft(2); + table.addImage(item.region).size(8 * 3).padRight(2).padLeft(2); if(i != list.size - 1){ table.add("/"); } diff --git a/core/src/io/anuke/mindustry/world/meta/values/ItemListValue.java b/core/src/io/anuke/mindustry/world/meta/values/ItemListValue.java index 77890bfe2e..9a236306a3 100644 --- a/core/src/io/anuke/mindustry/world/meta/values/ItemListValue.java +++ b/core/src/io/anuke/mindustry/world/meta/values/ItemListValue.java @@ -11,23 +11,23 @@ public class ItemListValue implements ContentStatValue{ private final Item[] items; private final ItemStack[] stacks; - public ItemListValue(Item[] items) { + public ItemListValue(Item[] items){ this.items = items; this.stacks = null; } - public ItemListValue(ItemStack[] stacks) { + public ItemListValue(ItemStack[] stacks){ this.stacks = stacks; this.items = null; } @Override - public UnlockableContent[] getValueContent() { + public UnlockableContent[] getValueContent(){ if(items != null){ return items; - }else { + }else{ Item[] res = new Item[stacks.length]; - for (int i = 0; i < res.length; i++) { + for(int i = 0; i < res.length; i++){ res[i] = stacks[i].item; } return res; @@ -35,14 +35,14 @@ public class ItemListValue implements ContentStatValue{ } @Override - public void display(Table table) { + public void display(Table table){ if(items != null){ for(Item item : items){ - table.addImage(item.region).size(8*3).padRight(5); + table.addImage(item.region).size(8 * 3).padRight(5); } }else{ for(ItemStack stack : stacks){ - table.add(new ItemImage(stack)).size(8*3).padRight(5); + table.add(new ItemImage(stack)).size(8 * 3).padRight(5); } } } diff --git a/core/src/io/anuke/mindustry/world/meta/values/ItemValue.java b/core/src/io/anuke/mindustry/world/meta/values/ItemValue.java index d36dd119d6..d42312c4d1 100644 --- a/core/src/io/anuke/mindustry/world/meta/values/ItemValue.java +++ b/core/src/io/anuke/mindustry/world/meta/values/ItemValue.java @@ -7,21 +7,21 @@ import io.anuke.mindustry.ui.ItemImage; import io.anuke.mindustry.world.meta.ContentStatValue; import io.anuke.ucore.scene.ui.layout.Table; -public class ItemValue implements ContentStatValue { +public class ItemValue implements ContentStatValue{ private final ItemStack item; - public ItemValue(ItemStack item) { + public ItemValue(ItemStack item){ this.item = item; } @Override - public UnlockableContent[] getValueContent() { + public UnlockableContent[] getValueContent(){ return new Item[]{item.item}; } @Override - public void display(Table table) { + public void display(Table table){ //TODO better implementation, quantity support - table.add(new ItemImage(item)).size(8*3); + table.add(new ItemImage(item)).size(8 * 3); } } diff --git a/core/src/io/anuke/mindustry/world/meta/values/LiquidFilterValue.java b/core/src/io/anuke/mindustry/world/meta/values/LiquidFilterValue.java index c585a7895f..c8206ad13f 100644 --- a/core/src/io/anuke/mindustry/world/meta/values/LiquidFilterValue.java +++ b/core/src/io/anuke/mindustry/world/meta/values/LiquidFilterValue.java @@ -6,24 +6,24 @@ import io.anuke.mindustry.world.meta.StatValue; import io.anuke.ucore.function.Predicate; import io.anuke.ucore.scene.ui.layout.Table; -public class LiquidFilterValue implements StatValue { +public class LiquidFilterValue implements StatValue{ private final Predicate filter; - public LiquidFilterValue(Predicate filter) { + public LiquidFilterValue(Predicate filter){ this.filter = filter; } @Override - public void display(Table table) { + public void display(Table table){ Array list = new Array<>(); for(Liquid item : Liquid.all()){ if(!item.isHidden() && filter.test(item)) list.add(item); } - for (int i = 0; i < list.size; i++) { + for(int i = 0; i < list.size; i++){ Liquid item = list.get(i); - table.addImage(item.getContentIcon()).size(8*3).padRight(2).padLeft(2).padTop(2).padBottom(2); + table.addImage(item.getContentIcon()).size(8 * 3).padRight(2).padLeft(2).padTop(2).padBottom(2); if(i != list.size - 1){ table.add("/"); } diff --git a/core/src/io/anuke/mindustry/world/meta/values/LiquidValue.java b/core/src/io/anuke/mindustry/world/meta/values/LiquidValue.java index 20db91051a..bdee87897e 100644 --- a/core/src/io/anuke/mindustry/world/meta/values/LiquidValue.java +++ b/core/src/io/anuke/mindustry/world/meta/values/LiquidValue.java @@ -5,20 +5,20 @@ import io.anuke.mindustry.type.Liquid; import io.anuke.mindustry.world.meta.ContentStatValue; import io.anuke.ucore.scene.ui.layout.Table; -public class LiquidValue implements ContentStatValue { +public class LiquidValue implements ContentStatValue{ private final Liquid liquid; - public LiquidValue(Liquid liquid) { + public LiquidValue(Liquid liquid){ this.liquid = liquid; } @Override - public UnlockableContent[] getValueContent() { + public UnlockableContent[] getValueContent(){ return new UnlockableContent[]{liquid}; } @Override - public void display(Table table) { - table.addImage(liquid.getContentIcon()).size(8*3); + public void display(Table table){ + table.addImage(liquid.getContentIcon()).size(8 * 3); } } diff --git a/core/src/io/anuke/mindustry/world/meta/values/NumberValue.java b/core/src/io/anuke/mindustry/world/meta/values/NumberValue.java index 65f6a48a78..0dd73ac91f 100644 --- a/core/src/io/anuke/mindustry/world/meta/values/NumberValue.java +++ b/core/src/io/anuke/mindustry/world/meta/values/NumberValue.java @@ -5,13 +5,15 @@ import io.anuke.mindustry.world.meta.StatValue; import io.anuke.ucore.scene.ui.layout.Table; import io.anuke.ucore.util.Strings; -/**A stat that is a number with a unit attacked. - * The number is rounded to 2 decimal places by default.*/ +/** + * A stat that is a number with a unit attacked. + * The number is rounded to 2 decimal places by default. + */ public class NumberValue implements StatValue{ private final StatUnit unit; private final float value; - public NumberValue(float value, StatUnit unit) { + public NumberValue(float value, StatUnit unit){ this.unit = unit; this.value = value; @@ -21,8 +23,8 @@ public class NumberValue implements StatValue{ } @Override - public void display(Table table) { - float diff = Math.abs((int)value - value); + public void display(Table table){ + float diff = Math.abs((int) value - value); int precision = diff <= 0.01f ? 0 : diff <= 0.1f ? 1 : 2; table.add(Strings.toFixed(value, precision)); diff --git a/core/src/io/anuke/mindustry/world/meta/values/StringValue.java b/core/src/io/anuke/mindustry/world/meta/values/StringValue.java index 1e3ab92fdd..1a0bc458d0 100644 --- a/core/src/io/anuke/mindustry/world/meta/values/StringValue.java +++ b/core/src/io/anuke/mindustry/world/meta/values/StringValue.java @@ -4,15 +4,15 @@ import io.anuke.mindustry.world.meta.StatValue; import io.anuke.ucore.scene.ui.layout.Table; import io.anuke.ucore.util.Strings; -public class StringValue implements StatValue { +public class StringValue implements StatValue{ private final String value; - public StringValue(String value, Object... args) { + public StringValue(String value, Object... args){ this.value = Strings.formatArgs(value, args); } @Override - public void display(Table table) { + public void display(Table table){ table.add(value); } } diff --git a/core/src/io/anuke/mindustry/world/modules/BlockModule.java b/core/src/io/anuke/mindustry/world/modules/BlockModule.java index 0b74bfb5e5..225b7c3e9d 100644 --- a/core/src/io/anuke/mindustry/world/modules/BlockModule.java +++ b/core/src/io/anuke/mindustry/world/modules/BlockModule.java @@ -4,7 +4,8 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; -public abstract class BlockModule { +public abstract class BlockModule{ public abstract void write(DataOutput stream) throws IOException; + public abstract void read(DataInput stream) throws IOException; } diff --git a/core/src/io/anuke/mindustry/world/modules/ConsumeModule.java b/core/src/io/anuke/mindustry/world/modules/ConsumeModule.java index c33e556ee8..40d06f6853 100644 --- a/core/src/io/anuke/mindustry/world/modules/ConsumeModule.java +++ b/core/src/io/anuke/mindustry/world/modules/ConsumeModule.java @@ -30,12 +30,12 @@ public class ConsumeModule extends BlockModule{ } @Override - public void write(DataOutput stream) throws IOException { + public void write(DataOutput stream) throws IOException{ stream.writeBoolean(valid); } @Override - public void read(DataInput stream) throws IOException { + public void read(DataInput stream) throws IOException{ valid = stream.readBoolean(); } } diff --git a/core/src/io/anuke/mindustry/world/modules/InventoryModule.java b/core/src/io/anuke/mindustry/world/modules/InventoryModule.java index f443a3bf3d..85a349f334 100644 --- a/core/src/io/anuke/mindustry/world/modules/InventoryModule.java +++ b/core/src/io/anuke/mindustry/world/modules/InventoryModule.java @@ -13,7 +13,7 @@ public class InventoryModule extends BlockModule{ private int total; public void forEach(ItemConsumer cons){ - for (int i = 0; i < items.length; i++) { + for(int i = 0; i < items.length; i++){ if(items[i] > 0){ cons.accept(Item.getByID(i), items[i]); } @@ -22,7 +22,7 @@ public class InventoryModule extends BlockModule{ public float sum(ItemCalculator calc){ float sum = 0f; - for (int i = 0; i < items.length; i++) { + for(int i = 0; i < items.length; i++){ if(items[i] > 0){ sum += calc.get(Item.getByID(i), items[i]); } @@ -47,12 +47,14 @@ public class InventoryModule extends BlockModule{ public boolean has(ItemStack[] stacks, float amountScaling){ for(ItemStack stack : stacks){ - if(!has(stack.item, (int)(stack.amount * amountScaling))) return false; + if(!has(stack.item, (int) (stack.amount * amountScaling))) return false; } return true; } - /**Returns true if this entity has at least one of each item in each stack.*/ + /** + * Returns true if this entity has at least one of each item in each stack. + */ public boolean hasOne(ItemStack[] stacks){ for(ItemStack stack : stacks){ if(!has(stack.item, 1)) return false; @@ -65,10 +67,10 @@ public class InventoryModule extends BlockModule{ } public Item take(){ - for(int i = 0; i < items.length; i ++){ + for(int i = 0; i < items.length; i++){ if(items[i] > 0){ - items[i] --; - total --; + items[i]--; + total--; return Item.getByID(i); } } @@ -104,15 +106,15 @@ public class InventoryModule extends BlockModule{ } @Override - public void write(DataOutput stream) throws IOException { + public void write(DataOutput stream) throws IOException{ byte amount = 0; - for (int item : items) { - if (item > 0) amount++; + for(int item : items){ + if(item > 0) amount++; } stream.writeByte(amount); //amount of items - for(int i = 0; i < items.length; i ++){ + for(int i = 0; i < items.length; i++){ if(items[i] > 0){ stream.writeByte(i); //item ID stream.writeInt(items[i]); //item amount @@ -121,11 +123,11 @@ public class InventoryModule extends BlockModule{ } @Override - public void read(DataInput stream) throws IOException { + public void read(DataInput stream) throws IOException{ byte count = stream.readByte(); total = 0; - for(int j = 0; j < count; j ++){ + for(int j = 0; j < count; j++){ int itemid = stream.readByte(); int itemamount = stream.readInt(); items[itemid] = itemamount; diff --git a/core/src/io/anuke/mindustry/world/modules/LiquidModule.java b/core/src/io/anuke/mindustry/world/modules/LiquidModule.java index dfd6a4d86e..12c87f1307 100644 --- a/core/src/io/anuke/mindustry/world/modules/LiquidModule.java +++ b/core/src/io/anuke/mindustry/world/modules/LiquidModule.java @@ -6,17 +6,21 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; -public class LiquidModule extends BlockModule { +public class LiquidModule extends BlockModule{ private float[] liquids = new float[Liquid.all().size]; private float total; private Liquid current = Liquid.getByID(0); - /**Returns total amount of liquids.*/ + /** + * Returns total amount of liquids. + */ public float total(){ return total; } - /**Last recieved or loaded liquid. Only valid for liquid modules with 1 type of liquid.*/ + /** + * Last recieved or loaded liquid. Only valid for liquid modules with 1 type of liquid. + */ public Liquid current(){ return current; } @@ -40,7 +44,7 @@ public class LiquidModule extends BlockModule { } public void forEach(LiquidConsumer cons){ - for (int i = 0; i < liquids.length; i++) { + for(int i = 0; i < liquids.length; i++){ if(liquids[i] > 0){ cons.accept(Liquid.getByID(i), liquids[i]); } @@ -49,7 +53,7 @@ public class LiquidModule extends BlockModule { public float sum(LiquidCalculator calc){ float sum = 0f; - for (int i = 0; i < liquids.length; i++) { + for(int i = 0; i < liquids.length; i++){ if(liquids[i] > 0){ sum += calc.get(Liquid.getByID(i), liquids[i]); } @@ -58,15 +62,15 @@ public class LiquidModule extends BlockModule { } @Override - public void write(DataOutput stream) throws IOException { + public void write(DataOutput stream) throws IOException{ byte amount = 0; - for (float liquid : liquids) { - if (liquid > 0) amount++; + for(float liquid : liquids){ + if(liquid > 0) amount++; } stream.writeByte(amount); //amount of liquids - for(int i = 0; i < liquids.length; i ++){ + for(int i = 0; i < liquids.length; i++){ if(liquids[i] > 0){ stream.writeByte(i); //liquid ID stream.writeFloat(liquids[i]); //item amount @@ -75,10 +79,10 @@ public class LiquidModule extends BlockModule { } @Override - public void read(DataInput stream) throws IOException { + public void read(DataInput stream) throws IOException{ byte count = stream.readByte(); - for(int j = 0; j < count; j ++){ + for(int j = 0; j < count; j++){ int liquidid = stream.readByte(); float amount = stream.readFloat(); liquids[liquidid] = amount; diff --git a/core/src/io/anuke/mindustry/world/modules/PowerModule.java b/core/src/io/anuke/mindustry/world/modules/PowerModule.java index fe35aa0d90..320408ec5e 100644 --- a/core/src/io/anuke/mindustry/world/modules/PowerModule.java +++ b/core/src/io/anuke/mindustry/world/modules/PowerModule.java @@ -26,7 +26,7 @@ public class PowerModule extends BlockModule{ } @Override - public void write(DataOutput stream) throws IOException { + public void write(DataOutput stream) throws IOException{ stream.writeFloat(amount); } diff --git a/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java b/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java index af32d6f065..c611915dfe 100644 --- a/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java +++ b/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java @@ -11,7 +11,7 @@ import java.nio.file.Paths; import java.text.SimpleDateFormat; import java.util.Date; -public class CrashHandler { +public class CrashHandler{ public static void handle(Throwable e){ //TODO send full error report to server via HTTP @@ -24,7 +24,7 @@ public class CrashHandler { netActive = Net.active(); netServer = Net.server(); Net.dispose(); - }catch (Throwable p){ + }catch(Throwable p){ p.printStackTrace(); } @@ -39,8 +39,8 @@ public class CrashHandler { header += "Net Active: " + netActive + "\n"; header += "Net Server: " + netServer + "\n"; header += "OS: " + System.getProperty("os.name") + "\n"; - header += "Multithreading: " + Settings.getBool("multithread")+ "\n----\n"; - }catch (Throwable e4){ + header += "Multithreading: " + Settings.getBool("multithread") + "\n----\n"; + }catch(Throwable e4){ header += "\n--error getting additional info--\n"; e4.printStackTrace(); } @@ -55,16 +55,16 @@ public class CrashHandler { try{ filename = "crash-report-" + new SimpleDateFormat("dd-MM-yy h.mm.ss").format(new Date()) + ".txt"; Files.write(Paths.get(System.getProperty("user.home"), "mindustry-crash-reports", filename), result.getBytes()); - }catch (Throwable i){ + }catch(Throwable i){ i.printStackTrace(); failed = true; } try{ javax.swing.JOptionPane.showMessageDialog(null, "An error has occured: \n" + result + "\n\n" + - (!failed ? "A crash report has been written to " + new File(filename).getAbsolutePath() + ".\nPlease send this file to the developer!" - : "Failed to generate crash report.\nPlease send an image of this crash log to the developer!")); - }catch (Throwable i){ + (!failed ? "A crash report has been written to " + new File(filename).getAbsolutePath() + ".\nPlease send this file to the developer!" + : "Failed to generate crash report.\nPlease send an image of this crash log to the developer!")); + }catch(Throwable i){ i.printStackTrace(); //what now? } diff --git a/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java b/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java index aef5a71b04..3c21144f6b 100644 --- a/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java +++ b/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java @@ -30,20 +30,24 @@ import static io.anuke.mindustry.Vars.*; public class DesktopLauncher extends Lwjgl3Application{ ObjectMap prefmap; - - public static void main (String[] arg) { - try { + + public DesktopLauncher(ApplicationListener listener, Lwjgl3ApplicationConfiguration config){ + super(listener, config); + } + + public static void main(String[] arg){ + try{ Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration(); config.setTitle("Mindustry"); config.setMaximized(true); config.setWindowedMode(960, 540); config.setWindowIcon("sprites/icon.png"); - if(OS.isMac) { + if(OS.isMac){ Application.getApplication().setOpenFileHandler(e -> { List list = e.getFiles(); - File target = (File)list.get(0); + File target = (File) list.get(0); Gdx.app.postRunnable(() -> { FileHandle file = OS.getAppDataDirectory("Mindustry").child("tmp").child(target.getName()); @@ -56,7 +60,7 @@ public class DesktopLauncher extends Lwjgl3Application{ try{ SaveSlot slot = control.getSaves().importSave(file); ui.load.runLoadSave(slot); - }catch (IOException e2){ + }catch(IOException e2){ ui.showError(Bundles.format("text.save.import.fail", Strings.parseException(e2, false))); } }else{ @@ -65,7 +69,7 @@ public class DesktopLauncher extends Lwjgl3Application{ }else if(file.extension().equalsIgnoreCase(mapExtension)){ //open map Gdx.app.postRunnable(() -> { - if (!ui.editor.isShown()) { + if(!ui.editor.isShown()){ ui.editor.show(); } @@ -80,26 +84,22 @@ public class DesktopLauncher extends Lwjgl3Application{ Net.setClientProvider(new KryoClient()); Net.setServerProvider(new KryoServer()); - new DesktopLauncher(new Mindustry(), config); - }catch (Throwable e){ - CrashHandler.handle(e); - } - } - - public DesktopLauncher(ApplicationListener listener, Lwjgl3ApplicationConfiguration config) { - super(listener, config); + new DesktopLauncher(new Mindustry(), config); + }catch(Throwable e){ + CrashHandler.handle(e); + } } @Override - public Preferences getPreferences(String name) { - String prefsDirectory = OS.getAppDataDirectoryString("Mindustry"); + public Preferences getPreferences(String name){ + String prefsDirectory = OS.getAppDataDirectoryString("Mindustry"); - if(prefmap == null){ - prefmap = new ObjectMap<>(); + if(prefmap == null){ + prefmap = new ObjectMap<>(); } - if(prefmap.containsKey(name)){ - return prefmap.get(name); + if(prefmap.containsKey(name)){ + return prefmap.get(name); }else{ Preferences prefs = new BinaryPreferences(new Lwjgl3FileHandle(new File(prefsDirectory, name), FileType.Absolute)); prefmap.put(name, prefs); diff --git a/desktop/src/io/anuke/mindustry/desktop/DesktopPlatform.java b/desktop/src/io/anuke/mindustry/desktop/DesktopPlatform.java index b0c2cc8a74..6511f3fe08 100644 --- a/desktop/src/io/anuke/mindustry/desktop/DesktopPlatform.java +++ b/desktop/src/io/anuke/mindustry/desktop/DesktopPlatform.java @@ -27,7 +27,7 @@ import java.util.Locale; import static io.anuke.mindustry.Vars.*; -public class DesktopPlatform extends Platform { +public class DesktopPlatform extends Platform{ final static boolean useDiscord = OS.is64Bit; final static String applicationId = "398246104468291591"; final static DateFormat format = SimpleDateFormat.getDateTimeInstance(); @@ -38,14 +38,14 @@ public class DesktopPlatform extends Platform { Vars.testMobile = isDebug() && Array.with(args).contains("-testMobile", false); - if(useDiscord) { + if(useDiscord){ DiscordEventHandlers handlers = new DiscordEventHandlers(); DiscordRPC.INSTANCE.Discord_Initialize(applicationId, handlers, true, ""); } } @Override - public void showFileChooser(String text, String content, Consumer cons, boolean open, String filter) { + public void showFileChooser(String text, String content, Consumer cons, boolean open, String filter){ new FileChooser(text, file -> file.extension().equalsIgnoreCase(filter), open, cons).show(); } @@ -70,7 +70,7 @@ public class DesktopPlatform extends Platform { } @Override - public void updateRPC() { + public void updateRPC(){ if(!useDiscord) return; @@ -89,7 +89,7 @@ public class DesktopPlatform extends Platform { }else{ if(ui.editor != null && ui.editor.isShown()){ presence.state = "In Editor"; - }else { + }else{ presence.state = "In Menu"; } } @@ -100,26 +100,27 @@ public class DesktopPlatform extends Platform { } @Override - public void onGameExit() { + public void onGameExit(){ if(useDiscord) DiscordRPC.INSTANCE.Discord_Shutdown(); } @Override - public boolean isDebug() { + public boolean isDebug(){ return args.length > 0 && args[0].equalsIgnoreCase("-debug_" + getUUID().hashCode()); } @Override - public ThreadProvider getThreadProvider() { + public ThreadProvider getThreadProvider(){ return new DefaultThreadImpl(); } @Override - public String getUUID() { - try { + public String getUUID(){ + try{ Enumeration e = NetworkInterface.getNetworkInterfaces(); NetworkInterface out; - for(out = e.nextElement(); out.getHardwareAddress() == null && e.hasMoreElements() && validAddress(out.getHardwareAddress()); out = e.nextElement()); + for(out = e.nextElement(); out.getHardwareAddress() == null && e.hasMoreElements() && validAddress(out.getHardwareAddress()); out = e.nextElement()) + ; byte[] bytes = out.getHardwareAddress(); byte[] result = new byte[8]; @@ -130,7 +131,7 @@ public class DesktopPlatform extends Platform { if(str.equals("AAAAAAAAAOA=")) throw new RuntimeException("Bad UUID."); return str; - }catch (Exception e){ + }catch(Exception e){ return super.getUUID(); } } diff --git a/server/src/io/anuke/mindustry/server/MindustryServer.java b/server/src/io/anuke/mindustry/server/MindustryServer.java index c82c4a5695..fdd846c7f3 100644 --- a/server/src/io/anuke/mindustry/server/MindustryServer.java +++ b/server/src/io/anuke/mindustry/server/MindustryServer.java @@ -11,7 +11,7 @@ import io.anuke.ucore.modules.ModuleCore; import static io.anuke.mindustry.Vars.*; -public class MindustryServer extends ModuleCore { +public class MindustryServer extends ModuleCore{ private String[] args; public MindustryServer(String[] args){ diff --git a/server/src/io/anuke/mindustry/server/ServerControl.java b/server/src/io/anuke/mindustry/server/ServerControl.java index 99c93b1b6d..5e310c46f4 100644 --- a/server/src/io/anuke/mindustry/server/ServerControl.java +++ b/server/src/io/anuke/mindustry/server/ServerControl.java @@ -33,21 +33,23 @@ import java.util.Scanner; import static io.anuke.mindustry.Vars.*; import static io.anuke.ucore.util.Log.*; -public class ServerControl extends Module { +public class ServerControl extends Module{ private final CommandHandler handler = new CommandHandler(""); private ShuffleMode mode; public ServerControl(String[] args){ Settings.defaultList( - "shufflemode", "normal", - "bans", "", - "admins", "" + "shufflemode", "normal", + "bans", "", + "admins", "" ); mode = ShuffleMode.valueOf(Settings.getString("shufflemode")); - Effects.setScreenShakeProvider((a, b) -> {}); - Effects.setEffectProvider((a, b, c, d, e, f) -> {}); + Effects.setScreenShakeProvider((a, b) -> { + }); + Effects.setEffectProvider((a, b, c, d, e, f) -> { + }); Sounds.setHeadless(true); String[] commands = {}; @@ -79,16 +81,16 @@ public class ServerControl extends Module { netServer.kick(connection.id, KickReason.gameover); } - if (mode != ShuffleMode.off) { - if(world.maps().all().size > 0) { + if(mode != ShuffleMode.off){ + if(world.maps().all().size > 0){ Array maps = mode == ShuffleMode.both ? world.maps().all() : mode == ShuffleMode.normal ? world.maps().defaultMaps() : world.maps().customMaps(); Map previous = world.getMap(); Map map = previous; - while (map == previous) map = maps.random(); + while(map == previous) map = maps.random(); - if(map != null) { + if(map != null){ info("Selected next map to be {0}.", map.name); state.set(State.playing); @@ -147,11 +149,11 @@ public class ServerControl extends Module { Map result = null; - if(arg.length > 0) { + if(arg.length > 0){ GameMode mode; try{ mode = GameMode.valueOf(arg[0]); - }catch (IllegalArgumentException e){ + }catch(IllegalArgumentException e){ err("No gamemode '{0}' found.", arg[0]); return; } @@ -159,14 +161,14 @@ public class ServerControl extends Module { info("Loading map..."); state.mode = mode; - if(arg.length > 1) { + if(arg.length > 1){ String search = arg[1]; - for (Map map : world.maps().all()) { - if (map.name.equalsIgnoreCase(search)) + for(Map map : world.maps().all()){ + if(map.name.equalsIgnoreCase(search)) result = map; } - if (result == null) { + if(result == null){ err("No map with name &y'{0}'&lr found.", search); return; } @@ -205,18 +207,18 @@ public class ServerControl extends Module { if(state.mode.disableWaveTimer){ info("&ly{0} enemies.", unitGroups[Team.red.ordinal()].size()); }else{ - info("&ly{0} seconds until next wave.", (int)(state.wavetime / 60)); + info("&ly{0} seconds until next wave.", (int) (state.wavetime / 60)); } - if(playerGroup.size() > 0) { + if(playerGroup.size() > 0){ info("&lyPlayers: {0}", playerGroup.size()); - for (Player p : playerGroup.all()) { + for(Player p : playerGroup.all()){ print(" &y" + p.name); } }else{ info("&lyNo players connected."); } - info("&lbFPS: {0}", (int)(60f/Timers.delta())); + info("&lbFPS: {0}", (int) (60f / Timers.delta())); } }); @@ -224,9 +226,9 @@ public class ServerControl extends Module { if(state.is(State.menu)){ info("&lyServer is closed."); }else{ - if(playerGroup.size() > 0) { + if(playerGroup.size() > 0){ info("&lyPlayers: {0}", playerGroup.size()); - for (Player p : playerGroup.all()) { + for(Player p : playerGroup.all()){ print(" &y{0} / Connection {1} / IP: {2}", p.name, p.con.id, p.con.address); } }else{ @@ -236,7 +238,7 @@ public class ServerControl extends Module { }); handler.register("say", "", "Send a message to all players.", arg -> { - if(!state.is(State.playing)) { + if(!state.is(State.playing)){ err("Not hosting. Host a game first."); return; } @@ -251,7 +253,7 @@ public class ServerControl extends Module { Difficulty diff = Difficulty.valueOf(arg[0]); state.difficulty = diff; info("Difficulty set to '{0}'.", arg[0]); - }catch (IllegalArgumentException e){ + }catch(IllegalArgumentException e){ err("No difficulty with name '{0}' found.", arg[0]); } }); @@ -286,13 +288,13 @@ public class ServerControl extends Module { err("Incorrect command usage."); } - if(arg.length >= 2) { - try { + if(arg.length >= 2){ + try{ int maxbreak = Integer.parseInt(arg[1]); int cooldown = (arg.length >= 3 ? Integer.parseInt(arg[2]) : Administration.defaultBreakCooldown); netServer.admins.setAntiGriefParams(maxbreak, cooldown); info("Anti-grief parameters set."); - } catch (NumberFormatException e) { + }catch(NumberFormatException e){ err("Invalid number format."); } } @@ -323,13 +325,13 @@ public class ServerControl extends Module { Settings.putString("shufflemode", arg[0]); Settings.save(); info("Shuffle mode set to '{0}'.", arg[0]); - }catch (Exception e){ + }catch(Exception e){ err("Unknown shuffle mode '{0}'.", arg[0]); } }); handler.register("kick", "", "Kick a person by name.", arg -> { - if(!state.is(State.playing)) { + if(!state.is(State.playing)){ err("Not hosting a game yet. Calm down."); return; } @@ -352,7 +354,7 @@ public class ServerControl extends Module { }); handler.register("ban", "", "Ban a person by name.", arg -> { - if(!state.is(State.playing)) { + if(!state.is(State.playing)){ err("Can't ban people by name with no players."); return; } @@ -397,7 +399,7 @@ public class ServerControl extends Module { Log.info("&lmBanned players [IP]:"); for(String string : ipbans){ PlayerInfo info = netServer.admins.findByIP(string); - if(info != null) { + if(info != null){ Log.info(" &lm '{0}' / Last known name: '{1}' / ID: '{2}'", string, info.lastName, info.id); }else{ Log.info(" &lm '{0}' (No known name or info)", string); @@ -407,7 +409,7 @@ public class ServerControl extends Module { }); handler.register("banip", "", "Ban a person by IP.", arg -> { - if(netServer.admins.banPlayerIP(arg[0])) { + if(netServer.admins.banPlayerIP(arg[0])){ info("Banned player by IP: {0}.", arg[0]); for(Player player : playerGroup.all()){ @@ -423,7 +425,7 @@ public class ServerControl extends Module { }); handler.register("banid", "", "Ban a person by their unique ID.", arg -> { - if(netServer.admins.banPlayerID(arg[0])) { + if(netServer.admins.banPlayerID(arg[0])){ info("Banned player by ID: {0}.", arg[0]); for(Player player : playerGroup.all()){ @@ -438,7 +440,7 @@ public class ServerControl extends Module { }); handler.register("unbanip", "", "Completely unban a person by IP.", arg -> { - if(netServer.admins.unbanPlayerIP(arg[0])) { + if(netServer.admins.unbanPlayerIP(arg[0])){ info("Unbanned player by IP: {0}.", arg[0]); }else{ err("That IP is not banned!"); @@ -446,7 +448,7 @@ public class ServerControl extends Module { }); handler.register("unbanid", "", "Completely unban a person by ID.", arg -> { - if(netServer.admins.unbanPlayerID(arg[0])) { + if(netServer.admins.unbanPlayerID(arg[0])){ info("&lmUnbanned player by ID: {0}.", arg[0]); }else{ err("That IP is not banned!"); @@ -454,7 +456,7 @@ public class ServerControl extends Module { }); handler.register("admin", "", "Make a user admin", arg -> { - if(!state.is(State.playing)) { + if(!state.is(State.playing)){ err("Open the server first."); return; } @@ -478,7 +480,7 @@ public class ServerControl extends Module { }); handler.register("unadmin", "", "Removes admin status from a player", arg -> { - if(!state.is(State.playing)) { + if(!state.is(State.playing)){ err("Open the server first."); return; } @@ -515,7 +517,7 @@ public class ServerControl extends Module { }); handler.register("runwave", "Trigger the next wave.", arg -> { - if(!state.is(State.playing)) { + if(!state.is(State.playing)){ err("Not hosting. Host a game first."); }else{ logic.runWave(); @@ -562,19 +564,19 @@ public class ServerControl extends Module { }); handler.register("griefers", "[min-break:place-ratio] [min-breakage]", "Find possible griefers currently online.", arg -> { - if(!state.is(State.playing)) { + if(!state.is(State.playing)){ err("Open the server first."); return; } - try { + try{ float ratio = arg.length > 0 ? Float.parseFloat(arg[0]) : 0.5f; int minbreak = arg.length > 1 ? Integer.parseInt(arg[1]) : 100; boolean found = false; - for (Player player : playerGroup.all()) { + for(Player player : playerGroup.all()){ TraceInfo info = netServer.admins.getTraceByID(player.uuid); if(info.totalBlocksBroken >= minbreak && info.totalBlocksBroken / Math.max(info.totalBlocksPlaced, 1f) >= ratio){ info("&ly - Player '{0}' / UUID &lm{1}&ly found: &lc{2}&ly broken and &lc{3}&ly placed.", @@ -583,19 +585,19 @@ public class ServerControl extends Module { } } - if (!found) { + if(!found){ info("No griefers matching the criteria have been found."); } - }catch (NumberFormatException e){ + }catch(NumberFormatException e){ err("Invalid number format."); } }); handler.register("gameover", "Force a game over.", arg -> { if(state.is(State.menu)){ - info("Not playing a map."); - return; + info("Not playing a map."); + return; } Events.fire(GameOverEvent.class); @@ -616,10 +618,10 @@ public class ServerControl extends Module { if(tile.entity != null){ Array arr = tile.block().getDebugInfo(tile); StringBuilder result = new StringBuilder(); - for(int i = 0; i < arr.size/2; i ++){ - result.append(arr.get(i*2)); + for(int i = 0; i < arr.size / 2; i++){ + result.append(arr.get(i * 2)); result.append(": "); - result.append(arr.get(i*2 + 1)); + result.append(arr.get(i * 2 + 1)); result.append("\n"); } Log.info("&ly{0}", result); @@ -629,7 +631,7 @@ public class ServerControl extends Module { }else{ Log.info("No tile at that location."); } - }catch (NumberFormatException e){ + }catch(NumberFormatException e){ Log.err("Invalid coordinates passed."); } }); @@ -639,7 +641,7 @@ public class ServerControl extends Module { Array infos = netServer.admins.findByName(arg[0], checkAll); - if(infos.size == 1) { + if(infos.size == 1){ PlayerInfo info = infos.peek(); Log.info("&lcTrace info for player '{0}' / UUID {1}:", info.lastName, info.id); Log.info(" &lyall names used: {0}", info.names); @@ -665,7 +667,7 @@ public class ServerControl extends Module { Array infos = netServer.admins.findByIPs(arg[0]); - if(infos.size == 1) { + if(infos.size == 1){ PlayerInfo info = infos.peek(); Log.info("&lcTrace info for player '{0}' / UUID {1}:", info.lastName, info.id); Log.info(" &lyall names used: {0}", info.names); @@ -707,7 +709,7 @@ public class ServerControl extends Module { }); handler.register("trace", "", "Trace a player's actions", arg -> { - if(!state.is(State.playing)) { + if(!state.is(State.playing)){ err("Open the server first."); return; } @@ -724,7 +726,7 @@ public class ServerControl extends Module { if(target != null){ TraceInfo info = netServer.admins.getTraceByID(target.uuid); Log.info("&lcTrace info for player '{0}':", target.name); - Log.info(" &lyEntity ID: {0}", info. playerid); + Log.info(" &lyEntity ID: {0}", info.playerid); Log.info(" &lyIP: {0}", info.ip); Log.info(" &lyUUID: {0}", info.uuid); Log.info(" &lycustom client: {0}", info.modclient); @@ -741,27 +743,27 @@ public class ServerControl extends Module { } }); - handler.register("rollback", "", "Rollback the block edits in the world", arg -> { - if(!state.is(State.playing)) { - err("Open the server first."); - return; - } + handler.register("rollback", "", "Rollback the block edits in the world", arg -> { + if(!state.is(State.playing)){ + err("Open the server first."); + return; + } - if(!Strings.canParsePostiveInt(arg[0])) { - err("Please input a valid, positive, number of times to rollback"); - return; - } + if(!Strings.canParsePostiveInt(arg[0])){ + err("Please input a valid, positive, number of times to rollback"); + return; + } - int rollbackTimes = Integer.valueOf(arg[0]); - IntMap> editLogs = netServer.admins.getEditLogs(); - if(editLogs.size == 0){ - err("Nothing to rollback!"); - return; - } + int rollbackTimes = Integer.valueOf(arg[0]); + IntMap> editLogs = netServer.admins.getEditLogs(); + if(editLogs.size == 0){ + err("Nothing to rollback!"); + return; + } - //netServer.admins.rollbackWorld(rollbackTimes); - info("Rollback done!"); - }); + //netServer.admins.rollbackWorld(rollbackTimes); + info("Rollback done!"); + }); } private void readCommands(){ @@ -772,7 +774,7 @@ public class ServerControl extends Module { Gdx.app.postRunnable(() -> { Response response = handler.handleMessage(line); - if (response.type == ResponseType.unknownCommand) { + if(response.type == ResponseType.unknownCommand){ int minDst = 0; Command closest = null; @@ -787,12 +789,12 @@ public class ServerControl extends Module { if(closest != null){ err("Command not found. Did you mean \"" + closest.text + "\"?"); - }else { + }else{ err("Invalid command. Type 'help' for help."); } - }else if (response.type == ResponseType.fewArguments) { + }else if(response.type == ResponseType.fewArguments){ err("Too few command arguments. Usage: " + response.command.text + " " + response.command.paramText); - }else if (response.type == ResponseType.manyArguments) { + }else if(response.type == ResponseType.manyArguments){ err("Too many command arguments. Usage: " + response.command.text + " " + response.command.paramText); } }); @@ -800,9 +802,9 @@ public class ServerControl extends Module { } private void host(){ - try { + try{ Net.host(port); - }catch (IOException e){ + }catch(IOException e){ Log.err(e); state.set(State.menu); } diff --git a/server/src/io/anuke/mindustry/server/ServerLauncher.java b/server/src/io/anuke/mindustry/server/ServerLauncher.java index 4a8c07ba20..dbc25ea35b 100644 --- a/server/src/io/anuke/mindustry/server/ServerLauncher.java +++ b/server/src/io/anuke/mindustry/server/ServerLauncher.java @@ -20,6 +20,37 @@ import java.io.File; public class ServerLauncher extends HeadlessApplication{ ObjectMap prefmap; + public ServerLauncher(ApplicationListener listener, HeadlessApplicationConfiguration config){ + super(listener, config); + + //don't do anything at all for GDX logging: don't want controller info and such + Gdx.app.setApplicationLogger(new ApplicationLogger(){ + @Override + public void log(String tag, String message){ + } + + @Override + public void log(String tag, String message, Throwable exception){ + } + + @Override + public void error(String tag, String message){ + } + + @Override + public void error(String tag, String message, Throwable exception){ + } + + @Override + public void debug(String tag, String message){ + } + + @Override + public void debug(String tag, String message, Throwable exception){ + } + }); + } + public static void main(String[] args){ Net.setClientProvider(new KryoClient()); @@ -33,7 +64,7 @@ public class ServerLauncher extends HeadlessApplication{ //find and handle uncaught exceptions in libGDX thread for(Thread thread : Thread.getAllStackTraces().keySet()){ if(thread.getName().equals("HeadlessApplication")){ - thread.setUncaughtExceptionHandler((t, throwable) ->{ + thread.setUncaughtExceptionHandler((t, throwable) -> { throwable.printStackTrace(); System.exit(-1); }); @@ -42,22 +73,8 @@ public class ServerLauncher extends HeadlessApplication{ } } - public ServerLauncher(ApplicationListener listener, HeadlessApplicationConfiguration config) { - super(listener, config); - - //don't do anything at all for GDX logging: don't want controller info and such - Gdx.app.setApplicationLogger(new ApplicationLogger() { - @Override public void log(String tag, String message) { } - @Override public void log(String tag, String message, Throwable exception) { } - @Override public void error(String tag, String message) { } - @Override public void error(String tag, String message, Throwable exception) { } - @Override public void debug(String tag, String message) { } - @Override public void debug(String tag, String message, Throwable exception) { } - }); - } - @Override - public Preferences getPreferences(String name) { + public Preferences getPreferences(String name){ String prefsDirectory = OS.getAppDataDirectoryString("Mindustry"); if(prefmap == null){ From 2fcb3c44201d574cf38a43c9daeb73e261eca58f Mon Sep 17 00:00:00 2001 From: Anuken Date: Thu, 12 Jul 2018 21:13:08 -0400 Subject: [PATCH 38/47] Bugfixes --- .../mindustry/content/blocks/DistributionBlocks.java | 2 ++ .../mindustry/world/blocks/distribution/LiquidBridge.java | 3 +-- .../mindustry/world/blocks/distribution/OverflowGate.java | 2 +- .../mindustry/world/blocks/distribution/Splitter.java | 8 ++++++++ 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/core/src/io/anuke/mindustry/content/blocks/DistributionBlocks.java b/core/src/io/anuke/mindustry/content/blocks/DistributionBlocks.java index f045133968..526563033e 100644 --- a/core/src/io/anuke/mindustry/content/blocks/DistributionBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/DistributionBlocks.java @@ -29,10 +29,12 @@ public class DistributionBlocks extends BlockList implements ContentList{ bridgeConveyor = new BufferedItemBridge("bridge-conveyor"){{ range = 3; hasPower = false; + consumes.power(0.05f); }}; phaseConveyor = new ItemBridge("phase-conveyor"){{ range = 7; + consumes.power(0.05f); }}; sorter = new Sorter("sorter"); diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidBridge.java b/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidBridge.java index 383a86cf03..55444ca2b0 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidBridge.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidBridge.java @@ -24,7 +24,7 @@ public class LiquidBridge extends ItemBridge{ entity.time2 += (entity.cycleSpeed - 1f) * Timers.delta(); Tile other = world.tile(entity.link); - if(!linkValid(tile, other)){ + if(!linkValid(tile, other) ){ tryDumpLiquid(tile, entity.liquids.current()); }else{ if(entity.cons.valid()){ @@ -33,7 +33,6 @@ public class LiquidBridge extends ItemBridge{ entity.uptime = Mathf.lerpDelta(entity.uptime, 0f, 0.02f); } - if(entity.uptime >= 0.5f){ if(tryMoveLiquid(tile, other, false, entity.liquids.current()) > 0.1f){ diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/OverflowGate.java b/core/src/io/anuke/mindustry/world/blocks/distribution/OverflowGate.java index c7ebb6e75a..db4ecebd58 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/OverflowGate.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/OverflowGate.java @@ -17,7 +17,7 @@ public class OverflowGate extends Splitter{ if(dir == -1) return null; Tile to = dest.getNearby(dir); - if(!(to.block().acceptItem(item, to, dest) || + if((!to.block().acceptItem(item, to, dest) || (to.block().instantTransfer && source.block().instantTransfer))){ Tile a = dest.getNearby(Mathf.mod(dir - 1, 4)); Tile b = dest.getNearby(Mathf.mod(dir + 1, 4)); diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/Splitter.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Splitter.java index 1edf61002d..f0d4d84022 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Splitter.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Splitter.java @@ -2,6 +2,7 @@ package io.anuke.mindustry.world.blocks.distribution; import com.badlogic.gdx.utils.Array; import io.anuke.mindustry.type.Item; +import io.anuke.mindustry.world.BarType; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Edges; import io.anuke.mindustry.world.Tile; @@ -19,6 +20,13 @@ public class Splitter extends Block{ group = BlockGroup.transportation; } + @Override + public void setBars(){ + super.setBars(); + + bars.remove(BarType.inventory); + } + @Override public boolean acceptItem(Item item, Tile tile, Tile source){ Tile to = getTileTarget(item, tile, source, false); From 3f63a6ed70403d1d378f1ab5a7495be7e5bbf21f Mon Sep 17 00:00:00 2001 From: Gureumi Date: Fri, 13 Jul 2018 22:24:00 +0900 Subject: [PATCH 39/47] Update bundle_ko.properties --- core/assets/bundles/bundle_ko.properties | 199 ++++++++++++----------- 1 file changed, 103 insertions(+), 96 deletions(-) diff --git a/core/assets/bundles/bundle_ko.properties b/core/assets/bundles/bundle_ko.properties index 3b3f145bd3..aab0bed693 100644 --- a/core/assets/bundles/bundle_ko.properties +++ b/core/assets/bundles/bundle_ko.properties @@ -11,25 +11,25 @@ text.link.wiki.description=공식 Mindustry 위키 text.linkfail=링크를 여는데 실패했습니다!URL이 기기의 클립보드에 복사되었습니다. text.editor.web=HTML5 버전은 에디터 기능을 지원하지 않습니다!게임을 다운로드 한 뒤에 사용 해 주세요. text.web.unsupported=HTML5 버전은 이 기능을 지원하지 않습니다!게임을 다운로드 한 뒤에 사용 해 주세요. -text.multiplayer.web=이 버전은 멀티플레이를 지원하지 않습니다!멀티플레이를 웹 브라우저에서 즐기고 싶다면, itch.io 페이지에서 "multiplayer web version" 링크로 들어가면 됩니다. +text.multiplayer.web=이 버전은 멀티플레이를 지원하지 않습니다!멀티플레이를 웹 브라우저에서 즐기고 싶다면, itch.io 페이지에서 \"multiplayer web version\" 링크로 들어가면 됩니다. text.host.web=HTML5 버전은 게임 호스팅을 지원하지 않습니다!게임을 다운로드 한 뒤에 사용 해 주세요. -text.gameover=코어가 파괴되었습니다. +text.gameover=코어가 터졌습니다. 게임 오버! text.highscore=[YELLOW]최고점수 달성! -text.lasted=마지막으로 달성한 단계 +text.lasted=마지막으로 달성한 웨이브 text.level.highscore=최고 점수 : [accent]{0} text.level.delete.title=삭제 확인 -text.map.delete=정말로 "[orange]{0}[]" 맵을 삭제하시겠습니까? +text.map.delete=정말로 \"[orange]{0}[]\" 맵을 삭제하시겠습니까? text.level.select=맵 선택 text.level.mode=게임모드: text.construction.title=블록 배치 안내서 -text.construction=당신은 [accent]블록 배치 모드[]를 선택하셨습니다.\n\n블록을 설치하고 싶으면, 자신의 건설 가능 범위 내에서 간단히 탭 하면 됩니다.\n일부 블록을 선택한 후에 확인 버튼을 누르면 배가 배치 작업을 진행할 것입니다.\n- [accent]블록을 삭제[]하고 싶다면 배치하고 싶은 영역을 탭 하세요. \n- [accent]블록을 넓게 배치[]하고 싶다면 배치하고 싶은 시작 영역을 길게 누르며 드래그 하면 됩니다.- [accent]블록을 한줄로 배치[]하고 싶다면 배치하고 싶은 시작 영역을 한번 탭 하고 길게 누르면서 드래그 하면 됩니다. \n- [accent]블록 배치 모드를 취소[]하고 싶다면 화면 하단 왼쪽에 있는 X 버튼을 누르면 됩니다. +text.construction=[accent]블록 배치 모드[]를 선택하셨습니다.\n\n블록을 설치하고 싶으면, 자신의 건설 가능 범위 내에서 간단히 탭 하면 됩니다.\n일부 블록을 선택한 후에 확인 버튼을 누르면 배가 배치 작업을 진행할 것입니다.\n- [accent]블록을 삭제[]하고 싶다면 배치하고 싶은 영역을 탭 하세요. \n- [accent]블록을 넓게 배치[]하고 싶다면 배치하고 싶은 시작 영역을 길게 누르며 드래그 하면 됩니다.- [accent]블록을 한줄로 배치[]하고 싶다면 배치하고 싶은 시작 영역을 한번 탭 하고 길게 누르면서 드래그 하면 됩니다. \n- [accent]블록 배치 모드를 취소[]하고 싶다면 화면 하단 왼쪽에 있는 X 버튼을 누르면 됩니다. text.deconstruction.title=블록 삭제 안내서 -text.deconstruction=당신은 [accent]블록 삭제 모드[]를 선택하셨습니다\n블록을 삭제하고 싶다면, 자신의 건설 가능 범위 내에서 간단히 탭 하면 됩니다.\n일부 블록을 선택한 후에 확인 버튼을 누르면 배가 파괴 작업을 진행할 것입니다.\n- [accent]블록을 삭제[]하고 싶다면 배치하고 싶은 영역을 탭 하세요- [accent]블록을 넓은 범위로 삭제[]하고 싶다면 배치하고 싶은 시작 영역을 길게 누르며 드래그 하면 됩니다.- [accent]블록 삭제 모드를 취소[]하고 싶다면 화면 하단 왼쪽에 있는 X 버튼을 누르면 됩니다. +text.deconstruction=[accent]블록 삭제 모드[]를 선택하셨습니다\n블록을 삭제하고 싶다면, 자신의 건설 가능 범위 내에서 간단히 탭 하면 됩니다.\n일부 블록을 선택한 후에 확인 버튼을 누르면 배가 파괴 작업을 진행할 것입니다.\n- [accent]블록을 삭제[]하고 싶다면 배치하고 싶은 영역을 탭 하세요- [accent]블록을 넓은 범위로 삭제[]하고 싶다면 배치하고 싶은 시작 영역을 길게 누르며 드래그 하면 됩니다.- [accent]블록 삭제 모드를 취소[]하고 싶다면 화면 하단 왼쪽에 있는 X 버튼을 누르면 됩니다. text.showagain=다음 세션에서 이 메세지를 표시하지 않습니다 text.unlocks=잠금 해제 text.savegame=게임 저장 text.loadgame=게임 불러오기 -text.joingame=게임 참가\n +text.joingame=게임 참가 text.addplayers=플레이어 추가/제거 text.newgame=새 게임 text.quit=나가기 @@ -53,7 +53,7 @@ text.server.kicked.recentKick=당신은 방금 추방처리 되었습니다.잠 text.server.kicked.nameInUse=이 닉네임이 이미 서버에서 사용중입니다. text.server.kicked.nameEmpty=닉네임에는 반드시 영어 또는 숫자가 있어야 합니다. text.server.kicked.idInUse=당신은 이미 서버에 접속중입니다! 다중 계정은 허용되지 않습니다. -text.server.kicked.customClient=이 서버는 수정된 클라이언트를 지원하지 않습니다. 공식 버전을 사용하세요. +text.server.kicked.customClient=이 서버는 커스텀 빌드를 지원하지 않습니다. 공식 버전을 사용하세요. text.server.connected={0} 님이 접속했습니다. text.server.disconnected={0} 님이 나갔습니다. text.nohost=커스텀 맵을 호스트 할 수 없습니다! @@ -70,7 +70,7 @@ text.host.invalid=[scarlet]서버에 연결할 수 없습니다! text.server.friendlyfire=팀킬 text.trace=플레이어 추적 text.trace.playername=플레이어 이름: [accent]{0} -text.trace.ip=IP : [accent] +text.trace.ip=IP : [accent]{0} text.trace.id=고유 ID: [accent]{0} text.trace.android=Android 클라이언트 : [accent]{0} text.trace.modclient=수정된 클라이언트 : [accent]{0} @@ -90,8 +90,8 @@ text.server.hostname=호스트: {0} text.server.edit=서버 수정 text.server.outdated=[crimson]서버 버전이 낮습니다![] text.server.outdated.client=[Crimson]클라이언트 버전이 낮습니다![] -text.server.version=[lightgray] 버전 : {0} -text.server.custombuild=[노란색]수정된 빌드 +text.server.version=[lightgray]버전 : {0} +text.server.custombuild=[yellow]커스텀 서버 text.confirmban=이 플레이어를 차단하시겠습니까? text.confirmunban=이 플레이어를 차단하시겠습니까? text.confirmadmin=이 플레이어를 관리자로 설정 하시겠습니까? @@ -116,7 +116,7 @@ text.savefail=게임을 저장하지 못했습니다!\n text.save.delete.confirm=이 저장파일을 삭제 하시겠습니까?\n text.save.delete=삭제\n text.save.export=저장파일 내보내기\n -text.save.import.invalid=[orange]저장파일이 유효한 파일이 아닙니다!\n\n다른 디바이스에 있는 커스텀 맵을 가져오는건 작동하지 않습니다.\n +text.save.import.invalid=[orange]저장 상태가 정상이 아닙니다! text.save.import.fail=[crimson]저장파일을 불러오지 못함: [orange]{0}\n text.save.export.fail=[crimson]저장파일을 내보내지 못함: [orange]{0} text.save.import=저장파일 불러오기\n @@ -125,14 +125,14 @@ text.save.rename=이름 변경\n text.save.rename.text=새 이름 :\n text.selectslot=저장슬롯을 선택하십시오.\n text.slot=[accent]{0}번째 슬롯\n -text.save.corrupted=[orange]저장파일이 손상되었습니다!\n +text.save.corrupted=[orange]세이브 파일이 손상되었거나 잘못된 파일입니다!만약 게임을 업데이트 했다면 이것은 아마 저장 형식 변경일 것이고, 이것은 버그가 [scarlet]아닙니다[].\n\n text.empty=<비어있음>\n text.on=켜기\n text.off=끄기\n text.save.autosave=자동저장: {0} -text.save.map=맵 : -text.save.wave={0} 단계 -text.save.difficulty=난이도 : {0} +text.save.map=맵 : {0} +text.save.wave={0} 웨이브 +text.save.difficulty=난이도: {0} text.save.date=마지막 저장 날짜 : {0} text.confirm=확인 text.delete=삭제\n @@ -152,11 +152,11 @@ text.changelog.current=[orange][[현재 버전] text.changelog.latest=[orange][[최신 버전] text.loading=[accent]불러오는중... text.saving=[accent]저장중...\n -text.wave=[orange]{0} 단계 -text.wave.waiting=다음 단계 시작까지 {0}초 +text.wave=[orange]{0} 웨이브 +text.wave.waiting=다음 웨이브 시작까지 {0}초 text.waiting=기다리는중... text.enemies=남은 몹 : {0} -text.enemies.single=몹이 1마리 남아있음 +text.enemies.single=몹이 {0}마리 남아있음 text.loadimage=사진 불러오기 text.saveimage=사진 저장 text.unknown=알 수 없음 @@ -179,7 +179,7 @@ text.editor.badsize=[orange]사진 크기가 잘못되었습니다![]유효한 text.editor.errorimageload=[orange]{0}[] 파일을 불러오는데 오류가 발생했습니다. text.editor.errorimagesave=[orange]{0}[] 파일 저장중 오류가 발생했습니다. text.editor.generate=생성 -text.editor.resize=크기 조정 +text.editor.resize=맵 크기조정 text.editor.loadmap=맵 불러오기 text.editor.savemap=맵 저장 text.editor.saved=저장됨! @@ -215,29 +215,32 @@ text.save=저장 text.fps={0} FPS text.tps={0} TPS text.ping=핑 : {0}ms -text.language.restart=언어 설정을 적용하려면 게임을 다시 시작하십시오. +text.language.restart=언어를 변경하려면 게임을 다시 시작 해 주세요. text.settings.language=언어 text.settings=설정 text.tutorial=게임 방법 text.editor=편집기 text.mapeditor=맵 편집기 text.donate=기부 -text.settings.reset=기본값으로 재설정 -text.settings.rebind=rebind +text.settings.reset=설정 초기화 +text.settings.rebind=키 재설정 text.settings.controls=컨트롤 text.settings.game=게임 text.settings.sound=소리 text.settings.graphics=화면 -text.upgrades=업그레이드 +text.upgrades=강화 text.purchased=[LIME]생성됨! text.weapons=무기 text.paused=일시 정지 +text.yes=예 +text.no=아니오 text.info.title=[accent]정보 text.error.title=[crimson]오류가 발생했습니다. text.error.crashtitle=오류가 발생했습니다. text.blocks.blockinfo=블록 정보 text.blocks.powercapacity=최대 전력 용량 text.blocks.powershot=1발당 파워 소모량 +text.blocks.targetsair=표적 공기 text.blocks.itemspeed=유닛 이동 속도 text.blocks.shootrange=공격 범위 text.blocks.size=블록 크기 @@ -247,7 +250,7 @@ text.blocks.powerrange=전력 범위 text.blocks.poweruse=전력 사용 text.blocks.inputitemcapacity=입력 아이템 용량 text.blocks.outputitemcapacity=입력 아이템 용량 -text.blocks.itemcapacity=아이템 용량 +text.blocks.itemcapacity=품목 용량 text.blocks.maxpowergeneration=최대 발전량 text.blocks.powertransferspeed=전력 전송량 text.blocks.craftspeed=생산 속도 @@ -260,6 +263,10 @@ text.blocks.drilltier=드릴 text.blocks.drillspeed=기본 드릴 속도 text.blocks.liquidoutput=액체 출력 text.blocks.liquiduse=액체 사용 +text.blocks.coolant=냉각제 +text.blocks.coolantuse=냉각수 사용 +text.blocks.inputliquidfuel=연료 액 +text.blocks.liquidfueluse=액체 연료 사용 text.blocks.explosive=이게 터지면 펑 터지면서 주변 블록에게 피해를 입힙니다! text.blocks.health=체력 text.blocks.inaccuracy=빗맞을 확률 @@ -269,6 +276,8 @@ text.blocks.inputfuel=연료 text.blocks.fuelburntime=연료 연소 시간 text.blocks.inputcapacity=입력 용량 text.blocks.outputcapacity=출력 용량 +text.blocks.requird=요구사항: + text.unit.blocks=블록들 text.unit.powersecond=초당 전력 단위 text.unit.liquidsecond=액체 단위 / 초 @@ -280,6 +289,7 @@ text.unit.degrees=도 text.unit.seconds=초 text.unit.none= text.unit.items=아이템 + text.category.general=일반 text.category.power=전력 text.category.liquids=액체 @@ -290,24 +300,24 @@ setting.difficulty.easy=쉬움 setting.difficulty.normal=보통 setting.difficulty.hard=어려움 setting.difficulty.insane=미침 -setting.difficulty.purge=[#FE2E2E]대한[#2E2EFE]민국 +setting.difficulty.purge=[#1E90FF]K[#ADFF2F]O[#FFB6C1]R[#B0C4DE]E[#FF4500]A setting.difficulty.name=난이도: setting.screenshake.name=화면 흔들기 setting.indicators.name=적 위치 표시 화살표 setting.effects.name=화면 효과 setting.sensitivity.name=컨트롤러 감도 setting.saveinterval.name=자동저장 간격 -setting.seconds=초 +setting.seconds={0}초 setting.fullscreen.name=전체 화면 -setting.multithread.name=멀티 스레딩 +setting.multithread.name=멀티 스레드 setting.fps.name=FPS 표시 -setting.vsync.name=VSync +setting.vsync.name=VSync 활성화 setting.lasers.name=파워 레이져 표시 setting.healthbars.name=몹 체력바 표시 setting.minimap.name=미니맵 보기 setting.musicvol.name=음악 크기 setting.mutemusic.name=음소거 -setting.sfxvol.name=SFX 볼륨 +setting.sfxvol.name=효과음 크기 setting.mutesound.name=소리 끄기 map.maze.name=미로 map.fortress.name=요새 @@ -323,8 +333,8 @@ map.tundra.name=툰트라 map.spiral.name=나선 map.tutorial.name=게임 방법 text.keybind.title=키 바인딩 -keybind.move_x.name=move_x -keybind.move_y.name=move_y +keybind.move_x.name=오른쪽/왼쪽 이동 +keybind.move_y.name=위쪽/아래쪽 이동 keybind.select.name=선택 keybind.break.name=파괴 keybind.shoot.name=사격 @@ -337,24 +347,26 @@ keybind.dash.name=달리기 keybind.chat.name=채팅 keybind.player_list.name=플레이어 목록 keybind.console.name=콘솔 -keybind.rotate_alt.name=회전_alt +keybind.rotate_alt.name=rotate_alt keybind.rotate.name=회전 mode.text.help.title=도움말 -mode.waves.name=단계 -mode.waves.description=이것은 일반 모드입니다. 제한된 자원과 자동으로 다음 단계가 시작됩니다. +mode.waves.name=웨이브 +mode.waves.description=이것은 일반 모드입니다. 제한된 자원과 자동으로 다음 웨이브가 시작됩니다. mode.sandbox.name=샌드박스 -mode.sandbox.description=무한한 자원과 다음 단계 시작을 위한 타이머가 없습니다. +mode.sandbox.description=무한한 자원과 다음 웨이브 시작을 위한 타이머가 없습니다. mode.freebuild.name=자유 건축 -mode.freebuild.description=제한된 자원과 다음 단계 시작을 위한 타이머가 없습니다. +mode.freebuild.description=제한된 자원과 다음 웨이브 시작을 위한 타이머가 없습니다. + content.item.name=아이템 content.liquid.name=액체 content.unit-type.name=종류 -content.recipe.name=블록 +content.recipe.name=블록들 + item.stone.name=돌 item.stone.description=흔히 찾을 수 있는 자원. 바닥에서 돌을 캐거나 용암을 사용하여 얻을 수 있습니다. item.tungsten.name=텅스텐 item.tungsten.description=일반적이지만 매우 유용한 건축 재료. 드릴 및 생산 건물, 제련소와 같은 내열성 블록에 사용됩니다. -item.lead.name=납 +item.lead.name=리드 item.lead.description=기본적인 시작 자원. 전자 및 액체 수송 블록에서 광범위하게 사용됩니다. item.coal.name=석탄 item.coal.description=일반적이고 쉽게 이용할 수 있는 연료. @@ -364,107 +376,112 @@ item.titanium.name=티타늄 item.titanium.description=물 운반이나 드릴, 비행기등에서 재료로 사용되는 자원입니다. item.thorium.name=토륨 item.thorium.description=건물 탄약 또는 핵연료로 사용되는 방사성 금속. -item.silicon.name=규소 +item.silicon.name=실리콘 item.silcion.description=매우 유용한 반도체로, 태양 전지 패널과 복잡한 전자 제품에 응용할 수 있습니다. -item.plastanium.name=Plastanium +item.plastanium.name=플라스타늄 item.plastanium.description=고급 항공기 및 분열 탄약에 사용되는 가벼운 연성 재료. -item.phase-matter.name=Phase Matter -item.surge-alloy.name=Surge Alloy -item.biomatter.name=Biomatter -item.biomatter.description=유기농 덤불; 석유로 전환하거나 기본 연료로 사용됩니다. +item.phase-matter.name=메타 +item.surge-alloy.name=설합금 +item.biomatter.name=바이오메터 +item.biomatter.description=이것은 유기농 덤불입니다! 석유로 전환하거나 기본 연료로 사용됩니다. item.sand.name=모래 item.sand.description=합금 및 플럭스 모두에서 제련시 광범위하게 사용되는 일반적인 재료. -item.blast-compound.name=Blast Compound -item.blast-compound.description=폭탄 및 폭발물에 사용되는 휘발성 화합물. 그것이 연료로 태울 수 있지만, 이것은 권고하지 않습니다. -item.pyratite.name=Pyratite -item.pyratite.description=방화 용 무기에 사용되는 극히 가연성 물질. +item.blast-compound.name=폭발 화합물 +item.blast-compound.description=폭탄 및 폭발물에 사용되는 휘발성 화합물.연료로 불을 낼 수 있지만, 별로 추천하지는 않습니다. +item.pyratite.name=피러레이트 +item.pyratite.description=화염 무기에 사용되는 엄청난 가연성 물질. + liquid.water.name=물 liquid.lava.name=용암 liquid.oil.name=석유 -liquid.cryofluid.name=Cryofluid +liquid.cryofluid.name=크라이오플루드 + text.item.explosiveness=[LIGHT_GRAY]폭발력 : {0} text.item.flammability=[LIGHT_GRAY]인화성 : {0} text.item.radioactivity=[LIGHT_GRAY]방사능 : {0} -text.item.fluxiness=[LIGHT_GRAY]Flux Power : {0} +text.item.fluxiness=[LIGHT_GRAY]플렉스 파워 : {0} text.item.hardness=[LIGHT_GRAY]강도 : {0} -text.liquid.heatcapacity=[LIGHT_GRAY]열용량 : {0} +text.liquid.heatcapacity=[LIGHT_GRAY]발열 용량 : {0} text.liquid.viscosity=[LIGHT_GRAY]점도 : {0} text.liquid.temperature=[LIGHT_GRAY]온도 : {0} -block.tungsten-wall.name=텅스텐 장벽 +block.tungsten-wall.name=텅스텐 벽 block.tungsten-wall-large.name=큰 텅스텐 벽 block.carbide-wall.name=합금벽 block.carbide-wall-large.name=대형 합금벽 -block.thorium-wall.name=토륨 장벽 +block.thorium-wall.name=토룸 벽 block.thorium-wall-large.name=대형 토륨 벽 block.door.name=문 block.door-large.name=큰 문 block.duo.name=샷건 -block.scorch.name=물총 -block.hail.name=헤이스트 -block.lancer.name=팬선 +block.scorch.name=스코치 +block.hail.name=헤일 +block.lancer.name=랜서 block.conveyor.name=컨베이어 block.titanium-conveyor.name=티타늄 컨베이어 block.junction.name=교차기 -block.splitter.name=쪼개는 도구 -block.splitter.description=항목을받은 직후 두 개의 반대 방향으로 항목을 출력합니다. +block.splitter.name=스플리터 +block.splitter.description=아이템을 넣는 즉시 컨베이어를 반대 방향으로 바꾼 후 내보냅니다. block.router.name=분배기 block.router.description=아이템을 넣으면 다른 방향으로 아이템을 번갈아서 내보냅니다. +block.distributor.name=디스토이어 +block.distributor.description=아이템을 8방향으로 나눌 수있는 스플리터. block.sorter.name=필터 block.sorter.description=아이템을 받아서 설정된 아이템일 경우 바로 앞으로 통과하며, 그렇지 않을 경우 옆으로 통과합니다. +block.overflow-gate.name=오버플로 게이트 +block.overflow-gate.description=정면 경로가 차단된 경우 왼쪽과 오른쪽으로만 출력하는 복합 스플리터와 분배기 입니다. block.bridgeconveyor.name=터널 block.bridgeconveyor.description=최대 2블록을 건너 뛰고 자원을 운반하게 해 주는 블럭. block.smelter.name=제련소 block.arc-smelter.name=아크 제련소 block.silicon-smelter.name=실리콘 제련소 -block.phase-weaver.name=위상 위버 +block.phase-weaver.name=펄스 위버 block.pulverizer.name=분쇄기 -block.cryofluidmixer.name=냉동고 혼합기 +block.cryofluidmixer.name=크라이오플루드 혼합기 block.melter.name=멜터 block.incinerator.name=소각로 -block.biomattercompressor.name=바이오 매터 압축기 -block.separator.name=분리 기호 +block.biomattercompressor.name=바이오매터 압축기 +block.separator.name=셉터 block.centrifuge.name=원심 분리기 -block.power-node.name=전원 노드 -block.power-node-large.name=대형 전원 노드 +block.power-node.name=전력 노드 +block.power-node-large.name=대형 전력 노드 block.battery.name=배터리 block.battery-large.name=대형 배터리 block.combustion-generator.name=연소 발전기 block.turbine-generator.name=터빈 발전기 block.tungsten-drill.name=텅스텐 드릴 -block.carbide-drill.name=초경 드릴 +block.carbide-drill.name=합금 드릴 block.laser-drill.name=레이저 드릴 block.water-extractor.name=물 추출기 block.cultivator.name=경운기 block.dart-ship-factory.name=다트 선박 공장 block.delta-mech-factory.name=델타 메크 공장 -block.dronefactory.name=드론 팩토리 -block.repairpoint.name=수리 점 -block.resupplypoint.name=재 공급 포인트 +block.dronefactory.name=드론 공장 +block.repairpoint.name=수리 포인트 +block.resupplypoint.name=재공급 포인트 block.conduit.name=도관 block.pulseconduit.name=펄스 도관 -block.liquidrouter.name=액체 라우터 -block.liquidtank.name=액체 탱크 -block.liquidjunction.name=액체 정션 +block.liquidrouter.name=액체 분배기 +block.liquidtank.name=물탱크 +block.liquidjunction.name=액체 교차기 block.bridgeconduit.name=브릿지 도관 block.mechanical-pump.name=기계 펌프 -block.itemsource.name=품목 출처 +block.itemsource.name=아이템 소스 block.itemvoid.name=아이템 무효 block.liquidsource.name=액체 소스 block.powervoid.name=무효 전력 block.powerinfinite.name=무한한 힘 -block.unloader.name=언 로더 -block.sortedunloader.name=정렬 된 언 로더 -block.vault.name=둥근 천장 +block.unloader.name=언로더 +block.sortedunloader.name=정렬된 언로더 +block.vault.name=Vault block.wave.name=웨이브 -block.swarmer.name=스머머 +block.swarmer.name=스워머 block.salvo.name=살보 -block.ripple.name=리플 -block.phase-conveyor.name=상 컨베이어 -block.overflow-gate.name=오버플로 게이트 -block.bridge-conveyor.name=브릿지 컨베이어 -block.plastanium-compressor.name=플라스터 늄 압축기 -block.pyratite-mixer.name=Pyratite 믹서 -block.blast-mixer.name=블래스트 믹서 +block.ripple.name=라이플 +block.phase-conveyor.name=펄스 컨베이어 +block.bridge-conveyor.name=터널 +block.plastanium-compressor.name=플라스터늄 압축기 +block.pyratite-mixer.name=피터레이트 혼합기 +block.blast-mixer.name=블래스트 혼합기 block.solidifer.name=고체 block.solar-panel.name=태양 전지 패널 block.solar-panel-large.name=대형 태양 전지판 @@ -478,17 +495,7 @@ block.pulse-conduit.name=펄스 도관 block.phase-conduit.name=위상 도관 block.liquid-router.name=액체 라우터 block.liquid-tank.name=액체 탱크 -block.liquid-junction.name=액체 정션 +block.liquid-junction.name=액체 교차기 block.bridge-conduit.name=브릿지 도관 block.rotary-pump.name=로타리 펌프 -text.yes=Yes -text.no=No -text.blocks.targetsair=Targets Air -text.blocks.coolant=Coolant -text.blocks.coolantuse=Coolant Use -text.blocks.inputliquidfuel=Fuel Liquid -text.blocks.liquidfueluse=Liquid Fuel Use -block.distributor.name=Distributor -block.distributor.description=A splitter that can split items into 8 directions. -block.overflow-gate.description=A combination splitter and router that only outputs to the left and right if the front path is blocked. -block.nuclear-reactor.name=Nuclear Reactor +block.nuclear-reactor.name=핵발전소 From 136559cddd91427b0c522a73988341a56088e041 Mon Sep 17 00:00:00 2001 From: Gureumi Date: Fri, 13 Jul 2018 22:34:41 +0900 Subject: [PATCH 40/47] Update bundle_ko.properties --- core/assets/bundles/bundle_ko.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/assets/bundles/bundle_ko.properties b/core/assets/bundles/bundle_ko.properties index aab0bed693..9709ec5252 100644 --- a/core/assets/bundles/bundle_ko.properties +++ b/core/assets/bundles/bundle_ko.properties @@ -299,7 +299,7 @@ text.category.shooting=발사 setting.difficulty.easy=쉬움 setting.difficulty.normal=보통 setting.difficulty.hard=어려움 -setting.difficulty.insane=미침 +setting.difficulty.insane=[#1E90FF]K[#ADFF2F]O[#FFB6C1]R[#B0C4DE]E[#FF4500]A setting.difficulty.purge=[#1E90FF]K[#ADFF2F]O[#FFB6C1]R[#B0C4DE]E[#FF4500]A setting.difficulty.name=난이도: setting.screenshake.name=화면 흔들기 From 93bec04c927696b71f1add0c7ac33bdf346d63cb Mon Sep 17 00:00:00 2001 From: Anuken Date: Fri, 13 Jul 2018 10:11:42 -0400 Subject: [PATCH 41/47] Fixed overflow gate, splitter --- core/src/io/anuke/mindustry/core/Control.java | 4 +- core/src/io/anuke/mindustry/core/UI.java | 11 +++ .../io/anuke/mindustry/input/MobileInput.java | 77 ++++++------------- core/src/io/anuke/mindustry/io/SaveIO.java | 2 + .../mindustry/ui/dialogs/LoadDialog.java | 11 +-- core/src/io/anuke/mindustry/world/Edges.java | 1 + .../blocks/distribution/OverflowGate.java | 47 +++++++---- .../world/blocks/distribution/Splitter.java | 65 +++++++++++++--- 8 files changed, 127 insertions(+), 91 deletions(-) diff --git a/core/src/io/anuke/mindustry/core/Control.java b/core/src/io/anuke/mindustry/core/Control.java index 6a9dca0998..78ee87ed73 100644 --- a/core/src/io/anuke/mindustry/core/Control.java +++ b/core/src/io/anuke/mindustry/core/Control.java @@ -37,9 +37,7 @@ import static io.anuke.mindustry.Vars.*; * This class is not created in the headless server. */ public class Control extends Module{ - /** - * Minimum period of time between the same sound being played. - */ + /** Minimum period of time between the same sound being played.*/ private static final long minSoundPeriod = 100; private boolean hiscore = false; diff --git a/core/src/io/anuke/mindustry/core/UI.java b/core/src/io/anuke/mindustry/core/UI.java index 059d58f5c3..5df0526caa 100644 --- a/core/src/io/anuke/mindustry/core/UI.java +++ b/core/src/io/anuke/mindustry/core/UI.java @@ -34,6 +34,7 @@ import java.util.Locale; import static io.anuke.mindustry.Vars.control; import static io.anuke.mindustry.Vars.players; +import static io.anuke.mindustry.Vars.threads; import static io.anuke.ucore.scene.actions.Actions.*; public class UI extends SceneModule{ @@ -231,6 +232,16 @@ public class UI extends SceneModule{ }); } + public void loadLogic(Callable call){ + loadfrag.show(); + Timers.runTask(7f, () -> { + threads.run(() -> { + call.run(); + threads.runGraphics(loadfrag::hide); + }); + }); + } + public void showTextInput(String title, String text, String def, TextFieldFilter filter, Consumer confirmed){ new Dialog(title, "dialog"){{ content().margin(30).add(text).padRight(6f); diff --git a/core/src/io/anuke/mindustry/input/MobileInput.java b/core/src/io/anuke/mindustry/input/MobileInput.java index 4094aa118f..4fc313a31c 100644 --- a/core/src/io/anuke/mindustry/input/MobileInput.java +++ b/core/src/io/anuke/mindustry/input/MobileInput.java @@ -33,20 +33,17 @@ import io.anuke.ucore.graphics.Lines; import io.anuke.ucore.scene.Group; import io.anuke.ucore.scene.builders.imagebutton; import io.anuke.ucore.scene.builders.table; +import io.anuke.ucore.scene.event.Touchable; import io.anuke.ucore.util.Mathf; import static io.anuke.mindustry.Vars.*; import static io.anuke.mindustry.input.PlaceMode.*; public class MobileInput extends InputHandler implements GestureListener{ - /** - * Maximum speed the player can pan. - */ + /** Maximum speed the player can pan. */ private static final float maxPanSpeed = 1.3f; private static Rectangle r1 = new Rectangle(), r2 = new Rectangle(); - /** - * Distance to edge of screen to start panning. - */ + /** Distance to edge of screen to start panning. */ private final float edgePan = io.anuke.ucore.scene.ui.layout.Unit.dp.scl(60f); //gesture data @@ -54,53 +51,31 @@ public class MobileInput extends InputHandler implements GestureListener{ private Vector2 vector = new Vector2(); private float initzoom = -1; private boolean zoomed = false; - /** - * Set of completed guides. - */ + /** Set of completed guides. */ private ObjectSet guides = new ObjectSet<>(); - /** - * Position where the player started dragging a line. - */ + /** Position where the player started dragging a line. */ private int lineStartX, lineStartY; - /** - * Animation scale for line. - */ + /** Animation scale for line. */ private float lineScale; - /** - * Animation data for crosshair. - */ + /** Animation data for crosshair. */ private float crosshairScale; private TargetTrait lastTarget; - /** - * List of currently selected tiles to place. - */ + /** List of currently selected tiles to place. */ private Array selection = new Array<>(); - /** - * Place requests to be removed. - */ + /** Place requests to be removed. */ private Array removals = new Array<>(); - /** - * Whether or not the player is currently shifting all placed tiles. - */ + /** Whether or not the player is currently shifting all placed tiles. */ private boolean selecting; - /** - * Whether the player is currently in line-place mode. - */ + /** Whether the player is currently in line-place mode. */ private boolean lineMode; - /** - * Current place mode. - */ + /** Current place mode. */ private PlaceMode mode = none; - /** - * Whether no recipe was available when switching to break mode. - */ + /** Whether no recipe was available when switching to break mode. */ private Recipe lastRecipe; - /** - * Last placed request. Used for drawing block overlay. - */ + /** Last placed request. Used for drawing block overlay. */ private PlaceRequest lastPlaced; public MobileInput(Player player){ @@ -110,9 +85,7 @@ public class MobileInput extends InputHandler implements GestureListener{ //region utility methods - /** - * Check and assign targets for a specific position. - */ + /** Check and assign targets for a specific position. */ void checkTargets(float x, float y){ synchronized(Entities.entityLock){ Unit unit = Units.getClosestEnemy(player.getTeam(), x, y, 20f, u -> true); @@ -130,16 +103,12 @@ public class MobileInput extends InputHandler implements GestureListener{ } } - /** - * Returns whether this tile is in the list of requests, or at least colliding with one. - */ + /** Returns whether this tile is in the list of requests, or at least colliding with one. */ boolean hasRequest(Tile tile){ return getRequest(tile) != null; } - /** - * Returns whether this block overlaps any selection requests. - */ + /** Returns whether this block overlaps any selection requests. */ boolean checkOverlapPlacement(int x, int y, Block block){ r2.setSize(block.size * tilesize); r2.setCenter(x * tilesize + block.offset(), y * tilesize + block.offset()); @@ -159,9 +128,7 @@ public class MobileInput extends InputHandler implements GestureListener{ return false; } - /** - * Returns the selection request that overlaps this tile, or null. - */ + /** Returns the selection request that overlaps this tile, or null. */ PlaceRequest getRequest(Tile tile){ r2.setSize(tilesize); r2.setCenter(tile.worldx(), tile.worldy()); @@ -250,6 +217,8 @@ public class MobileInput extends InputHandler implements GestureListener{ margin(5); defaults().size(60f); + touchable(Touchable.enabled); + //Add a cancel button new imagebutton("icon-cancel", 16 * 2f, () -> { mode = none; @@ -297,6 +266,8 @@ public class MobileInput extends InputHandler implements GestureListener{ margin(5); defaults().size(60f); + touchable(Touchable.enabled); + //Add a break button. new imagebutton("icon-break", "toggle", 16 * 2f, () -> { mode = mode == breaking ? recipe == null ? none : placing : breaking; @@ -311,6 +282,8 @@ public class MobileInput extends InputHandler implements GestureListener{ margin(5); defaults().size(60f); + touchable(Touchable.enabled); + //Add a 'cancel building' button. new imagebutton("icon-cancel", 16 * 2f, player::clearBuilding); @@ -806,4 +779,4 @@ public class MobileInput extends InputHandler implements GestureListener{ return world.tileWorld(x - (recipe == null ? 0 : recipe.result.offset()), y - (recipe == null ? 0 : recipe.result.offset())); } } -} +} \ No newline at end of file diff --git a/core/src/io/anuke/mindustry/io/SaveIO.java b/core/src/io/anuke/mindustry/io/SaveIO.java index dfd21820c5..bfe652c384 100644 --- a/core/src/io/anuke/mindustry/io/SaveIO.java +++ b/core/src/io/anuke/mindustry/io/SaveIO.java @@ -134,6 +134,8 @@ public class SaveIO{ FileHandle backup = file.sibling(file.name() + "-backup." + file.extension()); if(backup.exists()){ load(new InflaterInputStream(backup.read())); + }else{ + throw new RuntimeException(e); } } } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/LoadDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/LoadDialog.java index 7730850513..03fb0ffdee 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/LoadDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/LoadDialog.java @@ -166,21 +166,18 @@ public class LoadDialog extends FloatingDialog{ } public void runLoadSave(SaveSlot slot){ - ui.loadfrag.show(); + hide(); + ui.paused.hide(); - Timers.runTask(3f, () -> { - ui.loadfrag.hide(); - hide(); + ui.loadLogic(() -> { try{ slot.load(); state.set(State.playing); - ui.paused.hide(); }catch(Exception e){ Log.err(e); - ui.paused.hide(); state.set(State.menu); logic.reset(); - ui.showError("$text.save.corrupted"); + threads.runGraphics(() -> ui.showError("$text.save.corrupted")); } }); } diff --git a/core/src/io/anuke/mindustry/world/Edges.java b/core/src/io/anuke/mindustry/world/Edges.java index 6c0bbbdc94..8565b5a50d 100644 --- a/core/src/io/anuke/mindustry/world/Edges.java +++ b/core/src/io/anuke/mindustry/world/Edges.java @@ -52,6 +52,7 @@ public class Edges{ } public static Tile getFacingEdge(Tile tile, Tile other){ + if(!tile.block().isMultiblock()) return tile; int size = tile.block().size; return world.tile(tile.x + Mathf.clamp(other.x - tile.x, -(size - 1) / 2, (size / 2)), tile.y + Mathf.clamp(other.y - tile.y, -(size - 1) / 2, (size / 2))); diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/OverflowGate.java b/core/src/io/anuke/mindustry/world/blocks/distribution/OverflowGate.java index db4ecebd58..6fde022a52 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/OverflowGate.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/OverflowGate.java @@ -1,7 +1,9 @@ package io.anuke.mindustry.world.blocks.distribution; import io.anuke.mindustry.type.Item; +import io.anuke.mindustry.world.Edges; import io.anuke.mindustry.world.Tile; +import io.anuke.ucore.core.Timers; import io.anuke.ucore.util.Mathf; public class OverflowGate extends Splitter{ @@ -9,22 +11,35 @@ public class OverflowGate extends Splitter{ public OverflowGate(String name){ super(name); hasItems = true; + speed = 1f; } @Override - Tile getTileTarget(Item item, Tile dest, Tile source, boolean flip){ - int dir = source.relativeTo(dest.x, dest.y); - if(dir == -1) return null; - Tile to = dest.getNearby(dir); + public void update(Tile tile){ + SplitterEntity entity = tile.entity(); + if(entity.lastItem != null){ + entity.time += 1f/speed * Timers.delta(); + Tile target = getTileTarget(tile, entity.lastItem, entity.lastInput, false); - if((!to.block().acceptItem(item, to, dest) || - (to.block().instantTransfer && source.block().instantTransfer))){ - Tile a = dest.getNearby(Mathf.mod(dir - 1, 4)); - Tile b = dest.getNearby(Mathf.mod(dir + 1, 4)); - boolean ac = !(a.block().instantTransfer && source.block().instantTransfer) && - a.block().acceptItem(item, a, dest); - boolean bc = !(b.block().instantTransfer && source.block().instantTransfer) && - b.block().acceptItem(item, b, dest); + if(target != null && (entity.time >= 1f)){ + getTileTarget(tile, entity.lastItem, entity.lastInput, true); + target.block().handleItem(entity.lastItem, target, Edges.getFacingEdge(tile, target)); + entity.items.remove(entity.lastItem, 1); + entity.lastItem = null; + } + } + } + + Tile getTileTarget(Tile tile, Item item, int from, boolean flip){ + if(from == -1) return null; + Tile to = tile.getNearby((from + 2) % 4); + Tile edge = Edges.getFacingEdge(tile, to); + + if(!to.block().acceptItem(item, to, edge) || (to.block() instanceof OverflowGate)){ + Tile a = tile.getNearby(Mathf.mod(from - 1, 4)); + Tile b = tile.getNearby(Mathf.mod(from + 1, 4)); + boolean ac = a.block().acceptItem(item, a, edge) && !(a.block() instanceof OverflowGate); + boolean bc = b.block().acceptItem(item, b, edge) && !(b.block() instanceof OverflowGate); if(!ac && !bc){ return null; @@ -35,14 +50,12 @@ public class OverflowGate extends Splitter{ }else if(bc && !ac){ to = b; }else{ - if(dest.getDump() == 0){ + if(tile.getDump() == 0){ to = a; - if(flip) - dest.setDump((byte) 1); + if(flip) tile.setDump((byte) 1); }else{ to = b; - if(flip) - dest.setDump((byte) 0); + if(flip) tile.setDump((byte) 0); } } } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/Splitter.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Splitter.java index f0d4d84022..462f28da1d 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Splitter.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Splitter.java @@ -1,22 +1,24 @@ package io.anuke.mindustry.world.blocks.distribution; import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.type.Item; import io.anuke.mindustry.world.BarType; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Edges; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.meta.BlockGroup; +import io.anuke.ucore.core.Timers; public class Splitter extends Block{ - protected float speed = 30f; + protected float speed = 9f; public Splitter(String name){ super(name); solid = true; - instantTransfer = true; update = true; - hasItems = false; + hasItems = true; + itemCapacity = 1; group = BlockGroup.transportation; } @@ -28,29 +30,68 @@ public class Splitter extends Block{ } @Override - public boolean acceptItem(Item item, Tile tile, Tile source){ - Tile to = getTileTarget(item, tile, source, false); + public void update(Tile tile){ + SplitterEntity entity = tile.entity(); + if(entity.lastItem != null){ + entity.time += 1f/speed * Timers.delta(); + Tile target = getTileTarget(tile, entity.lastItem, entity.lastInput); - return to != null; + if(target != null && (entity.time >= 1f)){ + target.block().handleItem(entity.lastItem, target, Edges.getFacingEdge(tile, target)); + entity.items.remove(entity.lastItem, 1); + entity.lastItem = null; + } + } + } + + @Override + public boolean acceptItem(Item item, Tile tile, Tile source){ + SplitterEntity entity = tile.entity(); + + return entity.lastItem == null; } @Override public void handleItem(Item item, Tile tile, Tile source){ - Tile to = getTileTarget(item, tile, source, true); - to.block().handleItem(item, to, Edges.getFacingEdge(tile, to)); + SplitterEntity entity = tile.entity(); + entity.items.add(item, 1); + entity.lastItem = item; + entity.time = 0f; + entity.lastInput = tile.relativeTo(source.x, source.y); } - Tile getTileTarget(Item item, Tile tile, Tile source, boolean flip){ + Tile getTileTarget(Tile tile, Item item, int from){ Array proximity = tile.entity.proximity(); int counter = tile.getDump(); for(int i = 0; i < proximity.size; i++){ Tile other = proximity.get((i + counter) % proximity.size); - if(flip) tile.setDump((byte) ((tile.getDump() + 1) % proximity.size)); - if(other != source && !(source.block().instantTransfer && other.block().instantTransfer) && !(other.block() instanceof Splitter) && - other.block().acceptItem(item, other, Edges.getFacingEdge(tile, other))){ + if(tile.relativeTo(other.x, other.y) == from) continue; + tile.setDump((byte) ((tile.getDump() + 1) % proximity.size)); + if(other.block().acceptItem(item, other, Edges.getFacingEdge(tile, other))){ return other; } } return null; } + + @Override + public int removeStack(Tile tile, Item item, int amount){ + SplitterEntity entity = tile.entity(); + int result = super.removeStack(tile, item, amount); + if(result != 0 && item == entity.lastItem){ + entity.lastItem = null; + } + return result; + } + + @Override + public TileEntity getEntity(){ + return new SplitterEntity(); + } + + public class SplitterEntity extends TileEntity{ + Item lastItem; + int lastInput; + float time; + } } From 98ac25ac29f21e6972d8685f4170aab035eacf36 Mon Sep 17 00:00:00 2001 From: Anuken Date: Fri, 13 Jul 2018 10:21:17 -0400 Subject: [PATCH 42/47] Fixed distributor --- .../world/blocks/distribution/OverflowGate.java | 4 +++- .../world/blocks/distribution/Splitter.java | 13 +++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/OverflowGate.java b/core/src/io/anuke/mindustry/world/blocks/distribution/OverflowGate.java index 6fde022a52..929ffe12ca 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/OverflowGate.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/OverflowGate.java @@ -30,7 +30,9 @@ public class OverflowGate extends Splitter{ } } - Tile getTileTarget(Tile tile, Item item, int from, boolean flip){ + @Override + Tile getTileTarget(Tile tile, Item item, Tile src, boolean flip){ + int from = tile.relativeTo(src.x, src.y); if(from == -1) return null; Tile to = tile.getNearby((from + 2) % 4); Tile edge = Edges.getFacingEdge(tile, to); diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/Splitter.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Splitter.java index 462f28da1d..307405e9ca 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Splitter.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Splitter.java @@ -34,9 +34,10 @@ public class Splitter extends Block{ SplitterEntity entity = tile.entity(); if(entity.lastItem != null){ entity.time += 1f/speed * Timers.delta(); - Tile target = getTileTarget(tile, entity.lastItem, entity.lastInput); + Tile target = getTileTarget(tile, entity.lastItem, entity.lastInput, false); if(target != null && (entity.time >= 1f)){ + getTileTarget(tile, entity.lastItem, entity.lastInput, true); target.block().handleItem(entity.lastItem, target, Edges.getFacingEdge(tile, target)); entity.items.remove(entity.lastItem, 1); entity.lastItem = null; @@ -57,16 +58,16 @@ public class Splitter extends Block{ entity.items.add(item, 1); entity.lastItem = item; entity.time = 0f; - entity.lastInput = tile.relativeTo(source.x, source.y); + entity.lastInput = source; } - Tile getTileTarget(Tile tile, Item item, int from){ + Tile getTileTarget(Tile tile, Item item, Tile from, boolean set){ Array proximity = tile.entity.proximity(); int counter = tile.getDump(); for(int i = 0; i < proximity.size; i++){ Tile other = proximity.get((i + counter) % proximity.size); - if(tile.relativeTo(other.x, other.y) == from) continue; - tile.setDump((byte) ((tile.getDump() + 1) % proximity.size)); + if(tile == from) continue; + if(set) tile.setDump((byte) ((tile.getDump() + 1) % proximity.size)); if(other.block().acceptItem(item, other, Edges.getFacingEdge(tile, other))){ return other; } @@ -91,7 +92,7 @@ public class Splitter extends Block{ public class SplitterEntity extends TileEntity{ Item lastItem; - int lastInput; + Tile lastInput; float time; } } From 6abef7d6457d8962167d078eef60e6ae06a0536f Mon Sep 17 00:00:00 2001 From: Anuken Date: Fri, 13 Jul 2018 11:09:33 -0400 Subject: [PATCH 43/47] Fixed item bridge crash --- .../content/blocks/DistributionBlocks.java | 3 +- .../content/blocks/LiquidBlocks.java | 2 + .../world/blocks/distribution/ItemBridge.java | 4 +- .../blocks/distribution/TunnelConduit.java | 78 ------------------- 4 files changed, 6 insertions(+), 81 deletions(-) delete mode 100644 core/src/io/anuke/mindustry/world/blocks/distribution/TunnelConduit.java diff --git a/core/src/io/anuke/mindustry/content/blocks/DistributionBlocks.java b/core/src/io/anuke/mindustry/content/blocks/DistributionBlocks.java index 526563033e..b5bbf1ad52 100644 --- a/core/src/io/anuke/mindustry/content/blocks/DistributionBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/DistributionBlocks.java @@ -28,12 +28,11 @@ public class DistributionBlocks extends BlockList implements ContentList{ bridgeConveyor = new BufferedItemBridge("bridge-conveyor"){{ range = 3; - hasPower = false; - consumes.power(0.05f); }}; phaseConveyor = new ItemBridge("phase-conveyor"){{ range = 7; + hasPower = false; consumes.power(0.05f); }}; diff --git a/core/src/io/anuke/mindustry/content/blocks/LiquidBlocks.java b/core/src/io/anuke/mindustry/content/blocks/LiquidBlocks.java index 30205142f4..8634381425 100644 --- a/core/src/io/anuke/mindustry/content/blocks/LiquidBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/LiquidBlocks.java @@ -64,6 +64,8 @@ public class LiquidBlocks extends BlockList implements ContentList{ phaseConduit = new LiquidBridge("phase-conduit"){{ range = 7; + hasPower = false; + consumes.power(0.05f); }}; } } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java b/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java index f162f1a530..da6c3f30d0 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java @@ -243,8 +243,10 @@ public class ItemBridge extends Block{ public boolean acceptItem(Item item, Tile tile, Tile source){ ItemBridgeEntity entity = tile.entity(); Tile other = world.tile(entity.link); + boolean linked = false; if(linkValid(tile, other)){ + linked = true; int rel = tile.absoluteRelativeTo(other.x, other.y); int rel2 = tile.relativeTo(source.x, source.y); @@ -264,7 +266,7 @@ public class ItemBridge extends Block{ } } - return tile.entity.items.total() < itemCapacity; + return tile.entity.items.total() < itemCapacity && (linked || source.block() instanceof ItemBridge); } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/TunnelConduit.java b/core/src/io/anuke/mindustry/world/blocks/distribution/TunnelConduit.java deleted file mode 100644 index 8639a99da9..0000000000 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/TunnelConduit.java +++ /dev/null @@ -1,78 +0,0 @@ -package io.anuke.mindustry.world.blocks.distribution; - -import com.badlogic.gdx.graphics.g2d.TextureRegion; -import io.anuke.mindustry.type.Liquid; -import io.anuke.mindustry.world.BarType; -import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.blocks.LiquidBlock; -import io.anuke.ucore.graphics.Draw; - -public class TunnelConduit extends LiquidBlock{ - protected int maxdist = 3; - protected float speed = 53; - - protected TunnelConduit(String name){ - super(name); - rotate = true; - solid = true; - health = 70; - hasItems = true; - instantTransfer = true; - } - - @Override - public void setBars(){ - super.setBars(); - bars.remove(BarType.liquid); - } - - @Override - public TextureRegion[] getIcon(){ - return new TextureRegion[]{Draw.region(name)}; - } - - @Override - public void draw(Tile tile){ - Draw.rect(region, tile.drawx(), tile.drawy(), tile.getRotation() * 90); - } - - @Override - public void handleLiquid(Tile tile, Tile source, Liquid liquid, float amount){ - Tile tunnel = getDestTunnel(tile); - if(tunnel == null) return; - Tile to = tunnel.getNearby(tunnel.getRotation()); - if(to == null || !(to.block().hasLiquids)) return; - - if(to.block().acceptLiquid(to, tunnel, liquid, amount)) - to.block().handleLiquid(to, tunnel, liquid, amount); - } - - @Override - public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){ - int rot = source.relativeTo(tile.x, tile.y); - if(rot != (tile.getRotation() + 2) % 4) return false; - Tile tunnel = getDestTunnel(tile); - - if(tunnel != null){ - Tile to = tunnel.getNearby(tunnel.getRotation()); - return to != null && (to.block().hasLiquids) && - (to.block()).acceptLiquid(to, tunnel, liquid, amount); - }else{ - return false; - } - } - - Tile getDestTunnel(Tile tile){ - Tile dest = tile; - int rel = (tile.getRotation() + 2) % 4; - for(int i = 0; i < maxdist; i++){ - if(dest == null) return null; - dest = dest.getNearby(rel); - if(dest != null && dest.block() instanceof TunnelConduit && dest.getRotation() == rel - && dest.getNearby(rel) != null){ - return dest; - } - } - return null; - } -} From 380d9908b4544e2124955810c5c9c547acc46b4f Mon Sep 17 00:00:00 2001 From: Anuken Date: Fri, 13 Jul 2018 11:46:16 -0400 Subject: [PATCH 44/47] Fixed chat stopping velocity / Fixed drone repair not being synced --- .../src/io/anuke/mindustry/core/Renderer.java | 1 + .../io/anuke/mindustry/entities/Player.java | 22 +- .../mindustry/entities/units/types/Drone.java | 257 ++++++++++-------- 3 files changed, 152 insertions(+), 128 deletions(-) diff --git a/core/src/io/anuke/mindustry/core/Renderer.java b/core/src/io/anuke/mindustry/core/Renderer.java index 29ddc9762a..65aa2f3eda 100644 --- a/core/src/io/anuke/mindustry/core/Renderer.java +++ b/core/src/io/anuke/mindustry/core/Renderer.java @@ -110,6 +110,7 @@ public class Renderer extends RendererModule{ Cursors.arrow = Cursors.loadCursor("cursor"); Cursors.hand = Cursors.loadCursor("hand"); Cursors.ibeam = Cursors.loadCursor("ibar"); + Cursors.restoreCursor(); Cursors.loadCustom("drill"); Cursors.loadCustom("unload"); diff --git a/core/src/io/anuke/mindustry/entities/Player.java b/core/src/io/anuke/mindustry/entities/Player.java index d5b3b34a05..9a4641dce2 100644 --- a/core/src/io/anuke/mindustry/entities/Player.java +++ b/core/src/io/anuke/mindustry/entities/Player.java @@ -495,8 +495,6 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra damage(health + 1); //die instantly } - if(ui.chatfrag.chatOpen()) return; - float speed = isBoosting && !mech.flying ? debug ? 5f : mech.boostSpeed : mech.speed; //fraction of speed when at max load float carrySlowdown = 0.7f; @@ -511,7 +509,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra } //drop from carrier on key press - if(Inputs.keyTap("drop_unit")){ + if(!ui.chatfrag.chatOpen() && Inputs.keyTap("drop_unit")){ if(!mech.flying){ if(getCarrier() != null){ CallEntity.dropSelf(this); @@ -547,7 +545,9 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra movement.limit(speed * Timers.delta()); if(getCarrier() == null){ - velocity.add(movement); + if(!ui.chatfrag.chatOpen()){ + velocity.add(movement); + } float prex = x, prey = y; updateVelocityStatus(mech.drag, 10f); moved = distanceTo(prex, prey) > 0.01f; @@ -557,13 +557,15 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra y = Mathf.lerpDelta(y, getCarrier().getY(), 0.1f); } - if(!isShooting()){ - if(!movement.isZero()){ - rotation = Mathf.slerpDelta(rotation, movement.angle(), 0.13f); + if(!ui.chatfrag.chatOpen()){ + if(!isShooting()){ + if(!movement.isZero()){ + rotation = Mathf.slerpDelta(rotation, movement.angle(), 0.13f); + } + }else{ + float angle = control.input(playerIndex).mouseAngle(x, y); + this.rotation = Mathf.slerpDelta(this.rotation, angle, 0.1f); } - }else{ - float angle = control.input(playerIndex).mouseAngle(x, y); - this.rotation = Mathf.slerpDelta(this.rotation, angle, 0.1f); } } diff --git a/core/src/io/anuke/mindustry/entities/units/types/Drone.java b/core/src/io/anuke/mindustry/entities/units/types/Drone.java index 3cf689b327..f51229f6cf 100644 --- a/core/src/io/anuke/mindustry/entities/units/types/Drone.java +++ b/core/src/io/anuke/mindustry/entities/units/types/Drone.java @@ -16,6 +16,7 @@ import io.anuke.mindustry.entities.units.UnitState; import io.anuke.mindustry.game.EventType.BlockBuildEvent; import io.anuke.mindustry.gen.CallEntity; import io.anuke.mindustry.graphics.Palette; +import io.anuke.mindustry.net.Net; import io.anuke.mindustry.type.Item; import io.anuke.mindustry.type.ItemStack; import io.anuke.mindustry.world.Tile; @@ -47,9 +48,11 @@ public class Drone extends FlyingUnit implements BuilderTrait{ protected Item targetItem; protected Tile mineTile; protected Queue placeQueue = new ThreadQueue<>(); + public final UnitState - build = new UnitState(){ + build = new UnitState(){ + public void entered(){ if(!(target instanceof BuildEntity)){ target = null; @@ -90,6 +93,7 @@ public class Drone extends FlyingUnit implements BuilderTrait{ }, repair = new UnitState(){ + public void entered(){ target = null; } @@ -118,141 +122,142 @@ public class Drone extends FlyingUnit implements BuilderTrait{ } } }, - mine = new UnitState(){ - public void entered(){ - target = null; + + mine = new UnitState(){ + public void entered(){ + target = null; + } + + public void update(){ + TileEntity entity = getClosestCore(); + + if(entity == null) return; + + if(targetItem == null){ + findItem(); + } + + //core full + if(targetItem != null && entity.tile.block().acceptStack(targetItem, 1, entity.tile, Drone.this) == 0){ + setState(repair); + return; + } + + //if inventory is full, drop it off. + if(inventory.isFull()){ + setState(drop); + }else{ + if(targetItem != null && !inventory.canAcceptItem(targetItem)){ + setState(drop); + return; } - public void update(){ - TileEntity entity = getClosestCore(); + retarget(() -> { + if(findItemDrop()){ + return; + } - if(entity == null) return; - - if(targetItem == null){ + if(getMineTile() == null){ findItem(); } - //core full - if(targetItem != null && entity.tile.block().acceptStack(targetItem, 1, entity.tile, Drone.this) == 0){ - setState(repair); - return; + if(targetItem == null) return; + + target = world.indexer().findClosestOre(x, y, targetItem); + }); + + if(target instanceof Tile){ + moveTo(type.range / 1.5f); + + if(distanceTo(target) < type.range && mineTile != target){ + setMineTile((Tile) target); } - //if inventory is full, drop it off. - if(inventory.isFull()){ + if(((Tile) target).block() != Blocks.air){ setState(drop); - }else{ - if(targetItem != null && !inventory.canAcceptItem(targetItem)){ - setState(drop); - return; - } - - retarget(() -> { - if(findItemDrop()){ - return; - } - - if(getMineTile() == null){ - findItem(); - } - - if(targetItem == null) return; - - target = world.indexer().findClosestOre(x, y, targetItem); - }); - - if(target instanceof Tile){ - moveTo(type.range / 1.5f); - - if(distanceTo(target) < type.range && mineTile != target){ - setMineTile((Tile) target); - } - - if(((Tile) target).block() != Blocks.air){ - setState(drop); - } - } } } + } + } - public void exited(){ - setMineTile(null); + public void exited(){ + setMineTile(null); + } + }, + pickup = new UnitState(){ + public void entered(){ + target = null; + } + + public void update(){ + ItemDrop item = (ItemDrop) target; + + if(inventory.isFull() || !inventory.canAcceptItem(item.getItem(), 1)){ + setState(drop); + return; + } + + if(distanceTo(item) < 4){ + item.collision(Drone.this, x, y); + } + + //item has been picked up + if(item.getAmount() == 0){ + if(!findItemDrop()){ + setState(drop); } - }, - pickup = new UnitState(){ - public void entered(){ - target = null; + } + + moveTo(0f); + } + }, + drop = new UnitState(){ + public void entered(){ + target = null; + } + + public void update(){ + if(inventory.isEmpty()){ + setState(mine); + return; + } + + target = getClosestCore(); + + if(target == null) return; + + TileEntity tile = (TileEntity) target; + + if(distanceTo(target) < type.range){ + if(tile.tile.block().acceptStack(inventory.getItem().item, inventory.getItem().amount, tile.tile, Drone.this) == inventory.getItem().amount){ + CallEntity.transferItemTo(inventory.getItem().item, inventory.getItem().amount, x, y, tile.tile); + inventory.clearItem(); } - public void update(){ - ItemDrop item = (ItemDrop) target; + setState(repair); + } - if(inventory.isFull() || !inventory.canAcceptItem(item.getItem(), 1)){ - setState(drop); - return; - } + circle(type.range / 1.8f); + } + }, + retreat = new UnitState(){ + public void entered(){ + target = null; + } - if(distanceTo(item) < 4){ - item.collision(Drone.this, x, y); - } - - //item has been picked up - if(item.getAmount() == 0){ - if(!findItemDrop()){ - setState(drop); - } - } - - moveTo(0f); + public void update(){ + if(health >= health){ + state.set(attack); + }else if(!targetHasFlag(BlockFlag.repair)){ + if(timer.get(timerTarget, 20)){ + Tile target = Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.repair)); + if(target != null) Drone.this.target = target.entity; } - }, - drop = new UnitState(){ - public void entered(){ - target = null; - } - - public void update(){ - if(inventory.isEmpty()){ - setState(mine); - return; - } - - target = getClosestCore(); - - if(target == null) return; - - TileEntity tile = (TileEntity) target; - - if(distanceTo(target) < type.range){ - if(tile.tile.block().acceptStack(inventory.getItem().item, inventory.getItem().amount, tile.tile, Drone.this) == inventory.getItem().amount){ - CallEntity.transferItemTo(inventory.getItem().item, inventory.getItem().amount, x, y, tile.tile); - inventory.clearItem(); - } - - setState(repair); - } - - circle(type.range / 1.8f); - } - }, - retreat = new UnitState(){ - public void entered(){ - target = null; - } - - public void update(){ - if(health >= health){ - state.set(attack); - }else if(!targetHasFlag(BlockFlag.repair)){ - if(timer.get(timerTarget, 20)){ - Tile target = Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.repair)); - if(target != null) Drone.this.target = target.entity; - } - }else{ - circle(40f); - } - } - }; + }else{ + circle(40f); + } + } + }; { initEvents(); @@ -323,6 +328,12 @@ public class Drone extends FlyingUnit implements BuilderTrait{ public void update(){ super.update(); + if(Net.client() && state.is(repair) && target instanceof TileEntity){ + TileEntity entity = (TileEntity) target; + entity.health += type.healSpeed * Timers.delta(); + entity.health = Mathf.clamp(entity.health, 0, entity.tile.block().health); + } + x += Mathf.sin(Timers.time() + id * 999, 25f, 0.07f); y += Mathf.cos(Timers.time() + id * 999, 25f, 0.07f); @@ -418,6 +429,7 @@ public class Drone extends FlyingUnit implements BuilderTrait{ public void write(DataOutput data) throws IOException{ super.write(data); data.writeInt(mineTile == null ? -1 : mineTile.packedPosition()); + data.writeInt(state.is(repair) && target instanceof TileEntity ? ((TileEntity)target).tile.packedPosition() : -1); writeBuilding(data); } @@ -425,12 +437,21 @@ public class Drone extends FlyingUnit implements BuilderTrait{ public void read(DataInput data, long time) throws IOException{ super.read(data, time); int mined = data.readInt(); + int repairing = data.readInt(); readBuilding(data); if(mined != -1){ mineTile = world.tile(mined); } + + if(repairing != -1){ + Tile tile = world.tile(repairing); + target = tile.entity; + state.set(repair); + }else{ + state.set(retreat); + } } } From 2c8cd2f8dc988a7a8cb2606df4ccaa55ea1d069b Mon Sep 17 00:00:00 2001 From: Anuken Date: Fri, 13 Jul 2018 12:50:45 -0400 Subject: [PATCH 45/47] Fixed autosave not triggering --- core/src/io/anuke/mindustry/core/UI.java | 4 ++++ core/src/io/anuke/mindustry/io/Saves.java | 17 ++++++++++++----- .../mindustry/ui/dialogs/PausedDialog.java | 11 +++-------- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/core/src/io/anuke/mindustry/core/UI.java b/core/src/io/anuke/mindustry/core/UI.java index 5df0526caa..d57d230993 100644 --- a/core/src/io/anuke/mindustry/core/UI.java +++ b/core/src/io/anuke/mindustry/core/UI.java @@ -233,6 +233,10 @@ public class UI extends SceneModule{ } public void loadLogic(Callable call){ + loadLogic("$text.loading", call); + } + + public void loadLogic(String text, Callable call){ loadfrag.show(); Timers.runTask(7f, () -> { threads.run(() -> { diff --git a/core/src/io/anuke/mindustry/io/Saves.java b/core/src/io/anuke/mindustry/io/Saves.java index 79b5756936..4096974e97 100644 --- a/core/src/io/anuke/mindustry/io/Saves.java +++ b/core/src/io/anuke/mindustry/io/Saves.java @@ -4,7 +4,9 @@ import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.utils.Array; import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.game.Difficulty; +import io.anuke.mindustry.game.EventType.StateChangeEvent; import io.anuke.mindustry.game.GameMode; +import io.anuke.ucore.core.Events; import io.anuke.ucore.core.Settings; import io.anuke.ucore.core.Timers; import io.anuke.ucore.util.ThreadArray; @@ -20,6 +22,14 @@ public class Saves{ private boolean saving; private float time; + public Saves(){ + Events.on(StateChangeEvent.class, (prev, state) -> { + if(state == State.menu){ + threads.run(() -> current = null); + } + }); + } + public void load(){ saves.clear(); for(int i = 0; i < saveSlots; i++){ @@ -37,9 +47,6 @@ public class Saves{ } public void update(){ - if(state.is(State.menu)){ - current = null; - } if(!state.is(State.menu) && !state.gameOver && current != null && current.isAutosave()){ time += Timers.delta(); @@ -109,15 +116,15 @@ public class Saves{ } public void load(){ - current = this; SaveIO.loadFromSlot(index); meta = SaveIO.getData(index); + current = this; } public void save(){ - current = this; SaveIO.saveToSlot(index); meta = SaveIO.getData(index); + current = this; } public String getDate(){ diff --git a/core/src/io/anuke/mindustry/ui/dialogs/PausedDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/PausedDialog.java index d2be114720..a517f2894c 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/PausedDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/PausedDialog.java @@ -1,9 +1,7 @@ package io.anuke.mindustry.ui.dialogs; -import com.badlogic.gdx.utils.reflect.ClassReflection; import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.net.Net; -import io.anuke.ucore.core.Timers; import io.anuke.ucore.scene.builders.build; import io.anuke.ucore.scene.builders.imagebutton; import io.anuke.ucore.util.Bundles; @@ -130,15 +128,12 @@ public class PausedDialog extends FloatingDialog{ return; } - ui.loadfrag.show("$text.saveload"); - - Timers.runTask(5f, () -> { - ui.loadfrag.hide(); + ui.loadLogic("$text.saveload", () -> { try{ control.getSaves().getCurrent().save(); }catch(Throwable e){ - e = (e.getCause() == null ? e : e.getCause()); - ui.showError("[orange]" + Bundles.get("text.savefail") + "\n[white]" + ClassReflection.getSimpleName(e.getClass()) + ": " + e.getMessage() + "\n" + "at " + e.getStackTrace()[0].getFileName() + ":" + e.getStackTrace()[0].getLineNumber()); + e.printStackTrace(); + threads.runGraphics(() -> ui.showError("[orange]" + Bundles.get("text.savefail"))); } state.set(State.menu); }); From 21c106e9d59d555e7e07279ca2c496a1bd7b79d5 Mon Sep 17 00:00:00 2001 From: Anuken Date: Fri, 13 Jul 2018 12:53:40 -0400 Subject: [PATCH 46/47] Updated splitter description --- core/assets/bundles/bundle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 1ed040bc32..f19fceafef 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -438,7 +438,7 @@ block.conveyor.name=Conveyor block.titanium-conveyor.name=Titanium Conveyor block.junction.name=Junction block.splitter.name=Splitter -block.splitter.description=Outputs items into two opposite directions immediately after they are recieved. +block.splitter.description=Outputs items into three different directions once they are recieved. block.router.name=Router block.router.description=Splits items into all 4 directions. Can store items as a buffer. block.distributor.name=Distributor From 82c4c791cc69d111b919eb59a026636c7aff51f5 Mon Sep 17 00:00:00 2001 From: Anuken Date: Fri, 13 Jul 2018 13:43:37 -0400 Subject: [PATCH 47/47] Added message for loading broken saves --- core/assets/bundles/bundle.properties | 2 +- core/assets/bundles/bundle_de.properties | 1 + core/assets/bundles/bundle_es.properties | 1 + core/assets/bundles/bundle_fr.properties | 1 + core/assets/bundles/bundle_in_ID.properties | 1 + core/assets/bundles/bundle_ita.properties | 1 + core/assets/bundles/bundle_ko.properties | 1 + core/assets/bundles/bundle_pl.properties | 1 + core/assets/bundles/bundle_pt_BR.properties | 1 + core/assets/bundles/bundle_ru.properties | 1 + core/assets/bundles/bundle_tk.properties | 1 + core/assets/bundles/bundle_uk_UA.properties | 1 + .../mindustry/entities/units/FlyingUnit.java | 146 +++++++++--------- .../anuke/mindustry/io/SaveFileVersion.java | 3 +- core/src/io/anuke/mindustry/io/SaveIO.java | 4 +- core/src/io/anuke/mindustry/io/SaveMeta.java | 4 +- core/src/io/anuke/mindustry/io/Saves.java | 4 + .../anuke/mindustry/io/versions/Save16.java | 3 +- .../mindustry/ui/dialogs/LoadDialog.java | 8 +- 19 files changed, 107 insertions(+), 78 deletions(-) diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index f19fceafef..3e9976024d 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -123,6 +123,7 @@ text.server.port=Port: text.server.addressinuse=Address already in use! text.server.invalidport=Invalid port number! text.server.error=[crimson]Error hosting server: [orange]{0} +text.save.old=This save is for an older version of the game, and can no longer be used.\n\n[LIGHT_GRAY]Save backwards compatibility will be implemented in the full 4.0 release. text.save.new=New Save text.save.overwrite=Are you sure you want to overwrite\nthis save slot? text.overwrite=Overwrite @@ -292,7 +293,6 @@ text.blocks.inputfuel=Fuel text.blocks.fuelburntime=Fuel Burn Time text.blocks.inputcapacity=Input capacity text.blocks.outputcapacity=Output capacity -text.blocks.required=Required: text.unit.blocks=blocks text.unit.powersecond=power units/second diff --git a/core/assets/bundles/bundle_de.properties b/core/assets/bundles/bundle_de.properties index 23c3f886eb..78b1bf4794 100644 --- a/core/assets/bundles/bundle_de.properties +++ b/core/assets/bundles/bundle_de.properties @@ -492,3 +492,4 @@ block.liquid-junction.name=Liquid Junction block.bridge-conduit.name=Bridge Conduit block.rotary-pump.name=Rotary Pump block.nuclear-reactor.name=Nuclear Reactor +text.save.old=This save is for an older version of the game, and can no longer be used.\n\n[LIGHT_GRAY]Save backwards compatibility will be implemented in the full 4.0 release. diff --git a/core/assets/bundles/bundle_es.properties b/core/assets/bundles/bundle_es.properties index 9ee631d3ab..a8136b7ead 100644 --- a/core/assets/bundles/bundle_es.properties +++ b/core/assets/bundles/bundle_es.properties @@ -492,3 +492,4 @@ block.liquid-junction.name=Liquid Junction block.bridge-conduit.name=Bridge Conduit block.rotary-pump.name=Rotary Pump block.nuclear-reactor.name=Nuclear Reactor +text.save.old=This save is for an older version of the game, and can no longer be used.\n\n[LIGHT_GRAY]Save backwards compatibility will be implemented in the full 4.0 release. diff --git a/core/assets/bundles/bundle_fr.properties b/core/assets/bundles/bundle_fr.properties index e1ee0c31a7..4d29faf7f7 100644 --- a/core/assets/bundles/bundle_fr.properties +++ b/core/assets/bundles/bundle_fr.properties @@ -492,3 +492,4 @@ block.liquid-junction.name=Liquid Junction block.bridge-conduit.name=Bridge Conduit block.rotary-pump.name=Rotary Pump block.nuclear-reactor.name=Nuclear Reactor +text.save.old=This save is for an older version of the game, and can no longer be used.\n\n[LIGHT_GRAY]Save backwards compatibility will be implemented in the full 4.0 release. diff --git a/core/assets/bundles/bundle_in_ID.properties b/core/assets/bundles/bundle_in_ID.properties index 9f1ec1436f..b4c7f1e2a4 100644 --- a/core/assets/bundles/bundle_in_ID.properties +++ b/core/assets/bundles/bundle_in_ID.properties @@ -492,3 +492,4 @@ block.liquid-junction.name=Liquid Junction block.bridge-conduit.name=Bridge Conduit block.rotary-pump.name=Rotary Pump block.nuclear-reactor.name=Nuclear Reactor +text.save.old=This save is for an older version of the game, and can no longer be used.\n\n[LIGHT_GRAY]Save backwards compatibility will be implemented in the full 4.0 release. diff --git a/core/assets/bundles/bundle_ita.properties b/core/assets/bundles/bundle_ita.properties index 93cb8a4f36..4b02d88130 100644 --- a/core/assets/bundles/bundle_ita.properties +++ b/core/assets/bundles/bundle_ita.properties @@ -492,3 +492,4 @@ block.liquid-junction.name=Liquid Junction block.bridge-conduit.name=Bridge Conduit block.rotary-pump.name=Rotary Pump block.nuclear-reactor.name=Nuclear Reactor +text.save.old=This save is for an older version of the game, and can no longer be used.\n\n[LIGHT_GRAY]Save backwards compatibility will be implemented in the full 4.0 release. diff --git a/core/assets/bundles/bundle_ko.properties b/core/assets/bundles/bundle_ko.properties index 3b3f145bd3..6169edd949 100644 --- a/core/assets/bundles/bundle_ko.properties +++ b/core/assets/bundles/bundle_ko.properties @@ -492,3 +492,4 @@ block.distributor.name=Distributor block.distributor.description=A splitter that can split items into 8 directions. block.overflow-gate.description=A combination splitter and router that only outputs to the left and right if the front path is blocked. block.nuclear-reactor.name=Nuclear Reactor +text.save.old=This save is for an older version of the game, and can no longer be used.\n\n[LIGHT_GRAY]Save backwards compatibility will be implemented in the full 4.0 release. diff --git a/core/assets/bundles/bundle_pl.properties b/core/assets/bundles/bundle_pl.properties index 1336e0650b..ef022067b0 100644 --- a/core/assets/bundles/bundle_pl.properties +++ b/core/assets/bundles/bundle_pl.properties @@ -492,3 +492,4 @@ block.liquid-junction.name=Liquid Junction block.bridge-conduit.name=Bridge Conduit block.rotary-pump.name=Rotary Pump block.nuclear-reactor.name=Nuclear Reactor +text.save.old=This save is for an older version of the game, and can no longer be used.\n\n[LIGHT_GRAY]Save backwards compatibility will be implemented in the full 4.0 release. diff --git a/core/assets/bundles/bundle_pt_BR.properties b/core/assets/bundles/bundle_pt_BR.properties index 04f28bf94a..3b54e1282d 100644 --- a/core/assets/bundles/bundle_pt_BR.properties +++ b/core/assets/bundles/bundle_pt_BR.properties @@ -492,3 +492,4 @@ block.liquid-junction.name=Liquid Junction block.bridge-conduit.name=Bridge Conduit block.rotary-pump.name=Rotary Pump block.nuclear-reactor.name=Nuclear Reactor +text.save.old=This save is for an older version of the game, and can no longer be used.\n\n[LIGHT_GRAY]Save backwards compatibility will be implemented in the full 4.0 release. diff --git a/core/assets/bundles/bundle_ru.properties b/core/assets/bundles/bundle_ru.properties index 1d14c9c417..81593c7e58 100644 --- a/core/assets/bundles/bundle_ru.properties +++ b/core/assets/bundles/bundle_ru.properties @@ -492,3 +492,4 @@ block.liquid-junction.name=Liquid Junction block.bridge-conduit.name=Bridge Conduit block.rotary-pump.name=Rotary Pump block.nuclear-reactor.name=Nuclear Reactor +text.save.old=This save is for an older version of the game, and can no longer be used.\n\n[LIGHT_GRAY]Save backwards compatibility will be implemented in the full 4.0 release. diff --git a/core/assets/bundles/bundle_tk.properties b/core/assets/bundles/bundle_tk.properties index 6120e00fd3..b8006f0edb 100644 --- a/core/assets/bundles/bundle_tk.properties +++ b/core/assets/bundles/bundle_tk.properties @@ -492,3 +492,4 @@ block.liquid-junction.name=Liquid Junction block.bridge-conduit.name=Bridge Conduit block.rotary-pump.name=Rotary Pump block.nuclear-reactor.name=Nuclear Reactor +text.save.old=This save is for an older version of the game, and can no longer be used.\n\n[LIGHT_GRAY]Save backwards compatibility will be implemented in the full 4.0 release. diff --git a/core/assets/bundles/bundle_uk_UA.properties b/core/assets/bundles/bundle_uk_UA.properties index ecc5320001..7a85ec0af1 100644 --- a/core/assets/bundles/bundle_uk_UA.properties +++ b/core/assets/bundles/bundle_uk_UA.properties @@ -492,3 +492,4 @@ block.liquid-junction.name=Liquid Junction block.bridge-conduit.name=Bridge Conduit block.rotary-pump.name=Rotary Pump block.nuclear-reactor.name=Nuclear Reactor +text.save.old=This save is for an older version of the game, and can no longer be used.\n\n[LIGHT_GRAY]Save backwards compatibility will be implemented in the full 4.0 release. diff --git a/core/src/io/anuke/mindustry/entities/units/FlyingUnit.java b/core/src/io/anuke/mindustry/entities/units/FlyingUnit.java index cdcdbf4717..1b2df0d94c 100644 --- a/core/src/io/anuke/mindustry/entities/units/FlyingUnit.java +++ b/core/src/io/anuke/mindustry/entities/units/FlyingUnit.java @@ -23,9 +23,13 @@ import static io.anuke.mindustry.Vars.world; public abstract class FlyingUnit extends BaseUnit implements CarryTrait{ protected static Translator vec = new Translator(); protected static float wobblyness = 0.6f; - public final UnitState - resupply = new UnitState(){ + protected Trail trail = new Trail(8); + protected CarriableTrait carrying; + + protected final UnitState + + resupply = new UnitState(){ public void entered(){ target = null; } @@ -40,81 +44,79 @@ public abstract class FlyingUnit extends BaseUnit implements CarryTrait{ } } }, - idle = new UnitState(){ - public void update(){ - retarget(() -> { - targetClosest(); - targetClosestEnemyFlag(BlockFlag.target); + idle = new UnitState(){ + public void update(){ + retarget(() -> { + targetClosest(); + targetClosestEnemyFlag(BlockFlag.target); - if(target != null){ - setState(attack); - } - }); + if(target != null){ + setState(attack); + } + }); - target = getClosestCore(); - if(target != null){ - circle(50f); + target = getClosestCore(); + if(target != null){ + circle(50f); + } + velocity.scl(0.8f); + } + }, + attack = new UnitState(){ + public void entered(){ + target = null; + } + + public void update(){ + if(Units.invalidateTarget(target, team, x, y)){ + target = null; + } + + if(!inventory.hasAmmo()){ + state.set(resupply); + }else if(target == null){ + retarget(() -> { + targetClosest(); + targetClosestEnemyFlag(BlockFlag.target); + targetClosestEnemyFlag(BlockFlag.producer); + + if(target == null){ + setState(idle); } - velocity.scl(0.8f); - } - }, - attack = new UnitState(){ - public void entered(){ - target = null; + }); + }else{ + attack(150f); + + if((Mathf.angNear(angleTo(target), rotation, 15f) || !inventory.getAmmo().bullet.keepVelocity) //bombers don't care about rotation + && distanceTo(target) < inventory.getAmmo().getRange()){ + AmmoType ammo = inventory.getAmmo(); + inventory.useAmmo(); + + Vector2 to = Predict.intercept(FlyingUnit.this, target, ammo.bullet.speed); + + getWeapon().update(FlyingUnit.this, to.x, to.y); } + } + } + }, + retreat = new UnitState(){ + public void entered(){ + target = null; + } - public void update(){ - if(Units.invalidateTarget(target, team, x, y)){ - target = null; - } - - if(!inventory.hasAmmo()){ - state.set(resupply); - }else if(target == null){ - retarget(() -> { - targetClosest(); - targetClosestEnemyFlag(BlockFlag.target); - targetClosestEnemyFlag(BlockFlag.producer); - - if(target == null){ - setState(idle); - } - }); - }else{ - attack(150f); - - if((Mathf.angNear(angleTo(target), rotation, 15f) || !inventory.getAmmo().bullet.keepVelocity) //bombers don't care about rotation - && distanceTo(target) < inventory.getAmmo().getRange()){ - AmmoType ammo = inventory.getAmmo(); - inventory.useAmmo(); - - Vector2 to = Predict.intercept(FlyingUnit.this, target, ammo.bullet.speed); - - getWeapon().update(FlyingUnit.this, to.x, to.y); - } - } - } - }, - retreat = new UnitState(){ - public void entered(){ - target = null; - } - - public void update(){ - if(health >= maxHealth()){ - state.set(attack); - }else if(!targetHasFlag(BlockFlag.repair)){ - retarget(() -> { - Tile target = Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.repair)); - if(target != null) FlyingUnit.this.target = target.entity; - }); - }else{ - circle(20f); - } - } - }; - protected Trail trail = new Trail(8); - protected CarriableTrait carrying; + public void update(){ + if(health >= maxHealth()){ + state.set(attack); + }else if(!targetHasFlag(BlockFlag.repair)){ + retarget(() -> { + Tile target = Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.repair)); + if(target != null) FlyingUnit.this.target = target.entity; + }); + }else{ + circle(20f); + } + } + }; //instantiation only public FlyingUnit(){ diff --git a/core/src/io/anuke/mindustry/io/SaveFileVersion.java b/core/src/io/anuke/mindustry/io/SaveFileVersion.java index 48ec0b58e8..4a66242dfd 100644 --- a/core/src/io/anuke/mindustry/io/SaveFileVersion.java +++ b/core/src/io/anuke/mindustry/io/SaveFileVersion.java @@ -15,11 +15,12 @@ public abstract class SaveFileVersion{ public SaveMeta getData(DataInputStream stream) throws IOException{ long time = stream.readLong(); //read last saved time + int build = stream.readInt(); byte mode = stream.readByte(); //read the gamemode String map = stream.readUTF(); //read the map int wave = stream.readInt(); //read the wave byte difficulty = stream.readByte(); //read the difficulty - return new SaveMeta(version, time, mode, map, wave, Difficulty.values()[difficulty]); + return new SaveMeta(version, time, build, mode, map, wave, Difficulty.values()[difficulty]); } public abstract void read(DataInputStream stream) throws IOException; diff --git a/core/src/io/anuke/mindustry/io/SaveIO.java b/core/src/io/anuke/mindustry/io/SaveIO.java index bfe652c384..98ed4b251c 100644 --- a/core/src/io/anuke/mindustry/io/SaveIO.java +++ b/core/src/io/anuke/mindustry/io/SaveIO.java @@ -3,6 +3,7 @@ package io.anuke.mindustry.io; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Base64Coder; +import com.badlogic.gdx.utils.IntArray; import com.badlogic.gdx.utils.IntMap; import io.anuke.mindustry.Vars; import io.anuke.mindustry.io.versions.Save16; @@ -15,9 +16,10 @@ import java.util.zip.InflaterInputStream; import static io.anuke.mindustry.Vars.*; public class SaveIO{ + public static final IntArray breakingVersions = IntArray.with(47); public static final IntMap versions = new IntMap<>(); public static final Array versionArray = Array.with( - new Save16() + new Save16() ); static{ diff --git a/core/src/io/anuke/mindustry/io/SaveMeta.java b/core/src/io/anuke/mindustry/io/SaveMeta.java index 5fd655f6b2..f782e74db1 100644 --- a/core/src/io/anuke/mindustry/io/SaveMeta.java +++ b/core/src/io/anuke/mindustry/io/SaveMeta.java @@ -10,14 +10,16 @@ import static io.anuke.mindustry.Vars.world; public class SaveMeta{ public int version; + public int build; public String date; public GameMode mode; public Map map; public int wave; public Difficulty difficulty; - public SaveMeta(int version, long date, int mode, String map, int wave, Difficulty difficulty){ + public SaveMeta(int version, long date, int build, int mode, String map, int wave, Difficulty difficulty){ this.version = version; + this.build = build; this.date = Platform.instance.format(new Date(date)); this.mode = GameMode.values()[mode]; this.map = world.maps().getByName(map); diff --git a/core/src/io/anuke/mindustry/io/Saves.java b/core/src/io/anuke/mindustry/io/Saves.java index 4096974e97..0fd1792034 100644 --- a/core/src/io/anuke/mindustry/io/Saves.java +++ b/core/src/io/anuke/mindustry/io/Saves.java @@ -144,6 +144,10 @@ public class Saves{ Settings.save(); } + public int getBuild(){ + return meta.build; + } + public int getWave(){ return meta.wave; } diff --git a/core/src/io/anuke/mindustry/io/versions/Save16.java b/core/src/io/anuke/mindustry/io/versions/Save16.java index ece1178776..afe056970c 100644 --- a/core/src/io/anuke/mindustry/io/versions/Save16.java +++ b/core/src/io/anuke/mindustry/io/versions/Save16.java @@ -11,6 +11,7 @@ import io.anuke.mindustry.game.GameMode; import io.anuke.mindustry.game.Team; import io.anuke.mindustry.io.Map; import io.anuke.mindustry.io.SaveFileVersion; +import io.anuke.mindustry.io.Version; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.BlockPart; @@ -153,6 +154,7 @@ public class Save16 extends SaveFileVersion{ //--META-- stream.writeInt(version); //version id stream.writeLong(TimeUtils.millis()); //last saved + stream.writeInt(Version.build); //--GENERAL STATE-- stream.writeByte(state.mode.ordinal()); //gamemode @@ -175,7 +177,6 @@ public class Save16 extends SaveFileVersion{ } //--ENTITIES-- - //TODO synchronized block here int groups = 0; diff --git a/core/src/io/anuke/mindustry/ui/dialogs/LoadDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/LoadDialog.java index 03fb0ffdee..529c48ff73 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/LoadDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/LoadDialog.java @@ -185,7 +185,13 @@ public class LoadDialog extends FloatingDialog{ public void modifyButton(TextButton button, SaveSlot slot){ button.clicked(() -> { if(!button.childrenPressed()){ - runLoadSave(slot); + int build = slot.getBuild(); + if(SaveIO.breakingVersions.contains(build)){ + ui.showInfo("$text.save.old"); + slot.delete(); + }else{ + runLoadSave(slot); + } } }); }

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 delta 135470 zcmbQSl%szs#{}K_^n%#yrT_M(8~yxN_rd4aY@dIVeUjX_J-#t*ZsDi-d;B>M}#J zoRsQ0O?P!r>s%3QRbJNRDRT`|jjq_0JqVeRY3B7A=k1TUDCV;i5FrLq(|5MJaIml@k+{y$^*? zR`b0S*vs%>VY}R^w^9df4!JyIWSFKGJFCL}@Skc%20lhHornjbDqs$aN$xF^e1?XN zVvpbRGJKF|@cZ*|e$A(oABwwkr5PLq7(9&rGch16ah;lS!Kz-0p-0l#ZMNo&-!CsO zU)|Qm_P6SUPvDsuhR*%6b~P0aj%sork3C7Wiut|lX1VRTrb6b@9p7{>IQ%_c|Hu8s zX5+SBm+k)smtK!8&s64QU@-P(Nceo}S;xffos#vn`;Q%S+nN>E{JBo-asI6O=qJ1k zO)VV9^K$H$J=2X_ly-Jjy{nu31BV4W*7hg!%>8WgUsYkjx^;f{>(9-x{8jkz=i%p~ z^8;D774kgqN`A3fN`dQ{o#vID#m|$Bjje*`|Nrycdr{2A>eANpJ8FM_Td6*;!l_r@ zK8{(c=GpmqwyW>eevkcnN_+hZ=XSo8uH9lUizOz1x3DpH0*CTM50zI!_0Q^WKH91J ztfz0O;k(GRkM6xvuHQL*7#b895@ywNIWW!6EKg;evH5RJ&Yj0Q(*DZ|C_F0M#Kh2X zMyE$r@~5(Px^|0i4O`X4J8c3C2d2E66|uO?PvY&Kc(%{+OpI-MG6(J58S@hN3I35t zI=qiL^K3mQ%YoN=c4l(o_U#wGB-hXG6IX1!b@e%WW=}l}!yB24;f#v}Iu53b%@3@y zKbq6fu+IFL*k*=337#X1H zXVEILvlEh*2|Y{AKHYOcM|AsF%UPzr$^R}pFwCfR5oE~V2nY^#W?*2`R$#7w%5`~1 zak1a#e>LCkPyHnS-C|pTEuZ&){!=_(u#nGcH-q>mTyCD$8>HDrBxagp)R*Pp5k2U1KA+{rn{y3g6U zoiCJ`jVGY?*O$P4+ix?%dE{(vNG+M2zi(zweZ%eZb>B9>GM@jl$1d#6(S?U=Yz@wE zeBP1x#MgD5 zv+h)6zfId(^;mIh)o;d+xxA*^z zzPdVmeZjYqe}8`7I&xr#!iL^tlWeQfS64V#40`GkT(lSr3Lm-1US8&V`8zw0#thZD zk`q`NW~j;CF27UQ&NqGMc1DKVk8gOf%@;AZ4t_%ZAMzrVk& z84nz{`(`LRzy8k?^{dP6|7P+s{CoHQpI_~(;Q2u;Oa;HUuCMjwl{WKvdv9;_-haPd zcW(Z@kKsUij0}TC;iHzzH}-$IXZe=J&&!QiYtDSNd+*GD3nZ%kzmET3#ldLz z?_>XCGY1A11||VesjG4M&v8eF*A_PF=5PM|TXFk1U*Bhoq9+|)($9Za{HUL~K;1!% zp<%|>f}MOb>fw1?l0=GXFdDxv*Zu;<9nAK zwu$3oxKaJ^dDicX_gO^#6ml|rc>Xx~G$X?m<^#{Gn>VI1Gzc;8$+>BC^~#kcKFY7V z3LhV;KI&o-om|t^Us!Hq+E|PNp04>}0vQj>#{a6_(3) zdQr~B>^DaZz$r_p)5SHAy)v$R_N_navhUd#7VO(+H)o}3+O)$nqk2}RupPLuF}d`w z&eH#n>yF<1@4?n!_K$aONy6=Ej10zp^$Z70-lR?Vc)Zq!p&?m@VTW8ZGsCX6EDQ_g zi(6N{qMfT+w5DqPmhINqq}qWe0_!vP4%bm{2P*M;qUl+%H zY<{00i^CIEhD&w%r&t&Sn|Ia!|F_|vjXy&G$K@Zh1sNF(e$M}KL|r8J^RkB5*Vl)K zvN6;*&Cp@`9I>PSO@m1P@^$-vB`^P3|Ll|K)lFr=91H>;zmKo6_hR5+SiqA|clwsg zV|@l_DRx5O>cra&2lm?(*-n4)ih<$um4gkUyj=I;!_@Uyln4kFOMMvo~;ZX2|=O_~Yak zVFm{V4xjn;*Prk*u&_YeKBA$2mRtWhKKtL#wnF}Mb5E<<9CWzK)X-n|W%0s|%j*~n z^f=EEG-4LdI;t6cAk6GRklbm8B8CIj zpKe=yHfESmDe3U9rdWEq79Zn=-sC?&x1EV!xX!+)y#84z!@SxrW$)jgZQ&I5s^h=e zWOv+7ZO3LWh6;HGCWZ`_6Pp;AzOWy7zD|mP!I9z29r5({rONgam2Z@5=V$#qd)&S9 zz`42Bb8T6FSlFmNx^dLIfq{|f*!(=k)BjB546s>t+(ai<^czzFLxTfD1Eh^N zk)1)|^Zf97HU}Ar7hDVsN}HSBJu=WY(B;SpeqKNQ;+eyLcBe2k6wP8N`s{c1&n6Q_ zpQ<@t%nc?^?nU=lF0A?QALPaHAckqfjxLu2vxOc-%=Bh#OD>E*{<$^LPG0G0P{Xr| z_wRW(MGIImGUPh=&$GGt(3j!tRR)c}^4qtZO+MZyD|?Scp#G-dj~wxEuMbQrA*avF z&tCrT_*aGpk1w!%_^|VH*7uc{866qe87^r4pYgK&XZy+Ls*EK(jiQW63=RhzVLoAK zU}13Bx;;%N>VKDA{dwE(EZgrCb@MIR`dH%J$Bl&n4UP?~SQ+ZShSx7m|GLU>-}k-m zT^Sm*GiDyx@wEQ8&*OQek`7Xb-(L?r&9Ln6pV>2+W=wrr%NWGOAk3mM`)B&*|A`lj zCfRhg*$A;Q1c)7uPx^QB&HlGP4?UZ@?fK@JRq~(bhZ+3$SpPlt+@8ykjmg` z@W1}EdHrEy#yO4x4_+>xf9>>!N5&n|Z^Dx#4`g3o_cAs9<2_EM0)hGjW)+J*9|pU{ z@1xiRmWr<{WUy!byq>XvnaRcKU-04mH@m}KPBC>IXJ%sQ`+_K+r!p{btjgZb$PiuZ z_}OxmD8rwh^Z!>~omJmu{eF+JFoVHa>$C5IZ=A9H`{$9|aj|vBSE?~IIHsk`aG>_g^TVg))ff&Ke*U>&3xh)Uisi2N z7#_5~w*NdYHa|Q2a~p#v1H)5kyJz#6((aVXJmhA0VLZu%ufKEIqNB&ZGco*#yHM2N z@w=Rnp{A|tv^3k%w4NFsMuw>j42QndpZTzvr=fwJfx(E0g`vYmX=1&HiqKOQhB?~I zZ+=#CFeLnW875tInu|e=|EG*1+cwH_GQ_m$ zpS`i9prK(>fvtN~q!FXnqpjcT9@ebml{TA_BJ&{Pj_J>Y>sH}z4w28~ouXg8{GQtKWj_YFX^0)ff!q*}wkfxW4bF#Z)$idb?w`{0t2r7#S+} zicNpH|JUX6J#nwUzP|3=x6m>ATK32LtDjA0W%%{wrLbmdOMPJv?+H$PwF&A`O)pzO|; zD$|YY0)1!ib~!ue{+fQpB!-6it(*(i2q}PieG@$x9C#QGyp7J^yR=#UvGM;uX7B2l z7*??}d?-j|*fDFvPPyZA{w^=D^;2bRcs}cy-sRa#Ust^;Kb!2ib<=~^$6tS&P3^Wx z<8qcvseGF@5;@M8LLsS(t4_7P&>Ffys<{xa#k z7MH<4J73m7xrWzFoB#h=Z67B8CqF=FH@|%U7e)n!fcsa(C&UyL>9i={@@jR!DV0bR)Gh>^M8q~l{+w-scc4E z{om5Ll1;f$Y67R3FWKGW_St)WQep`|(O`_vMw??hUI}1jFC% zxodUe&v8bE88vy&GQY($H+*Y9^moVa2u6lkYNCt(poNGJ{CXzg-uJ8Gs#GUQF%59;SdpHQb~$oJy*k4KfrcJC^Dk-)9W(wPGMr<4w%(JIf#JZs`Q@ez z6H0IH3u*hiSpILuvK4F$2H6j;hdkUG@%a3|H|MX&|9dFEs=wxma-{=<{A1Z@@%M@K zH-ecN_?=_(?3t!MtV~vHdjGZSwf6*8hBYx;*DYaD$g@1{HHD#~PH1(#xCox=Ax!Xe??-@@EL$;eX`L>ZIxO>%9Ede`(-Q_oq6q&oGH`De2>L|k)cJB>B>*}#-;x>gdZIK+p5SQ zVP9Cp!BBCYLz>}31H3f&D#P#~iz#E-w_ug23^N|j@nzU;!0;i|pP3;tzWmmYz4w3H z%0Hf;>SleyNKOB9yeor3Xs1IM!-nS%HrHp}>B-GHc4;+ZQuOcNRx!V)MzBp_Xt=rM zJS)SAKhgpYy;7!L%m3>~ZSk00_l4_FNPNG9;jg1T46+vI z!}%7d)$Ca*$A9Jos8HVNz_fXr{N^7u&+c!NQRoZ2?9iio?vML4#-5e``I)sD4}1xp zYwutGH{HRZ{$9kc{R|9{(YHlKj0~o~7PHldeU4{gFylzLH(y^+15~W>E&TVQ|KEx% z28Ua->d#MQwpz`i@b&AN4-!^k`8nav$JV|QV`w=4WBb;vFAtc#oqd||(2K>l?U_#h zOZ>Tirg8eKj%YUny%%3+{oZHazqk7Pv=kSHr22$w^5WUg&&`cvSn%V|M4v8Y|M?y) z413PW3mowIz1)D&A&9dfd8GraV_2oeuwg3047Ph$87`b*dhnQqA;g}+;OxHq5M~C4 zBQu^AEpXT=pzz_O#0d@tt6iK46ADEc*UyNsj{p0hG4Yjl{7OML*HCm`Anvc$p0TNOaCyGX7I>i(txD*dZobr>HfkFm#_R@!;~O>UACHmgF%2H zfb;Ww1_rAv$pb1C6V$jB&Gr?ZE>HYd%+BDk-TwEPpGThWFL>B=YfI+jS!TJj)@@zC ze*N{#KM|Y>*VaU4y}Ghek>SPt{rg|t-MyXf&G*;W{mmI=Ofn{z&5)Qa%24ync>cv_ z^P6S~{$gUN@3Ad@=5zDq|F>`7USTjaWpJo;h~Yf&ZTY-Un#>wA-=;G-EM;Z*vM)u_ z0g^my<2+9@2r(!a`=9-*w3=bVm-@||3i9p@3ufyuYcMRxW@y+m&00JCoBh_G-whsF z)fGSZcIErYfWz`j-X>4DRv>nEf0yy!n))9f9$KCF!~VQJyCg!P!Ebke^wx&cb2PV~ zo#EViJ&8Ak`O1|mD-JfZvvcfYS+G$gfs^4uBtysS{C$%B$)=CiIoz#_>t))glhk#F zSvEvT;5tje>1(-h3=OG_7Kg*(wRG1kgVKq7SNW%hVB|BRja_mVX!oDRt{OaD)2 zXt=t%`D(p1KPM=>a~Tv){|&h|>%RoUgVZ?L_x7P|El&-noUiu#ed{w5gURRocft%A zd3j+SJPZmSSQs2-7!1zt=VaUSt@Y(lX&&WwMjqXZh_s4;DJNZxJ+CXs9>6;9#idQN!t0Z0Xi9ogqS@ zL5Km;99nUFMcNyc8TGEb3}@9E1cT4d`a6}O;gF>?!+}C+77a#*)#2O>FBlmV-v0i+ zgHMm)!otA5%=1hPr*`^Znz82nUYS%~#vK=a%rN_xpCiAF`Sujf1_!DChg!Ll8{K&r zIBXalI2hIH{pLt$G8Qm0SjErz$iIS9p^?F=ExYKv?RSmoAI+7O8BQ48TR!C&S2qJo z4P(RGfAF9^6>#xSG-JcnKhkY-ng2`r%K!K8=1&#lV0cl%FyZQp<-CjxQw>i=E_9q# zD4Y4mGlOBqQqF{Mb_ScT@|*vGMga~LF+_&wf4T ztG;(x5l_v|4U0A#sR^W^>_)lLUi+g%T&j z38Mx!28SzVI$;r2g3sci%^_PJh5`-;$K#eP4;-JL{IdJZeg+0chDyeUIcKU@&AeRX zTT(BlW_NT_o7Va3^A>$v_}QxHv02`o9YTwHl1u8I{h6Ef^ql$CLtD+S+&Jki%Fa+Y zlR<;gfy*}SreOS^{pPFh+5hRfwPV5bs-GVuzJA+me6_XN`0Bc^mrG@C*u=`-v+{i# zVH}#9H+xl-m~SZev$G+*yu4Wln^+fqT(fpL_;w%;v_PO*P$)ZpoD7PqG&@cfzi_VspUG1F2RCLeofSh&_Gje&#Vg#?3u z0(e~PdLFl9!vRPoR>i=eaQNI=`}a@sofx8C_4Dk{{3pBlKNABB!xK)1CLT76(qARV zj(4oyUjIEd@j$iMcBl9D9zXwFE1vQ5J1c{P_py;|^h*DkBl z_j|t|dcbb?@m{^LaEVCIzCQK&H9_oeXCFVCqvq`rcmH5~?sdoCo72ySaeS_8pML%2 z2HSgQL^p@L?b}uG)5ds$?StRH&fMd8X4m@u{n4Xa(+{tZ-fb=PC+6>$sn;fT6@@Xb zU|^UK%CLxGfeLrS-Bb3y;OTrG2#6v9V33oGyW76!p3CAH4(OQXMeW_p-vDh8?J{Ig;0QQ* zp!kCQa!>-A!pxA+VkN-9qRG&pExT_sgTu5-O6?n$z+K9*->2;oX#W-!^DEfrUM zUz}mjuIvuJUG|KO!Ym)2|NZ(i?%;aquD~15&YwQ6TVLy{QolpcT)uza`5K!Ux2nzT zmb|=caQbQjL$397h!qewc*%(ZI`%h$NFlcPzWZ3dk|H$=y-%Mq< z{rzI0#$q6Uo=2*E9s>hID~Es?$ARE`h4btGpRZOA1;X6{YEnUto{1G;k*0y3(dN6{Fq$m-+49*do`1uv@B&|2%4JWarS>c zBg3NWE1DPnT)rUt%;o>xn*SM_8F#TP$h?~NbGavIx@*xq4u+gG`?WvK9UZ*AyFpoJ zCc}YEsi$`-GkDxqFsSEeo}bR&aHNNMGfTtynAr>s%TGQ2cKiOnZG2kH3K9>v8GfAE zKQr=xF{9Jrdmo}d%R4o+-Iw~d@@=oxi9f{)8j?eV9Nssl{k0T0U})8Utbv8$#1CnO z1uP7A%8oDaC}41KllNAgrO;r+C=kie5XqDv6TEv{%r_>6WuB_ZUK5wqKXGJmxbj0> z*+FDoYQt;q+dH=XRiCHB$ne!SH|x_I)`xZu2mVak`pJJXxVpK^!BBC$Wo38B+dX$z zC45=iSS<1Qa|CFbTyyS^^s@o!v+G+;cd&vQ#UD)n%yTpE7izG${Fa&FlFsJo;hYZl zBpEsvDEbN?n8(9VA;xgmu71Pn@>M(y=U5otvag#t_utys|GtyEzI<8q^Sav9={7U8 z-2y5=qqYZLK6rSsU1a~c+GFzlSN|$_uaC67{n0kfK|bBpEzUtA=f2d2r(3 z^vetl9f40J8UFm`V(?&PU=m)l;7n7WqBMiUg8wtZOuw8AD>iNL;F-Tm`A;QipwB}^ zsMMO1;l(rtht!y3dS`>G;usW;3e`6}WojszE~ry3%JAAE@5LpChO9iM6N(J&j89D% zpF}crJlZJ4kib@NWArEG*F?^S7aeL1zqiE~Pi2_Gz_8^1;hsh845vXYNI8ZJZS!aS zdl+S8ATV!v?mg**3qQU-H2nNr5!`+Ec%nOh>s9OMqoCnBTYiQmM*E*KFr2Qh{mjOY znf~GP3&sbvyuGEjz0b@^{1^KF-`~R@r!YGFj%d<9Yt+EP;3MGhn}sE3?$=z0Ma!0{ zeX3ib(y)KuqZudup8bB1Hz)UX#3?3?PQhaKDKYzZTtCj}5ToWG#^7+1LFAcH!|SMd zo<{#C3N<`BQ-8oGeS$K>i;1=jU2Spk$LrmX%XA)UOghgx&oDt!`d`e7bm*{lVC1p= zw;USU6T>B)862K6IJjRqsGiDDVefX1!C~smSN8+2>+i{zVrXD12wv`2%GB`dSCwhS zhX;Pl49Um)LY>=q3Yi$@9DeFJ4LfXR~Q;z;nKNrTuAiywT z5obcXo7e_XW`3=IR;OEwDvvGmoqdIw-$p>ns^r)5tuHk#SY}w4uj}iTHh*RCJ@$C) zxZB~8Az%cQtXeOsa-iyL@8DE?^7$(>IHyp57zS$U@hlM&%nf?o9Wny6{ zk!5ID@#OjT?)v9Nr@2mi{&lzXdaS{JL52i9W{*gQj)TqYuNyL#=GOJ_=-8JnjDP>( z$A3LpVaemO`EMOc(}|V+E%;?e%Ad0a^TqqWAN!U#@j-vO@$YngvzHqf8Bw znbn7{U;b8qrF!N%ZpoUeI~EKL3e4}A7$k(#|5blo?aRurVHeM|8KMjy67SgOtT%hR zY~peWyJrjR*1!JOJ)vQG!iK-F3D8MC9utD+ZUhzXf(!-U@0LHGB^7@%fdH8|LNbtFz>of(VlJF%r3D^aAx4LW6oz{ zXkxg^#Ne|yzqy`q0z<2bJB_4~fQYg0pi{h!CHZY20II9z80*%QLV5E1<0^pAH_K3f#Z{xPn7FSLoxtMC2& z{r18P1*QM$%S{=dI2^e4<{VGBumc~X1<#3FpkU%-j$t@(m04k(VqA={10Tz#81c(5 z)EODPckO3r_{8YI!t!Q*X~0b{MuW8PTlT67G%)@C5;6ODe(0aVXIJF}p+g7~|NYGW z$ucpm*u96L;T=E2CaDJ^3<~;><1!fnu9fR0`kbuasl(799l1}bVds1ky|YFQg?FEC zToQjPmEq^(Ku?DHJzhG@GrpI4Ml(%F_{YjHg`L6a^SR5v5}*A1xo9JQ`rq5nC7|x zOMSu_80wwdcmnxtzf2HjcyQ-N;pDxH2j;Y~u3EP27$?IrpP7rMoq2D^@Zcc3d`Jh& zXMF|*c>#f0f?ApfeW#W2R2my4;AI9O45=3D*r0_|NZ#Z z>&&+;o)yR2xQnwgth7(&Q@%fw!68&Y<_A;55}|sA+Ps^aQWpv?dsd!y;pg`JzjMMb zR2VP4yY;;EgO6^_BK$TV8p35Nf0@dL=d)O?*f#&$*~e2}z31}UxqLw#dz#k24T^tF zJsh2nK3c_PwIMcV(~34e*;Qwr=g$1n#`n3B-?sXkwogF9Z8w`g9}aUVN;16LzdX{_ z_g@7Y!_0~^uj<#!?%lT}oI$c?nx?=EhKBEK0`R!YGXKAGDr-qNL&JBIo#(@odH)n^ zzvE>*F#q4b|BIU$7|tENE8Z_#es|ScmIcj>4i_956wa=x*=fsAbIi(h^J#vza8M20bPBt7b#^8IBI zL&E#Z&r4)%D&uVbzDfQ1<>2O3wRbO0>S=J!ZC|^WX~ntu(zV4h$97B;6M@ul$M&C2 zXIALF&cnvAD>cN+^MMP4!%+qYfhju|Im}A^^Xb=K2L^`B4`vJw&ubYQ)*2?HaL9}Xrbvz-wNjh89Ii4p7)f_n{Xci4Hr|t4Ovi(V ziGhQm!8<%I#60g#2XFLwhI=dpci()?y7^<;2}@xH0TzMIdQio8n%0EK<+Vn) zW<31=@B9AszrMU=77$SI5xDSsVW1+zg5wLH8-CAC`@eMlp1`6W{h1Qmdz2U$no>@( z1gQJX4{UwTxA;T-Kc$Z+{p&6r|8aa~_5H$?j1H0$4*c4Egq`6{hP^L?f`bT8T~5y3 zU7^17?P5($Ojg)RXI;J8e7H1Wc6>RDBg5vz1<-zK6&u5gJTF#;T|pu|MeS~u3=4!* zc|)(UY}m}`pe7K;#_+*5-YDe0jLEZ(26Bugc4V8x|aY zbiOw^L`We0-_LE4Ob!lB+bY={K!c&A$2Q$MyTZS4&hVb8W;~zY;U$J_1u(Eq!eMsx?Y>SXTH(xGmlVxSt z_v5I3)z=Fi$**kpJw5Ox;H+PlN*S9R8YCkb4qSS9R+X2v`zCx2 zh(Un?G$|GEaD^AR3F7`Qu?{rUFw;2w%EQC$S1&GhzuGCRer09w@-G|H&&>%_@0STV zUS7X0{C8B^%NzG%)_;3@V@=;S`7m+)IGrv*?+B?1E}bX#LNi02&Zj&cv{Jx#PirR>!UDZO&GN1}`@I+gjgL znjn4rOjU$j1AC$DZfjwNBQ}0V867UY7nj zT~PK`5#nSJV7SoCaKQfGWBXrbQg2Kc7=lAs7|y9)ug?(cQA&(7<7L!RqWo8Dh4qF?PeQix)pKFRBGehU=8}_B&>sOtNV>4LE&cMhZ%yPkw zk#TyVnK9d$$Is5#vNK#>=KEMcL8#yER|aQ>V}NJF&pnY04wsA?`0f7`=rAZ;=4WV7 zxcE_zfnk*ubA5w!o$>1yX2!b`1-ZSMKbXasm>_NjB`9^qhWcO2>%BH#a#+sD@P%c= z+S+;S3>AOBUJrH+XJxRG-LW;)ea_GG%nSzd?OE5(2 zjDU_EgT}fz7(C*-)+;Tud%dYG;`^PL-*-~i#IT)VV811B_`?2=$E5E)n}6(B=A|W` z6LVO_e$DBB@%5m^f;)-N&djvh(3`xDr_JKCf9cJT_POQ^A3`twsXlt}{omcQs{hMc zF{k|d^V4cW{o7l|v-7jFFY`Xt9?rnU(c6D%9YYmDIC$ z0hgESUu=A9`dV-G+}-g(xvRge(hPp>y-8lb5?`d z@fGi1m%RyLZC?NQ$>;DZM_=0q+1LHf{X1*joeEd^_>GCpvux^Xt7hy>7+4Mq$c2W0MT3gWGJF>ejSf$wz-KkVFKviFPOH`#4m2Udxm_F`~|5k6p| z%E0$n{)Pjz#|#?tUnRis=i~q1&u1>uW@cE^nRm^0@=x>EXL3uQn=x!ym{4`%!v8nv z_J?x?#E%+w3NW0mpSRGz@umge@d7${;jWlE^o>Z@#h79XzW=J)9h9*Ca-F=yt?KiIrT^qUCeXiBlrjK|0S66-(b2($UNhe6}`HQVitdCAj z)!rrfY+LSayPmbySGJw4zPhcqJv90=pK0|mA=8Wl1*Un2SWL?wI`K02#qL^C_hs_@ zBeQNFcJCLv_dMqB@8c%_EzFXS)${z__uF2BFOFW3G5oS9*YIzxb7firk?`RK=VeRqZiZ4Qv=kkW1OMhSTc~gAecKKO*S5-!boxGgFQc7u= zCtjrPIC#QtTGeBwzk=72}!3J!bBKU{_JTId3Nu4 zac-@RJVQgpx1ZT785?#owQ_oVthZKYWbojGq##I^TVcm=VZXl!!xF>)GE4=`ck5m# z%&dIRA7d!3z`_u6y{k?C?fy1VJ$N&GBXmT!w@r2kfjqF)>6aefaT^ zc_$+Si>a23SI-Zd+b&FT>t6G$w@p6 zOC|}k?5Oydbd{-Ljq#7vfA6}P88U;u1uy-s-s2lTi3=NY)@J3ztxP*JGyGC_ed5C}?pIFs z^1nV{_O*V&ecj~aeWK77IA}b9iD8!*!wjoM^Z6u_jE+V!Fq~bKpT90V{r2~#oD7HN zv6y^**5To`pOc|tv-2MRDjPmK3l@i&3{A`ocE7uBmCyQX&d89;DDryqTn2{sU*B%O zANGFF=e}Q0_3KyudtU!fz5dVKrd*lBmOX`2-JZ85%%7ljUZ}@qJalE`I;Ix-X06 zS6+{+_LcUC4&L=^7Nf!cos11mD?Yn_d&0%=sHav)LxES`&L-yF1O|rsm|MrI7f8jg z;d{Wukn&6O?|d(Y6+8{P{pqX>dtN@Qe!q9Q-H!*&Lcz_+7UI_x4AmI|{FwJz1izJ; zti-G!8sTu{O51W>@ejJ*1*r@x>X*nd*gfU9WNgs6e)aiZcFA?&N(VeO`Z+#RV%WmP*000^>$QPG)-&<9dLV;CB}2lGGu&Cv8?_VXT3uZ%U;QjSE9>;{i5qQq zvNSCJ_xV_ZoGL@T;Ntg83_MI0j185&2Cw7KuocA3)?#CLI)!1$+>LtM*&X!uD_@p+ zE5qm@^>`j@#)>Hgu1hPGxpxaHIKhE%D!q9kK^FFZM9(fIa}Fz`b-TB zfd;=f8m68SG1i7HxdxRkM`r%_a&DN%z@T={dZprlS#Rslw=*Ps-&nro=lsi6%0}DN z%^6Ls>!f=by=O6m88J67Y5qUD<^P9>R%3=!x{N1E{u`a$e=0NP9)rTgGtU_qV%&D! zS<~Lf!Vu@HWX-{l0cqP5_8eTLw2y(|Cd1aPTVY*v1_g!#%nU3{44)VoCQJLT__fTr zfq}7pQNo{6ow!oJtJ~xHd)|7TW-#Iqh-4Eue&NXJbV&zcrVk5rn9uyVJ&o}RL&G}V zuZ#;``+WUha`rI;!_}|9T~5g{WHLIa2E9HVc}@T9<7I0v>YMMqZ_aAxE$kp4A#`NP zk@BfR4XYR#j+^U+vM^MzG35E@N3T16-~hk8osEM4N4-HaGeZk_)IteVUp!)TIBCCu ziD45XgT$4#qqjreHa)(b$YMC~IlX^% z?CRNvdgM+qEI2dwaCF!zQSR5J{{@5@COi~JDzBcN{l`94kMqSjhKBN$`7xUosxU7o zeS2_{T7#bR8)n9ev$pIEudl7;PG56Yq&#}U&-(JSMUy9RI`sajcx%`9FlLeuhr{bC z<*vVsB|MTwDwAvvFdmrFc6ZkvraT{*~NL%+lnZTnU9mWMgjPe(MyZSTC`1i>Ed)4f(oEkgj^EY(N zh}gehMf$*;ulYHw!4YsJH`q~^btHbX&==j>hgRTuXx z<=^WlGHZ#Hcl9)eOP#{%Tu|qzFf4F-`M=ZH7POs=;r|}J*j*u8w{2^CcHd+f3!_2W z+gn@17!tUn*Y1%_Id-vSed0Hht!vkY#b)J)h5P3RJ@)i>2?O%rTqt zEw1%%?;5yo-Fl|~kMlXv%}fp_dus0)^)}Ar{or!g)HLy;|C-JJY(5!zXeKyVDky$5 zEHXJCw|@JXFaHjF-};mxA?bcNAA`cS?z(WZFZ*r^K4oHH`5$}v(xpXVYokPC1DA4e zT%N*kX12NiKTF6g6sYXK|E#{x>ed}_sMd3&Fdld^`R8*62Xm&FJr)aY{`X>FkSKIu zU}(r@U;d(Dqj~x*jRL*An5|3<^LP!Cu00iC@QM57cXCaAI0M7eJNBou&wbo!CCYGQ z&$1ier(R&Wu$*Nxc*n+p!NHZ0;q1R)=6VM$ zMu!W9aa;+Red3%tf)=GN_MryfT5xp{&n-oV;1W|9r=@KItXR zUQG9H*!i^caugi)f1YW>$S{RjVV5EUN05Z!`_iY1oB3OAzF(-!;Gme$)Wj}d6Hxno z_x-Q;{J9y5{?)!^Wl-~<7b9f%Jg8=#)4rrE!eySJZ}!|4CEnRT~&>>U}b+UGGmFZb8}doDI6ski>h z{kzMbM=x$>blBOpj)mcaoj}@tP=x~CLUaEw9~*;KUZ>skpYB=a=06w`zwFw0_WZ>g zTucoy(@nqB+*!aR$>109z<6FdLxsbWCD-FW$^7^y(zDl`;lK^+_j`ho(tX2{FaJgM zGO{onVP=p$9sX7?a$er;`giGTdSAvon;;c6Q}g#Ww{UGW8!<)(7G4gf88aETMJK$U@Sv%HA1DT~5p z`8`Ekw;TC##1}eneV(zZK5jpg=G|Xg_kK9U{p!OUz)n(@m`0B_f*{zgYGhjJU)8yoim# zbLHK1tRFwpY}<76mo8qmOs$21h2g@Je#eGM z3Jeb4H|wfvxI=+ay)+VEBzz`a_jIZW5b#Cz6=N23s38Ae^D=4!N6ejxw+kTskHl83cB|KRH?bbmmVN zV@SzmlBm`#W@FGg{AQ0oTXT^q&lzoI231D4NaPlHM<&w@OBRM2i7kJo?9>T;pXh59 z^LrWxBg6We>Wlr17#WuS*v-K3>hJtWbH}%Na~HlpzL|AGJ!eA^!@WQ2of#O+|1GoA z;XI+tyxZ(jioqaN)RFHK`q;$?Wy zCSUmH$HxNs_08b<(Zdgpr}0HFI3%5KX}SOO;l}0j>!L2^*qi_9NLRbRs`=Z`$~)|V zj0^!Mg&I~xux+U4VR$f=@zHEXhK}OnvgI1HA8v>5LjJ12aAB?kXgw}y*z@ajhBf#9 zW^phSoXvQ4Kj)vvOa>l~2u6p$f3E(T_5S8&cUkK)9h3i&j0~&{4D;sa?QHzc!ccW) zOXg*z#M5#sSQsp-zGU2Z(4Y1_@l)a-!^iyx%p~ij>f3)S1pWE@L3Z{s&V=cl>>4jl zXzc9!5&QM;`}+8aH=noNzbFs@9g(x;VJJxzc<}Xn#!iL>n-}k9Y%tBf_M)LvO`<`< zx_CX`bvvf-8^JS}?KdS1d^s8Twb);%sWQlNG93E--twC;FT=4jRZ-3i|B4s$Fx=}9 zd{%$0zDwTPn<33KFJNnNxmFYB(SY;w_P^o<&%K@aa{0`DUWNzLwgn!V#KPe7Ip3k7 zYZ}9r?b|PZs(a{T-*m>F)jqt10kq(J<-f8ijI($5{yfjb5cIG1Tc7;D5A6p3PZ{2Z zkH#>@3b8EtGrYU8QJIAy?W{e2T@M4pq048E@4Kecwo53vcGr*JIv>9u z@841%xqY+Swd+#l@pH6i)x&lf1pO7h`8546D?SaC5~=`_Q) zx3{lnz2ap0p~oyywlsg{;sCw|jWkx5Y5!gyI?b?ZN$K%oFK@m_3=H~r7o;-$Jg>mX z&@;Q%BuC4mzw=p>Z?=B*-+#{A7{a6s7#nWZ|2WUW&>Haj`+NOLoBm(_Ll_j^)!f<1 zD{Ypc%R*8=AKht`CdNXKbxT;jQar-i$)rg3j>4f^C-iIhFddFC3gk=?=@<=a6`HN zGehDx0R;w!r3}~k`~shs`~QAheu|GVX!idPA2qle+#g1qdS9YfdMuLhz~$%n`qnTw zTo*Yumx19c!vZ^o8dZkiGk@HZu5I#;yS^pk-_@zw;UeZg%orR3WmYS*F=%*Sw$Bzm zz_Wk4a6S8>Ba01Lh9;!8|^ z?wqO-vVz?~4Rn6MQU(@=yxnij5OK5lX6mKCte`b+3%=A{`CiPBVEp%w3?$Ig)O$(O_7xyBF6o~LIS7#Zf>v|Re7I&|4be);1j|Ldpi z`p?DOu;9+`eHD*ux7Pmud}W3Hn%n%wdNV$zFYL4ZZ2a7)L674BH^YV^hl>~3)mRM9 z+V7p0eSO_e28Q*i48ec=zP;mTD0JpO93La9AY{>2EF+<^hwpvb8q4!_%Ol^E{5)a*|Egtr4a^3T)?;K$>t*?AmWLkdn^0nvNZ1wAHYVHS2GD|xnVOsVk zf_FJPpUjG?udkLaj^AIG_2I$6UlH@|YNzbn?)l9xfV-tW8q>+Spf|LZdTCGPmC#jUHW`%6A;iGHxn|39Bq+pP6BJY--< zjM-7(xcASe(}Elq1s#-*-I_EHwx{loi?dO#+YG`LdC7@ z+p>%eW;Cn#%=a%7Teo+i+V%Gr zzi;e4{9Zxku;u5S>gQslf8X)!4-XDr?5lUcgeh%)m3cz-kq*IM)66$RhA9`l<6zSI z)bCx+#!%_z&&il#oW$iIIyeq^`Y0>vaheZ@!sari`?5W9<$vi z?D(0VmBq!#P(N?FGsF4&?y0(m(`MDDs0lEDW|ypw{s|Xm_^{)h9m9d^9Odi`6I7WG zyx#gHh2cQjL*_0?@AYh~&g+?P|B+!e>gsZ{QDAU*{Im8<;nDk!e>QH3Tz-AR7m%|n z)_BzXzJ33iw5ojJruJ}VB?0jJx)&;7@FoRTJtO}&C>OfsVA4t_VyJelecU+dwvzE1Jy}f^U%lWOjx6Nd4Z_f#_Hd`CI``Y$ZbEU&q%g(<3 z`hZ!V?bh1Kw`yN3xK;melGT?-rB~}7n{NGiW^>ld;OZ-9p631%``x~N|02HqKQ`UE zQWtVIW{1bu+CPO?_a{iC>&C1wdvCvh&$im{c;7eWtedw=v!2Y@_-ftm`uAb+zb;L` zGJXG_)URvvYrV}i_Wb|%dv0NQ;-B(;pJl(E&94i-pHzQhf})A^d?|)KUoLq+eE#su zQ~o&&4gJ=Ik6eyy2v$3AWo7VQ#skISbx&$+;u#uz7{0t}Xq<0fzi*M-yc=eqK_8B~ zdB?uIzaMXY)R&ndXPG@WgGJS+DGc?X()?Ph81F)@Afh{Oq2Y(#a2!0E{nmp z;wgsA3Zaz_XU;2r+$F@|V6!!GX`gNO^w#4We2N`dLM>%&Tu3ou4QEIr-=Iy!po_9z4Er$8qK6^PtUH0vhZIYIZhp zH8Qm_Ga1+zIBxZ=3z$7Ga{Uhr4oTJq1_o}{`o;1RflLfZJ@o-Rd<;z8ANl*=-rFA^ zd8RI*1-!80m6O7P%a{M|R%TdGKWkSqr^CI|3{vyYo<6#vZXes#HJ2P67zG;48G5*- z(i@mtU+6I$hzYv*{nm`fmJALd6`@Tg#%DZ_t-8G|DE!0sIynY~jgfIPjo_CHe zRv9vHf3v(xmTR84bmrIO=l`M=&&B>-de6SdwLkXA^sQY%SYTQ`BZI@on3Ine{*?c_&IA-_ z30E#Ax6LaDEs=HneS*covVo03yN;V-+Wvh?7C%R>?0(-o|LVu<{r_Gsm~?R_!vS?4h5!!Jk{d5}oPRPg zDZ)Ozwq3WW{wF^pgZYl~JB98!1F)Bw-@EIz z_J8cnuVCHp&(73vWwp?eKX%q3J$dX5MG@0BOghalZANzVlP$X(mfftl{rdFJuS-}N zI%f)>7BelAW)KKo_ORI3sP}gLm#iZ<4*%KhV5y00lkKw|kQtyz<-_%mqt=aVUN7~k>2h?oY`rC85-q-*C-N?+egTdhe z3qyWv(NqSOd8&|wAS%ENrtFb6BT-fw*lGB7^=kBb2wk_^!8^cvLhKTQ(f8#FCsOMwU zVqmzW=PHrH!0?ra;Yzt0!-CY`XI8KSR@i&Wwq40d@!RwP*7-|$DZ`DlmS}%*a;HFDJvzJ8Smr z;M(J#rJg6h@sFwS|5!L>W3Fl=IAVAwYQ)|%sYiqG5bVrOP( z;AGsC#_;FQ*I>_Q_ph=TKw|%{FiT06R&F>ugX`z}Zy6XQtGHGjEq^=vz=3Nw?S0t} zB>#1*l{oX@*Q|Oz#uE?pa_eio6ctj$Z0z#ZwlQ#DiwWOW$Ee2gLV-a*w|qbMrW*g^7T5b?HI0pH!FK11AJ~t2a5H!@-92_uLHG43 zk;*jQCzAWFE3V()8)qZ8I}GET7TH>p~+()_;hrJob8^?#{b@-z7C z{#=<;ECG5An;3hJ7tSa%-unMT=k%$}3@$;xr*C`LnZmVWUir~3(V5;188028IS)+o z_04*@@Yy7xhPmR~R;+z1pT;0^cE2c71w%c9G|PqKhGv`$3Xr;vpP6y4A|u09PKGlI z#cyscS8(o(>}O#Rn9q1&cYWtUU3P{8cl07}Nii|7RPhANXYgh|c#eg^@s6Q9!-xIe z$0L-P89cJzoc8?xJ$GGM^~E3j8hPbX3=Ro1j~vQv(8!z4%J4!yhmE00hk@a=^Run> zCaK|!43Yo#g$giKe7|Ej-DT~3+wQe|8#M34?MqzT^wB-wmi_*xOBoEFru<0!7ys*| z^uoyFW{eC{?rnA}Wg0I1`>CtXs_@wR*z9<5lTXI8S%S{)&z_SX>^Oa+`Rv~x)f!w^ zB-F;;Gge*4nQ-w-exAXJGNwP86p@O zZt^==GB|jChjqw6b4OeZ31J`nz0TO*&hAQNXAo$G?n_C=cf+b+J{nuWo{%xuz3{w~3H>!Y@MwO7vhRu~`eJI5lh z{`i*6%S_>74d=I>{xGvQ*C%rAGyCjsolpO5)DB$I5G%^e(0lf@i2v2+FaJ+rO<uZns%3a$CP-SqjsRW2up026>hCc6>8g*F86t zUOsCngABu)TYnx-syo55fTtlTtih_dXr-$^Ru7$}; z7(aa_ob}>#{|a~gJ8Rq4%dc?rmtA?RUtZpc;R}m~8C!^{sp+i-<;Cm|xfyjB4oHbH zB=CZFXIHI}U|3Nh!c*nC_`~_$f9;DBo+xk3I-Iz*=ymAVg!dnR{ZHU}^H}ECf_wkD z<9|3g6h5z?Tdw6I#e7kcfg#z)kmG_RgTiz<2GDfu=HE}#7=BD)s9BWpXMg0Kb36?9 zs$T2LuHW}-)vl_qtE|f2%(%6`zJ7;_czA8?UcR(9hbz)Pt+mQ|{-SQ*p`bL-Awmqz z47R)s73M2+w;%UC_Gy~J`5f_RP6h>rMus>hhQ;TD|Lv^*xV)I-%&*{I_UBm)7BZy$ zt<g>st+f z-jFbT`=sTBAw$E#)xYXjNE)ZDsQmnF<>~4A?ehiy_#HBwqx^Zk7{d`qhAKJ3e@YWQ zuJ~S^JpIC{-Rw^8ObqcH49fh**DqaKlr0&)KJM@B8xz5hkOobmmO@&i&_~n}>hi|9$0KRbPgU{x=z16#_1?Oz5%C zRgPKBpfKTEzXHdDEG7oQZeoL6lC*^)TcD2j}i_d>&IX=$#|El+ZHW%A{Z7#O??M6yA#~ED(8CX~t zcqS~IrZeN|E5-#mwhWIH8qWObc2NrCH$7l`_ec2cuRo3&?@d0ubk{Dcxpf@L;RYX< zZhT(<{Nje*p9Uqj9=`u`?tRPjkB#hd9L)|42M)ylIT}BOfuZGby1~S$3_fps7!sTq zieEZtGQRjA$FPqmU!@fpI0SyMSASepyejo<;OjFr2QOqZ97z9h@=x%GVu|kG z5BBZTTK2zPK!KH^Bj)?<{Qa?_``Ei?*9$X9{H|nbP^f?Y>!%KzU7;xpf5U@Xz16eC z&h&!z@&}w@Q{WJ2Vl0+)xcucd14Am40KXn4?Y+Jl?n1V6)(sYVQ1 zd9l~0NI_Tea58Z0vSv7uJahRuv;WUtdonTpSYCK(iKoZu=WPDR7kynHaahKkq2bHK z{u-C~bMNbG-&b4ZC@82hXx!{$XyD6W%5Y*}*>+K4!wxG4|9qy1lacqXFetqHJty>h z%)gaZmH(DYG5F+G#)ImCZQ|a?#Lm{wU_6litMWrK3xmKuEi;BIrUx_`98SJBoKepr z`Jd%;JST$y%TLqu-m?$Ib8<}B{j+_VZeGahsOeJ~8h+K!3=WCSz0S&DaOKXI%lkMP zepu}De}3}8=fax#=Pv(#{FsA5;mr5b$3NCu?O*&i{r^kh_4_wGT5Xre&~PB_pWwc; zJb`=#CHJFJ4S$~tWsvD*sAXs{VHD|fS(KOZulXGpKA0h z2CZ+FU#!6FyYjk{zmUxHM&(e?k^-`lf)&#Ccv|Nr%g z^-~y_q}&adf6L^|c-+SSqmF~&mD<S>5HGiL-C`LA2Z04j3<(!XWcLKuOK~t1pT7Jv+AJoBV-hQq%cGBl z*`_QCrCa3~ESVd;me-d{F+^;8`@G9%zoZ#ML*GBv+0>`eYdd=om@=pZQa;&%!Q z!zq*h^_dI}YuO!~ zJ0Ftg@3*_RVS;p>@oL7l`Rr2}C;VLg-Ek(L$d_G-|9)<2pYrm6vD}L7jCtCu3|tFi zHvcQBd3w$~mf^rJ*&io+xz`Cb2r(pheupeuu)Xo8zMPw5!sd_fLj)3-8j2524G~z7 z_GSqOL&uVD?|<$z`*(d^>}AvQH;!#=5o!=%xUq@Bh@s($T7&bm={i@Rb2A#W9s6AM z@0st#$h#^W2SVR6G_3Q5tj=Iqe9Mzz!R22CKW5bnnSU|(b2OAusWI8&c%!4#%(nX` z{|)aiRmc>rXNcBx2xn(_>eR4@!QmPY(~NqSnRR)8auphw8!R{uFf#NmzFEY_piz2n zTh0Prh8xp(w@hUScx9jV|EwLulz06PZl}pJ#63R0y)u%ack%Vk+`J!^5P_9j8v9@K zFkINMCHA9GL-X-<$MlZ>dUt!h%FKFk@DUse3=S&wLY=$Luc>&o<(OR7A8S??8~YE} zK5Seczkl627X8()5B4YiXihr9)W8`m{*r;=LFvDgf9`K*$ul)%KEKu~pkO4%knnin z2gCml`2RT+ayTs9WXkBXr!TF_?$swtmJV*I_TK^w4!-tEtF<^8Hl1K&s9D!r*_*L{ z;X+46tsOPdLG{5~^)Am4IVQ*ZEA8BzmFpvsMhG+0{!LbJxHa<`mp*gET2+Rbkaa-6 z>ZF$SYoBE3;Fic@NxPDGq%gagA!U+ba9`n$t*J~C7B0Wan9<6>#Jq#Q>Dfw$1G7rA;xtv-g~cntxvU z+s>wbRu%`J_?ob_QQHFFX3t!@&HVc6P~X<)(R%^{Us<1RIJ_s3m9-@IxbC;YnkOF; zcQQ9L_WK2vCYQS>UE8sDRox-Ss}?VJL`78l=+4{fuUkKFyPt04l|}3Mr(;8^`kd@l9Cv#xcV=kLNoZ2mSl^T7w}#)R)0_-tE%| zo?m1!c&)`$oOS%bpXz#M2BTXxYwu0tbaCWZ`!6($E{(#;%~7#t70wLenoLZmOl@3++f|Nkm-9Pnlm z*u$x?shfu(;L_iCBk-~7XX>Bb@-X}-%g*G(@Zt1>@QL4NurQ>V&8=?*4Fa6~B*f70 zQ=P%Vd;UesSHb}?N&#!{eQm4rWnuJT_`v`F1OF|VMNb--8zw$}mc6guq-<=o^ z_^PIP39>M}_%Y|#&Q~8NUM^;4ILke4_2JX6O9U7;%+zIKFeplS^y1DE!^K6+49>sL zX&L?(VPO!vK984?VcvWO1~21!frxj8`V1SkmF8vq)BJJ1lZk=h+5J6c95KN$w^twk z-ZKB{WOu$*-@bi2@%{6oquqC<7;e~Eb1*S%*~P-xVDWjrFoTDvB~wF}9`g()naD9lXF*@53Kq9{bfC4 zLfcK|hEoz}g4n|O8H{(FG=KfCvgDdk!wvlz{{@%=EE$+?*GM`vBycluJpOg{@T)^l zmtTF^z5L2e@wgEEJ>PcmGPN9^wmxR1Tr^TEINU!W$6W7@T8 zT=53~CVuej*Z*F~#b(#jw9#Mm&%SB*coxCc`x2H;2&~8IYgbnNoTx|Jvska~!Bh0MXY`72TFgncm^<}yJ+BSaq zbwP>rLqc*GvQwzp-sF<$d!0dYE29cGZhlk`5oQv{kN3_>lbgNVMsy zhvNKN-5>j1F8=vnHtl}yKX1?wjKSx4lRu6AKmOg}VCn#^uVQd;|9AMNXvnmcD?@}G z?lDZ5&JdvN5H0L*k7I(%v;6{$O+KoUK3==c52rf3EPSz1BX4j0-#poMwVyYx;$ zw*U4~bfpp#qmA@$)z;U~TYr5088@*YHn}Czu3>|0v8=z*jvr@kD`kGGzq6xo2d}`< z-0U64QceCda)Y+$iI(eHupHQyJBOj6CTyNZ)mcLZh1|^tHyw1FrPh$E@GuZQbFRU`1-%4vdexxKOW1#&^E6n zcgk{w6K_7(+MN?;P?=HB%aG%3eChd-?-A~d4EKAM?s(Sr)I~Mnz|raYE5D|jtzH@a z`sxw$wATl|&8h9pdiRWV;%m>p?)7DFZ-sucQ0KQ>%D3g}YSZ9ftDki;gZ5JuoRisK z_qXcq{Kt287O!kPdfENk@>h%(LKzO!I)8s5Vezx1q9_0J^&BS#KIV#I2VtfHi39g% zb21iqOA7q?J5!q-#9z(H$RjbKThYzg-sIb2wfeNr_R+!zT)V|~GBo5dRXlC2Hx6TP z;FZf|I1nnN$N)NDd)Ks|;gx5SxEVC~nWiu^T$)k8BIDoGOsSRo=4buWT;bnv)Z|rK z@iI34>Y(=1KC_NpD(jo=RXiv2)5$-(&y}-+3Oe)eB8AB@3&M&dVUM)3Z=&*$o`t+;M8Hoa|6p2Wal{QcZt6NUqa|EM!DZ00#o zrpL*!B+_g19LZW~h5}RbHDU|}&(28x-BEh{`O3`c(pOr(r@uU4);II|WCjMN0%o6YK_k1|2d;fm4XgR)H#5EBPFTI(+>!CU zbp3-_o@_h@iVW;5YC9bMfZFyAJVrn4e?17FIFE5lJ`)3V z;Wiu2cFb-#e4^yUQGvxWUDLiF4yyn0*X8v8xx0UP@2&sxWp&o&Z;KzkU+Ub>cXTb& zg2QRw9y2tgfX*BaJU{pE`YxS@c{~gWuO|O{__V(CeoGX|R`W18ew49cNfASF4?n|( zR(*yC_WCmJZ|x6#-E6Sl*!=sKd$FoQGE4={-Ag4DXWgy;eAayHNAuR?qs3Kgco_n+ z_OGw;W~ez^zn_=k!+F-+%g^tXonv8$P`Ysa&+mWhJSRKxKa^56HTRJeJ(-hGpr|47 z-{ZzS@zedE)RxH9DC(KmEjzq5pHrQoC&om}C`G;9T z8O#iYfq7ngQrm3Ao&UVs{r*_3{Mw~z42z~+`Om?y;@Wp@q0V}jMXyZ$&*fs=m2kIy z-Sz(2zb!zwrARY8IQaTcoFvx1Z%g&+4Z-YwsJ9thMoFf-^#cPIq=Z~k|IUA{)3?q2upM6-rSt^>1w&(!=^X#D@^ z#)q}1P5VP)9xMPt!oh6e9Q#si?k-BcJEif=kxPMCd`=_ePX9m9bby$RFn84{k&Py3&! zlB>!vCF=a;f4Qm*H8QfBr!ssfWHGL=XSm1QaO|RCG$(_+`}@U0stg97=V?c@+z)kV zw)i|>oWVc=x(o}nWiaUad6QqB$L_N+=eRcAr1_q|QtMhGq5hnHh?|NjkR8pIybrlJkAz*@R^2wEI3kj~f=tv~n`!JY`t7 z>%RrVge&|9CY}}C^Wn<(k_WY)XVu9Vzy6~s-0+=!0S}`@Ela!d4fVy@Qjxpt(kzdC zdsfxDz^Z;mb>6@F&$I8pcp%W=#Tb*y&?i-&ZOOpMaNVdO^NfAcw*#D6^*?^3x$f2f z@pZ$@dgJP*yqDD%H$9x>wB$sG7dJzb1&4!EvU7B?^JnX)T+wOFVGJj^7;*#-Xy{)! zZ~p0)i|m`7%Bc)L?zV0CD(bjrW9-$(`LRc7PtY-cJUT9>+&~$#&#Oz11szwc5OFb^QH^cR6A|PA&C)_+Dc})fA;^aKmO-$Y^}D?+q2D>7H4E0}K|6=goOAT9ofd*#bZOI6obKiHr-QH~*{;)B1*nfwk0a*H8J=`*y(%@dK;*9fJ57;{N9|HEek%|Iy{G*o^w4;rk9$ zFUu8Ycwo!elgf0pE7yLd3PV8Q%419HYJW|cV)o#hcKpw-@EcY3Id^wWmHu_Wcnt%? z`rJb;oT>~7w@%IEN@Ql}*tX4#S1HEU^-KAK8ef$kJNGagm~+c8jNw6jl-LBGe`~kj zTcym&aA3~jZS&F@KkTZu>zl@L#4O%SAaU8>&(U#Q4sC*GKPUXEbauG4-IVc(Hq(WD zYdIC-1slFuGBkWVUbs(*;qC0xa*r%PLTA4>KCW?PxaEFk=Iu8opcen6Y1}qDw;P5E zJDBjD(PnPAxBln{Z}7&udQQ;!O>Y?#^vv|;E%N<;X4z&d4u%V-7&A0ls_yUFwQIx8 z=?o307#KFcoX^nkih&{WjQzXou8a*{QM%UnIT=2@v`_kW;Q>R!pA`#N{=a7GBR@S$(&4@w!=1^o;ga=y z2YprYXQi(bI*|SUqt(l6F?SoT|GfOhKA-JH7E^{9b5HcwhV^9$#zzX@FHZQdcTvKJ zy-H_|8oZ^Juc)1!wt6%B^jiuIy9$10PGyB1n)&(uLxBa-OaC8_vraqH%*XY&=IN>B zEC=3b&ELw>;LM=K`GQZCVcXOHR-0K4)W6w0VJl06wCz2K4XdXxq?rEaSn|KTRfr*; z<3b>V!b#9k#A|PEy2i!u!s_sq%d7`aG2Gsk%Wcf;!?B_Eu97xGfvK`-b<&6AEm00< zOt-O}v2RNIle_t!oh`$e+I!3mKHzpduieZ#yFMev6AHI`?9DGQF<7xNoLMWqn(KYN zy3p}*eI7=JZRKxoL(YP(>iD_df?VF5`KhNlJsc6{`y$5zzWCXlf*3G$b z8Q&NlI2^e4;ryDP=F-fwY#DZNK6`vE&vs5U=YiJd($@Q(lS2+ZSp6|rZ1rFF^=XiSg(6bLOr#V|Z{op^-%S1$ojd=OfD>!j+&V1q%pNT&a7``;A2R5QX8)L@cTms1=b1I zpU=13WPe$UA&sA*c)oO&-QsiUHwB-au{`@@@w9p87u-%e_qZp;pONA7tnk&n*7Nmt zb3U-1{yd9eL;i8D(k~|j(~Hm5?%sZ>2YRsd=lh9mJ0A%+FgTPlGhF%qS&m`bu87+@ zn{(ObU;6bpt^Q6$Jj3luqa9|C^OnsGp7DG#Lqj&_mK~qho%_E>MIjH=z*&3m0XM@L zp6(sVW(*GRueUI$nLK>%+w5(tmhfgHkJwbE05gWOzk*-c_ikitSTeIdT&;nXVTsv) zj>{UKzn@yqt}nQucXOk3*M~PP)#C^_!*t;+ zW1&0~!=KIP7#p&8+Ve4-5o&nW$$a^r@3qIfgc%<2{y!7SkmJj+a;025!#1`0nmLs{ z=06`lezEt)tUn(MpBdhJ(%)~~uh?d1=-4Y^eUg*G>)-xDh6aZaU4t!B=+wAEmsp!Z~Mg~KkpZ_KZAe@1H<$GLWK+sJ@ud_3tLQE{4~&v zf|B8X(RKzV1}ia!g2nm&Cck20+;V5?Yx(4RtaI7twtGH3b~n{0@P2>6+snx}85$(l zU8xP0b)cZe- zIe4R7ia}xViOYX8Ep78uawWO86){BD3p+$xHmv1buvX^4nUn_3WrcdqJ_?7!_zIrS zEnih#SFxUxfe92iDGU+z?v+y*elRe&Cp&-_#fGRp+kctC;hw;P*|X}iB^`tqe!OtE zpQTyDctH05r+TGh|HH%0BM-MQL}*QLY~bKnu&aJEr-Hqk@m0_Pua}qk2Cti0Z&v(m z{-J5#^3L4}XJ+^?^K1WUwhfF7*VpXVe#F4Q4^lt`?mSqg|H!U6Z{jcdpg*u6=dmoa?J&6JNd(Pd~Q0iIXSU{qtkN z#W~!s1==S6?@Gtg%%!@5|ST-ewv(&82garq@|VnIkJLH=lfVWzNx=cU5MeeWo%| z#I@^@LY9hZwt}kHA~&r~DW|9D-ro4eqOUe~{lD2;`@+^nRa=yrZ_(@6AHf>-D@08SDN_F--W$P*K4keq`3`{m+Ez*YEu2J^hEGbK4h5hKg@j z9Mu^f+;9G~XJJ6YiL3uv7=pZYw_ee^c=i9XDn=EC3)>kQ>ihK=4k$82RafuUyw4CF zy(J^?F2jQpePx$#Z&hJz@D^cMv-kCampA_`GG$=!{WDpRVZx*R7r*^^cQWn%GPBKP zZ=*J+o)%jr9G}X1QP+8H?57tCQ` z&^)tyQD!Pb@}z$|TV($|W16b*i-)0R-Pg~i0uI7+LUbEWu^&p|V~}GyF#oef)w^H) z*QaqZrv9L+J0Wf&lNYg^RD6 zPXGB$wfN|L@9HPhU$41XQziFWN&dH0$XeD|5^K-> zt&nBw&qW_UC8s4lt{?1aX*-{ zv$Ibhsn0(j)?{VMpm0oup}{=FXQtcqqaF3^3@fU71sDocxmC8;|2yH#xa78d{gZ_Z zHvi_|`DZDjpy+A%ujZ<}C8Nie%daQhn7;a-!2ds%M}J(l(d*bL&d^yq>v*f-ccumB z9X{9p$lCFNX#&H70HFhW7!IiZ=?-BmSo24nVFEZH7#x@wnq?XG7=6pW`P;GQ>-u}O zr=*w}RCyT^LN`5PodA;OWVlj!Pr*)Hf3LQ-yhYKI6FdwDUMq?*{5Vo?%)s#X?Xj+U z-JK8je!u6!PEU)-U6RYiKPUCN2ZeW+M zG4SVT;AgP-98mEu-i?tVQ{_SC*7@5M&t>*Vul;t}EbPY59cn6Dmo#kBYFwSg*#CdI z48xXB{^u(>7#bOL)fvQ(urOILJcwhk`4_+LSNHR@Ggr1QD}Jy2?7`yvnab+-4R7=B z%L^-+Z(1KbeXhyd*Yme*tGOM#?D>Vw&!U&ho37tibLG$N)$DH{7wg+)omwBB`rZE9 z4$H4+j7{s6@9t&yt-ifw*>px928FFxg1mJ*|JyY)i0}Ndo|9R^gMr~&z1@#K(>;2y5zxmZibkrpX$42v2a)Jm%mQALO6&6DF}D4PA;HjRN{=D$QCh65L37#es! z{x!H>%*nuzy=(XG*Ls&Fl>aeG%6w;97RTVg6eIl4M%tb;+xN$3o!`@?7-n4AcC%+j z?J1pmf7ZOTtvQxs`$5;Z{`WffvulIp&mFOsJht>k`ORy#<+E?T{e1Sq?X+3Z>N%Uu zYQETL?@K>7=k9ujgyQdJpXK*!E|vZS^7d911|bHCHw+KV_y6i&_4z#$!`iiL?-@1h zoBbkS|F&PRv`-(;{T^K$Qhc_Yvp8~*@9pDTf5}(zax#3VIBNRXNZLGeuW9|ggu<@Z(XfkfAiwk36TcH#Z#(lbrv|;{M)te@3VK+@pY69c%YQGtpVePr?w@U2em}c{$9_%m=d#U}znRnOj<>&` zti>`xkm1IL!hJjcH|nM?+;=eN-kzOY3@r(L-0v=Y-B$Cnh>$w=Vq-@RY1dlnN=P-Pcxh?R?rq4db<~+l<=j**| z_srj~SFfru<@Zy;i(Bl)&;2Q_l%K!)?R57kr~h#7@&9eJZbO4IzubpIY&H3Z+xg8G zyY+5r%l)!dKO)0yYQ1!y-IocKf1>>(Kh9@x0F_#|mTLc#W@FT2{QiGdv1tTj!?eHg z(YjBaf}%Ya+vV;TV7L$_VaoA9=yBA1{e|-JE2n>s)vo`oc%k8K{lEL>S0y^Db9GGm z_s_Sv-=A;&21H6@Am3PUer5klk@ae+~$I(dzU3&&fHx0J$l*qM*cmY6D1DMS^B!q zlJUT~yRy%IPOs8os6Sx$!6jmQUhH88hQ7|nS8s3m(90gxHtXF=;j?=xKd({T`0&-$ z)o;sN-HqvVpG9Y^X?XZUCPW1sx|AJ-o+i2qfr5MX5JO8dvp#BkO&rb&mlB-8L|F9Sp6k$Mh>BPw6s zdAk;KgPM$+ z2qS~QpO_ovWo1zp{jVBq=(V#;U%=SVKELwHotOonu z3Vqx2XMxF?gtXu^6y0)O_5cH&Nw#FC)X2 zNAl9$K0g^hF)++Jesq<$O@@*g3qy_e->(%+M;I7etN_7M|RiPj2I@= z-(R;s+bUj&NraW5@+c%Qd>9@adi8H{J^S?;;c`)lj0b9~x6htyU7p6kuxO74gM%}N zqwAIFC%(PCJw2BH#&L}R{V6BXUlAYb;+ZAo&HJ%C%=RH4ME}rH8o;{Iy!_Uvp-6c(PlCRcXT@|`) z_3G1V_AC|;+UyuwrfP>vJ#lB4aFUrJAh7X&-~Kw)jX~*N0;d=ktgZjDJE85o@BwxPlS1*y z2jgy=voU0xWN`= z`kU)<@%;a^47=Fr()@RmdXMbicIo{mo&$C!ZFd;k`}0h?#dKE*{Sx?YaL>J%gW-U{ zX8wu(i{v>u62Hu0V0gw<&vfDWY`fa3-XCuAoTYERUvTEP6y|4Qdcw#sNQ5h9bkP z*Yz9>JDeDbsu-py*|1#ltpXR&syhE(w=r-se355pSi#T8AoQPwfv>E-XWOyxFH3*# z5SbO_R$`}i_d=!b*MJ;5M~3ii^^trAYzzg8XIb^$9u8OAxc2PS+brWZK_`LB!r&+ih zznkIHvm*Z$859mz#$0~`I{rDkeqFW&3qy>maWX^0o^>_%%)i_cV(@sdpp`Yl(#e8h z$M=1_VGF`rsyG+b{k(Ma+J;^`p^ptrpxvQ5U)JW{-gb6P{Qi4V+g8dKpV)EZ8)xC3 zb1Q?F$NaZG`|s~>ZbpWmaxW}7>(^YmY$pDRfyqEpSif~2H^YY*#v*&ocPFpLGuJZ+ zF`d}MxM{u(%a*?>fA(+7T7UMlD&v(R)*YX?7s%h|-TCi0Q$vRUgRsz#lgyAN7h^*j z6DS4NvNK%Rv19t`$F`NO3=9nno-U3d?lnC}>~Bq8?dI@jHABO0r`<>PiwJBv+kdcC zh{aBlvo`#YZTS9a5(38`-D6a-N|;q|urycb{~uEomJdhOf3ITr5LzSiJv=>stt;)*`4mZ!7pFj6<$VYi52I2Q}mN79@ zy;u3Obmzaa7*2zyr}mgFQ@wJE-e;AMwRTGW@-1NG-0?sEee8VBT8?kB(qidM z^7Xe>9vQOh{g+`_@Q8u)@4L6Z75;e}RpLco!g9=;ClZoz9 zYz%SrIcrS!@EuW`8{EVoB6J}9A170RK7-_Me&?F~A|Hhq66C&Se?H+l|JTk5&*K>G z%$?uPwPO-PkRRi98=>D<{0;x8Trw@Mo5>L1&E#>szU1s;iTYRS4$ku!6d4w@GAM8= z9ICVFWbkEz7*X(}oGmWe};f#GtC-Q6w! zKimJ`Y&xCWuBY!Y6N41X1H+Rs1_c}oRJA8JsfMsN9P{{4>>Ph)pA5r~u(DcJrr0=! zPrnOW>+PPjGT43pzmJFU!Si0b%huN$c6avpHGOYd8 ze(KlHq(7B)47u|}er%k|sqrsfjrGd6>qU$njprE~#CFR|KR*Bc`m>Ke>UX|6KmYiV z|G|<6{@T8ceAnOf2v&otb^mwdW=K*vpzwdqI;e78^^Hk>i>Tq7#n zZoPQDbZSD!=jRLzwV(1^6d2C)JBTuARNKdwJ}>+w#qwbF+fPyxugk|aUrC8*JUzL= zi7_UQ;nU7D+zs`jH5|XZg!T({*8Zxilz%p%`+t|H_L}Pl+SbLE+3)eUS@+5=z4!at z`&W{m>qTxla$-jPCbxD5pE(v6XKAw>5EVL5%y8k9eeP}{W;F%_0|tg3hKAE0whMDI z2{Cx+g1SMg|MPxkG7#VW!=FKcv0;%MLsV%43&Srth8;ExuUP9DdPV;8F--U?cGob) z_DH=bL&HTa`(H~|+katbDf{*JePZsbq?lMbVT)XplM)R@`b@&E0??yR%#VIvt<1 z{&cJLc5SoT!&3d9mp|Xa`}wKggHD$4^*57}|HxZ!xi#hd?Ajy6LjO_>zbC7_^SArk zb8}z1KRkn|b6nt9-|>%u!T(K;`Q+#K^V@vwQ{A@CHCfv&ztL>h-=2EOy)W+jyngk4 zO`>$|%^!Ev&*W~NXS8|RT!Z%OdJ-EH8Dy9;W*%dY;&2e;XVCg9{+Mxs6T>TZhA57P zY0-|qZ%-FcU}2Eb{m0J5pm0%N@jwC#LkbJy6Ek*(CyWm3{;GViWSGFoP|3m2%6K9- zj;-K$pX|0b_cx!n%br*L&N4f_{@ffxb^m!=-XCOV*!OMg`pC-+3_VUvDk23_zf3Ot ze^}k8>4!hVf_{br9XFSsne}z=v#p!A2E4c#%x=u^fXVGr-TUu#=W8B*(x0_@{a&%# zs++D#n=R9VlnfFQSk3pJa3pP}s)h;Kd}t&*0?A z5G%0Yo=U@6f&YpO0>VrR|HHak7(6%v{5crf8EiHt*eWutyU!oa!65!eh=GG6;rVBw z#)7xyZ^I=ww=Ls>}M zzLh)m#E_%@M*Z)@|I_5{zuo`8hKnI&-t39O$7V!HQ!&HXZs{foB63(7_mmuWO#GK+f ziJ^!iz+cFL^Ti46<@%EtiWnL`YlgqQsLsgH`HPiXET#S=!}WFdXPrGd zUt-vq`gLDFn=&-4QmKD1QGsEVwm?DTuE!5!oaE;zt>61?R^{ij=4bos|0I7t$Sxnk z$Z%u+KYRJjkN#g1E>CHD{{Q;_U;FhPn(Tiy=j4GJP~gTU7ekCmoB%T;L&8&er@9jp z6qjDRcFpI;^GZJd2GinW($}>%smWB@>?~1zuH+P5`;C#|o$Q0C`n!)8GH+yP=+D%9 z7qz3|wCKIBRyOPnr-dDib@Le<8T9IE?)S4X?s&a>^|jsK{+b;6Yp=$$U_ArF|3B*{ zaQxe@{XDz)>BofSeEBAzrf7iBf!*KVluu)PBH$p!UcS(%;g4k{hlnP##`?Wgry1Jz zbfic+2z?cJH+532s?d)cZuRow{|&NC*0269csTa|#$G$A=a1^&+}N0QYKmssjh43p z|0Nk#VCl^7~uctn3^Z z*cjH_I9}cC$>A`Mfs37%}wiL^8Gt~TKtYL6)VvxM9e>`Q!Q3oc5Ssl;g1V8OL z5hGr7f749%@N@aAeyB4%5Mn;wCmYPz@JC1GPpze*3c~}ZS=Hwz9Pcsf-&T37e!6&H z+1rmPa}0|9PH&n&Z}%sr3%?fzG(3-gW%!%FB<;{XCWcjOm>7Dv>ls3h?7vz4KeG7y z-u~#rX1_R?GTs)O%PKPjFf&G}<(e6%oiVuM8o%pfv4FsA9)mJDb_P3+2SVTX-S|7B zhS4V4o`I2(nUle!@O{_a{*&Sgo4=m^8T4Xz5#xy)^?Zyb3=4Mt>n@(l&2V7Dr}}4S zB&|6(8lForC_FsBJu0c)#3fZuw%s=p)YM8>b`XgGJFUP$TRYb>N{QQ%+TQ6 zaN}w!m+%D^cMgUE!ADV^OA7U>oERLUPhJoEk$l{l!J#XPkEvplmG7OM`{buvOir@D z7IXXqV*`^IFT)F)Ohv&cQEuHePD~B%?`3X=e0U}hvwzfKvtI_ww))pp?)~}1boblG_V@k>urr7;-7)$1 zPK$wIqW)gf75is0G-xqAn5b}Id;Lf6dQpZQN#84T_MZ_~*vpu5qk&su$C14vZ*IMA zYhbitjycfKUGsC-J{blZ%>^}jkN=;1JAZyU8^fm=xeW{q3>@qQl?)SpEI$sNP!M9s zuwamQBB1bNr+yfNLTleN%~&<>&pQKlGc=UQmvPolKV8qmaN$v7EW?4R4F=bk7t{;R zV=!cJ;ELuwk>UCN6BEPrF8ib3+ijTGgcv#+qx5z>Wo9@l_MeMEKTOWv^^8*?{B7*I-+I^UX0il4 z<9YCV2Sda8()&+u$$QD2_+H4!aEg^-vLa&?AA{aEPKF7P(f32#2ljkhJpamMmJJ~v zSvFWQ#>g=UF{waC6{bbSGfs4T|I0prq2aW`msl}jrU$2F)^jeOAyl$(YNhT&`SS1e z-$R)`s4|vt^2Q%Bn(xs0kwN1Xr+_rmf}QJkZ*#uyuVLfVRd>CNL$2cAI_WGuh6h}k zAHSXyU}9&;nDyhlG}D5aK1?T$GcY8q%kGfY7d&y8VN15(v7gyfEw>oWm617dy0gTY`4@x-)xNH=I45p;yny;QA+BV1XdRg1E~u|5u(DXF3r3 zy%yB)n8;u-#ooa>O=D&JiLLQ={WsmY85b;RSSQ19M@3+QCj;k}3#aVYA7WtGuql1^ z&0VG0XHEO}Jv`i=ZmGs6XOj_Q$+$(O;SP7hk@{4fr_i?K+ez&V7vuPuIT$uoRN{WR@Sd^3Yxq1-3A2Tli7{kHsEcYWbMCBy%JcD6`?N7G;Y zp2* zSX|T~x-dR!dtR*XK87!eJqZloI2jq9u&+>Osdw1*X}1%D(?UiS4@;H_=Y<(wSTk(c zkUm%R`_uQw?i3v6y_wIj?c(vtK8HS(|BAQ&Kkw&L_V+XXSNvD~y!pLt?!Uillb2P$ z^(lV(^#7Epj4B)ldXLzj^@L6!Y)x}+=w9^7k%PhHV3rfZjuZ8dI@LElxBv5yf3<>x z2Froe`luw!`2Fj*MELaB>$6KtRtSi`*)J-!?)k>`k2NjBrk{NL_3HPb+O~&j7ndboyCv4YFvbZ|cqPvhJ+)_j@cN6dzg+!(*@xjkehj-3 zgGSLkg-=t28hAzj&ogTH_Wf0_`MrX!|DJDyeP6$NDXz<)F*|Z!%D(!)Yf>lXl)Zmx zdR*Qvfp6{$v)kYGO^e0)4L9G4I{W74=1_(Qo-4wb8F%g4<&&PiyzxMLI`5IX)T8&K zjw&DMOXPc&B+tfR#Bt!vw_=6^jtuKL6r34SboOhrJ7}0Yyg60E#K6qYpfktZm+PO< z<~8+~eoWIcug{wvUst)y&{ubB{goi^wf>9^l55ue|I5knZbtE)H8;Lxbt*6%v0M8m zn}MN!OK)wN?C;E#wST!e8B`c1FgP6i%oez>{LEWl(f^7J42Nf2JNCtIOWEs(hN=;- ze^y>g;oxm&s_A)l*+TrIYtKJ94Ifu#rkD?xEzYVl6x6);V(#`$n618e*PHk6-nG^L zIM2W!H(l@LCOJlfoX>`5j@sEVeAqhMY}Td?j(>IRydD0`4ExUf-&a{<9n#o(OptFw_Ula=3?E)Jf1kDeUe)P8Pt@%V z?f?Hgzcx|2w)}qW_F5lspHg?Lw;$mlbuabjGF!>y5tL-1upZ_4_@Scij11zqxYRR=1sh+xx_&(-iuh z&o90D+wxic)?5?e{ z{`u#2ZhtSs{V|@E!O2ti>3unN2HDx~w}q`|XUG*!QT(`m?U!ure=Lj{Z~h$SK67(% z_u{Oj*BKdB^=;0*ZI=DyQUBFCo4zc|+}`hrd}oi}Z*O3@)>eP-9_PpSB!&Z0|81)u zG_vQ^F-Qop9%^PRF7VyDWxdJU^ZypGNN>$3J^R(o@X6`Cf14_+@1%QgxVk-lYVW~Y z({xO4^U0h3o@bYP?97~8yMG`1rzd#t-Zn$|{QivJiQHl94cA?*mz4g$^V<37=89v< zav%GTlyuB~{@XK=|FF$Y{dz{8Gs2!c27P>waugj*NYf0 z*zhs_*r6||&A`z6cy}pBeP-B${}0&h4ID3~{S<{?*TAa8UbK z%+wIO`^+m-=`}y*8>jd6Cdz6sFuc!wJMC%Ozc$UZxaV`zp53}A#?T`buKP#vRq-F* zo0Ye#%hMPZq#oMEf3&`GHp77%8_R?46fiLyx?APAn~(8WPgEA;fv?x^#;E>VzxP{| zw$riRJxABftDVit@czwr=LY%Q9S8SpuYbVxd2eR?DW3EGI#Tcc#{D{&bM{9g4?|md z-!?af15>YDD)*~=(ZJ1+;c&pbouQ$|{*ti+v+#`9j0|xrqM!el+0B3FXQIj{&kyJ4 z{#|E!)Bm$7!;Uw%_=?@Ve{RqHb&H4L%*=gPxPCjg^R0~I;J5uU;j7vV)9kRH-!s?j zEcWHEKT^-epmqCMUug8%51O0LTv+J5wv=zb%p-edhN-!i?|us1obkqho5Ad@b%LAk zOsS*leA8rmO0m4{?UDp>Ge@xr^_+Mym(glvwZEF$qj0TegzIr40=zJkM7rx zVXzcP__FKMj!Vo9^=Y~?4(AR(Kd|_Zj1a?zsr7seAAZ+MDgM)CSh42YZTl7Re;$dm zxd|{NKmFYjv)>VPQ%scV8~aZa`$IVEg?<#tt+|x`Jf`yL)MrOJg##1LssD3dv;W$i zO)pNBB+mseD^PIweANDy^zHVD>blFOOZOS<5o2Vy!{dCRxN$urgVC8(ruzpjZ1}}L z`R0MoUH=2>ZF>QR6^#rAS@oaI>zx@EurbyA^qxLDdVl5aiJ&pv0*(pp zcZ#R~57^NV<2n8F=Z@-kpWYX}Ew5xdk(`zH=h9pIDdG+J<^l{Ua!dt3)j#^zyR$GF zd^^Z4@A7xvpKJMl!^(cUovc?)47#(p_RC`VwKLs|f7;ytpUuypF|qg77hdgu`LTti zyH@&va*uQi!vP+(>-AasdHcj3?LW`J5O9d@{zz~1( zf$kM~CI)5qJ{5iD(3<_`^^;8FGo5BlPS4|D_~EiXzx4jrSoSj+s~&{9)Ypfm{ml)p zJ^6d@k8_2BtH0OOd|e%%+aa2{z5a#iV-|*We}oko9QtE!{GON1;LugA`7c(Wp`Ly3 z-Pf-e8Fo4|MC{x@>uTLw%d_n_=37=q^0Fu_wp-J(@53SPZy%4_|J(Svwx;;6UGedE z?r$G|X^(#nS}O8p*Q>|ReoXMcaa=sA{+~KKL#V(5{eO@2&&2<|8h`Wn{=c{PKV>-J z)Nua)=lwHgx7|rKv)?eoI_Um2Js`aeI{ z`@8M~`>f}m#8w!y9N=b>Vr97S?*sq;3vpiMcM980d08f0l(C$Cw(#J(RnL>Z>8}<1 zpL%hAi0!sDyRY#?mxV$D+FM}3&qwlWmHz$aW!Rz6;QeRvhW9UDZq)mdG4*jOgF_R; z^u52|?G6=tRX&rO;lgjFg{r*fCk=j9?U>nkvToLUE4jVzS|*21RIcB(e$ScTdrdBX zn$z#b`QNRfLG1W_Bk?OMjQib8pUXN$vob8KVGv^TU~pK-VE6I7V#6fy19j}aObo}^ z88&S4Uu5si67lXaLxN*9``T}_x2;@#;_3Z$zqX%`NEbVECqeAottpHQY>ZxZ3vduxR{bU}$Dg!&mXM*_r=mwpJCQ(FQFISeA<&4?rpCw6=BFo z;AjwicRjwo*5k(N&l&5O8BV;Nl>K#ncm3}jPq>V8T%iM|&9Y2ic3!>xOPHa)p!vY} znIhjh)LnT$3ouN06vND*=!}x}GblLyxz5etGgqwIFY8>p^PcIiwj2_0U}AW} zxM066AA<$Ug1T9Uz1em#G-$Is=rB0!sjuf?`0%7&jv?TCe$5IV1!jh@C;IXG<}ff! zW!h3(^mTq@Co@AP3xmkV`Hl=6=1dG-Kh7&0n4^C0BtwJJKVyy$zh3yKGdKwTmt>K! ztEmWE${^67{&%t`gY&xI3k$E;2wi1id?Mq!!TaO;;2BQ zn^cDS?>W&IuX{2ix7M2ZH?ztyENEmfuKHZMik;!jsgv;Gd!487AD!6}CD%b+P}9O&7v>lV>#V?D+cr#fuH*XQ#)O&2;=U4|JGaAcMja z)(5YP9UH0`818F_iq>3^m6qohKaNH zB|dnzzVm-}dQ94>{%5c=@>Oaj*HcDdkV57F=61L!6c2Uj6IB-KY4& z>$xT#Hv3i7QNNh8+DLiQ?}fE*lI2%7GA!#;X*e7no6N}2S$Ctfe)ImE1cCqITmR+% zhAe)J%BsB-weO#0cK!GA`tYuep^yJx-E#kz<{x2QUXx-*hWXs}8k`ICTNLyeEV?Bn4+jxyZ_d>2ew>E_RL@1kpBYB`|HG|16o(0@Y^(VeBn93;A!Ek_`>79)2-=|o-qc~>WpHBtX)jhi28Pqh*BF`WzU^Hu&Bd_8oyU=Z zlc8SWnW{;_nXQlPpVS_X7k>2rSY^Da`IgwceRhAlZ+6-9h%!kDo|_47x zac5vy{=;4V&gSVlkx60ep zfnX&=f*pfH@;=KgzU%&fGTj_ByIn2vu|Us$c`jCq{~wr&zrAR$pUfS5d0OfD5QT{UFn+ z|FPr8o89krX@CB4!0gwLIo-SrclH(QfO;iS=g$W#Ht64B`Q5_MAo4#&k%3`i{Nw{g zxm&M>JW4ppz{qe;CT_yq?nwWV=xh}Ri_(8}!M|QcUwt%xjn2RN(q~N$^>t4k)O%?b@# zyX^nI^q(zhoMv&K`CnfwXz>@g(qUoHlVtety#C*FW;uo_t=mm!o;WXhT%L(x-QSl# zjA9gZ>$wIzjRn953HS#ixCuv+b@xAe5mw3agRXz+2$3HtO6muxJZm*9y z`n-}krL-Y{kKv`|1ouD3D;P88af&b%tSZjj-|;_!L4nog&h(ERr^MG)d_NVz!DJ%D zAfhQggPWn}<=l*3W`-sPnXHRDcvdg?$-)q`?q8+u@95QQzJ#}4*I-c4PS&(x=KH2+ zWRUfkf8Ca{*VimnB{+Y+>@Rhl$;R-8%Ra3Bcm1!hm3v};eSOWo>FxiTteHR8r-w&= z^#As0@@mB&-lD6U7>b{rG2EPXw&{j`=$V(i2VCkq-(1}-cTRyp?)|Me)qma+BbXj1P9L32udQ$Bf?d zTQE2@MGM|r6;K|^R=~)VweE3Hcdh)LweiP3XMg6y|OyBG!s;j}GY>o}jESv!-F zDZcYS03X9VYlrUF)8AcMCsC#w-(a)v3}XO4Lq^Mw_4VuwKJ)GFe)FAcb+y7vI?R!w zh1o=sVMc?g2*Z|j^YVJ{*@M;?*gY=Jtk-)KyZWErWd;WEKMTJvW@N0%&kn15!FXX) zvK?qq!;K&J4$s^f_RF@I$Hnf5-JARJ^MaK=_UHB-v|!MP?qqDx;%M0K`X_dgh*UF! zDle1GmKOn{^$b5!zpvEdV2Hk0dqd~emia98nTHO2RlU`g=o6;o@Q!icF8;|34prO} zawBc!AKgE~RkM~w>DN;82JbDqTdddq*v+!<+b*ey35EYQ8va{%r18$y`N#k6zVnBZ z&m&<1GsBOl#bN)vOFRC%?QMt5z#P2(@8$e|ni(r&qwc>>sAp!_H}%&dhK4>_P6i)g z#)eKth9^z$h2Dj1>Tg*zZD))%+jk~~K>1H{3yNCyU8-AW7k=$~&eYJA0S<}`8~zFW z|H{yy8N6(U=VUcWEB<}&S(7=~9sC;9m>3qTemc#`Sj5;6$(GUlgJ`4{o{mv6+keI`8V0+d2ov*wT^}@fHGCcUCRPdDH z10#c8+18>4JBA0R7z4~cJ6@ktCkP6WjmgJn6+S*T^TvBtOU4Nj8}u0(a?Ua_*z_~n zFls$yFf&Y8YSoY>v08<{e#`HK`=-)XXV+Ep&)(EvUM^pI=Z-;*N!-4WBkTSgNi>HB z4WK}7U}5Co&&bX;_P@9Jiq5<}7kp&Rua=yNNpiKfEn+YzPUSyRKUJKezO!EHu(J4f z(ZKkvyZY_cMll|6SnAa{cX#2@B2fm1FQ<>x2?=}%VEk$G>fM%J|pFg&<@N!TI!>vaZ)sXx}|+}^`*f>Wu4yQLvo5aMZ!_aUbe%sT4Y0M2r>W`jTa#Z@QjO6q8 zT_3|09L)G9B;38!$HMSSfIGtmM>49fk##Piu{e zIV*Gd7=jO99I2Fe*IQ)#;3#yV>7*rTeJZ3bI zU~n)#QqRQjm9ar$wtfA+tXp^3xa!q8G%h(YIDBU5U}O+sDmeJ`|2m%^)8pPv1dXzs zkYT82V({Qd`2FB+!~KPY%Eui4L<=|wFl<}8*7!7IL*X|Y&NT9J`kO~@8Q-NmWIDiU9WxfwtVliS)bp=|G5_Q1 z>`*w6z|5e@vY>*aL1cDR(EfcKj1!i!&X#4mpz|@FpW%WNL(TouMzl-gL^>*bOm>KRpKmA9a(IN6j_|2QYcb$FtyKJ^G`}c^$_uCm5p3M~YUMbbE zH9RgR@7&%kNB;fO5Lj?5NAuFAAM&O(Q)*k(53K3jEXbFTdwkD3ErtshOAi!W(kyHs(hPv_?7Fj z^}ST`u+D}s^4yX)}_5}!*$(ljA|2Svn*J?b?a5183qS?B#n>V zsd~LO?ZgB{F@^^d84jG*-!HTK)V=w6|9ja41LasDO+rD26_5V^^u5l)*x09$k>pk!gxjKpQ$!yg7MLMTLuR;#xJ${;@Lt61Q;47KG3(HmcYXDWRJHf zgN)VX)YkvS)z@e0uI|4k@z3tVZl_28KOW{jd)fb-x&L0H#m8QlYpDD?sqpV%J*UCT zKF_psOUyPW+&aX=^r2Ga*Yr0xl3Dkg#J$UZ_(?hK_QU_H4EY!mI5tLX&zl>$F{yRu z=X2Je$s&o}saJAMLz!b9#cJz!{8Rkh=Gs|bS@Uz3-p6%&^~C?{X&m@|oq@qWNyA%8 zh@s(R+iNbpzk2JXrB3T@s!9F!%lO%T!TR^=<))3`W*s9ZgULsEZU&P?{iC&K<}6}3 zV7fhyVS(&s4TcMSnv4zJ4~iKc%zn(wAje_wg3&>XdB%R81=`&^pSe0b+J7;XVe67| zBbEgdQyFIK{(H^Hu!(2E>*}2|PBVDj-dVi-<*QeV?q3#?W3*HFqkUeN#X)|4bM1!r z3H59YHtY&O4=6~4@`EEVhg7j$%!-K_GmW6#h2WSu^< zd|F&yUDUt(pW|(>&%CrwQC`aWS?KjUDf}Sk}M6^dG;Us_V#vqZ=L!J%ij8R^*N%r)i(mI2kbZWB zli?G`S2y)P*&+-lzFLB}*s$?3ba*noh`!0s$Y9ydosoNcdrpet12hfm`5&op?mWub0btAN?0)cyNZ1q0#@CtNr`W zPZ(A(a^K#bZ@zC*Rm_yk)lY*`iX0r2qSyUvXJBCGwDD=+uH7&Jr${s%HN{5uxQz_6`8^|YAwyN~}iF6w@3{CR&G z@9h_>pSWlw>bBOaM)|RDRoPDSqtKqyKm2=-)X0e*LdmKTo!;UVYTQnL*%ZiaFojvoYm|4Ssh# zzaC?K#m?!${r{IHfBRVfLfb!`VZo-{ZT3HwL{?naG%o+B9rmL9?f*(%h8r86-#>eY zRr-yoFgJt8gyO<=lAIMnj~D)Y^6C925e3J0!v9VD7C1DsMrkzgi+oUNllxw;&fpMn z_SV*H;|FaMlqDL?dlLO7GddV=`fD!4utWy5X!F(sHipW(hkqJMJ>2%LzIgBX*N@^w z7=FaX@4G&M-Pz|w0}F%eVa*x_1^*ex6Brm4sx-uamXGAx-!D|Wq0MkWjj`tS+U=WC zYTxhwzfbf%XjvxtnWcps4$Id5`|ZT=qxvHwgFFkz z+483h4Zr_G`uNA0801+T>Ma~p)f-H6!kt()S@M0npTQ7tWDi5b|GC)=3$|~cex!!; z+RdApN7=V|-`=0ed;7&)zZaA&Rzm}0<%c=9>|57a)<{V~ZIJ*D*{GIo@ z>V-OP#O;4`k6R!idHc;R)zfZ%y}-f8==HvS!)2*$@l~sprLFfqPt*Q)|J4tpdO7dZ zjpucl8Rq{yvwxO;T$yP*!_wRCJ-QDC6&Pxj#m|^&%kMnS^_M-q_FC8f&G&8dd2dg( zD}FxLKO#H++b+@nr+42|WeB*n=!0dnWvL)!6yqe)j>2Cd9QSV=0PH@p-$Z)Z6mDBHM zVd!E8jmgX^ets^M;eqx2pMGlBex9nkCoR5DU-td)`~U0M7#P^KR{xJ4I{` zdInCOEmOY8V=n*R*UHfG`MLJrm=D4N7Z@7E9?N&V zoc?*oX}kX4(~Dyl-Bo?#%oJZ8%d1l`Q9s_Cg@NJXhyAV07N2%KT+sW>f}tUELV^C* zqg=I2pv+KzOO=x$MnJ)gbAj52+bJsw$81VH{JbB#$b2(`*oYl?^P(9CD-TF8|`;G?g^g0>;O%m9#Lbw!m@9h zlI$$y@V`InqQvTUo>gp+`JVduy!ErH_4}^#)gHU>bK-Ml6ZI!g9}4|%mSy;n1gdU7 zMQmhn;3}Kd9QwQKb=|d^+F3uAKR4Ny;?iw8{lXQaPsh6~x_(}FVs~P2sMZ%+mhmY5 z^f&*{Kk6f1+^}%pJNw$#?jPs58M+)8D(5(IF?dNaI5Zi~{Gy*SWlfK|dbSdSMvV+7 z!&zQ~;x8c=5;Yo%Qh+4f#HH)7Sl*QZEv~&3I#1Vdq}`h4(pk{yKk{ zp+f%IfyZtC`0(a+Tpj{pL|v@fZ($CcOozJ7>kN(*`iRDt6f8?L{@;8%A7WzN^o)S|R zXQ;mU*XK{wMk^QaGg!1#2{5#LD`af2El&U1{iC0SLDhASxZppv0zV^$3+w_8qW_r~ zR+!&Z`8P39CY0~Pj*5>-pEwHI7!r7j4W@tlyV?HtW=4L7f6smE%63k6$~*wC_}v{Eq{(UhhmgdRSmy-K{Ct z)@FNnID&K)X=q{fuW!^V9cVFqASh|F-wq?)W!9J1vhZGCcSd7kk{3 zVZla*hRD#Z4F2(xTQ?}x%gwS_7JsCkk0GFl<7d^H5C(=BX&wI;F*Hn9P+mOM{oOx* zMTV4+MQ?=X|8UvN;NZM=B7?(6rWH*Lr*BoSuvx&u5UR+yTV5{nw-tZ=i}iL)p_%=T)niIT!*&g*Lo>xj3@(z`Ew;KGE*FEDc-r>+e2em{9M@@HC%a ze)hJyWtox2w@kbeK3$)9lHK{(ciV~&32FU#(P_QkGnbv^<}8?=xnDHIm-+ehb=od!JpuntL`)?E27=)O9xa`Ze znc`u<#xNlvy^}H9Wgj!&joO&w^`BcACft}Ja3C~3PVL?2&WcK(O1WglhSwDvlMNXj z+*pzLSLt6YgF=Cxl-i$IIYAi)3x$TvDIcyM+0Vdm??*io!=1nLnHe&RUWY89T`HHA1q~<|8`y;-xobIW3w}+hsEwHIyP*5y>9o>uKHi=WCaf7mD^VCZ)KQq z{78N8+06ac;gSw#Tz{TnVK~IVAo)*Qm0@q2CCAQ}FJ7$hv^!<5+*xmAr*{ARlarIz z?k&#WjNM5JHE8Hm4V^exjB}XCmeWk$S^FSsNlix zwWpIy&aRtpdo5~B!E}KG>IZ7~UoPsI^r@A_SW;`U&s?j}v-S3$mg?7@oV7;k@b9JN z;{WRuUc9u-IkIG(*5hOr9utO!?>h?~Iz3Ncun{r}bR}r_liLYQ42snskBYC>x$@qK zm1Tykby*6-fm97aj>+rAUzN-D=k2O>nmqk~cWi6_+}A?)4bArm8GU%ZF8A%uJ)P5< zzP~sTZNhM&RodclXk^nz*ysQ2_;cwW1su)oH=D*JpfXY4ElxVp1?v z;JN)`bp>aHHuq5}1_5mYRfY+no-wQcEd2LU*F2*6hkfm@lC*VK>+R~>6&Th8GR>Lv z>FBn?$HyWcI~@FO6=lyZ_^0NvjW@$%qka603?b|X-pl6A}~|3f|qZ?D!+KF#cum?~VR}3=f3Qew`uq`h8R> z*IsqaFSYkL77DQbv@2lVU0;8PV=b4%<)igH3@^ARKUY5QTe~?*j^RP%Ms3dE@GrAw zuxxnA(7;{0v*;d=z=5Ox|EzuN^ZM0GUS@`C-3xLy`Bty{%fymk_W5M$&h;Bl7Ds10 zC8z1m%l*&xVLJH=gFywBIp+z`JtEA&|)O9qE> zzhaM|M#J;D*O?vc)W9p>dB2zyJ~|>b+aR%N>C?x@`?vR3^!>Y8{A@;VZ0xg(`L(C_ z{JqD2=6v1P?9bizrx^V{$6uErXIXiwUMF(fogOLMTcCB<&yGpwpUF8fLGjziWwY~) zK=-$ou9dQ@sYt8ntLr)3#wVLKfAT}iZ~OoKdcF4K-4; zx}S|{f=;I2&!~gxm(6>7JCAI38-Hx(JBhLzTly-mym?c3rQ*mwE>^4S@@C2J zXQbxse!Fef<9>PLD{G>wChuIG9I>C7Vc#OzYMJZxul8w+D1Ixx@Y})X6?3fgS?}!W zv!_2hZ=io>cHX&jbJONU@4PYR=FMckc{Z7RG8PH0AA3H1Rb7An!u#Y5nd>F>%nX%w zWwMrOa@D`@E|_caf7;FeS2m~py<2^Dj%)YX7SGAW|MwmI@VoVX;LqQ?%HPL{GW~hs zZeP^OcqMfWuNFgn!P#{CWnA_092qInm#r_}THAhnO2XRb&m$*mpONAEU^r|3L53FQ zDQnpnY;3Ok+_AoU!6s+1e*WyOmFqICx4m#q2)ySuKj*Rf*{#B<5^@{)pXNDq z9^j4UyY)R@@Q00yhViTQ3{1gx>mggjcmH5y=(&I7!bg- z50BN)PI`KJEywdmvz-})9>x5be7ENF*)o~poj=qM|5N$2_3`5QKGNz83={S8<185_ zJeuFb(2!mKWpd`@lHfO1`&sIRVtxxVG)Tl-B=33|wdcaESE}B#^!}Y(x9`_076uyz z26LVt*3BCF_Zgx#Gc34s{nOd2%TiAqUiP)``I$$R-*1clo_kxKm#Jah*7UPe0@Ke- z`Dhzvv$Jo1;ZfaZGuhXfSDW$sGdS$&uYSMx`koJmxUZd?W0^d!@|oo4wV(1Od2ZLg z(7tan`|9d&Ye#sPF#YTAcQGUiF!tZ$C@_?-OJw_`C1j&CTk& zyX(H)Oh3COa`Tz^e^2AB-UiSAHD%|UO{d@F3z>gO^f~wcvHic~pZWz0ru^q-V$l0i zUzRc@ONPm0(_QdHuPDQZr@t>uIu**;P(NSS{PQ{e^_6o^W-p($GCD2oI>)w))3^KX zPVckj+bg&2VmPyzTImdHwY3JlLNgQ@7DRF|ocO$^lHtR~G)9I$@v9vggoOT+?dU3-zqGUxOmg zSm>7gx$vVyP}%G0?9k;?^Ii6e-Lf~4v)aG#@6Ou~j$Pwi+Fq7(es1B9pzY`D|9$>; z=i<-9#S+KN-rk6;db0C*=;4UdTkY5E{iyr>;3;#ro4)nDcNwnT;Rt(X^_{<$y)VA% zbyoJ4S&g|`+--jglUt6TU-x&5t(jcni{^#z!_)J1|Gv^od}?>6=hDBT_$nRlob1%Z z*wicUt@kS)-na0vxGaC{eAna7`}JMx>z#g=%T_6@y0;_k#`YKQqt85VKG=Ih{a<$! zE1QVWtNJhQ^(l4#>%Z66A7E%ma|Ew{5M)r;pni1adM2!W(<{NiYPsB>ZA<(0F&Bogv1+LHMr}!vitx1F8Eac(F3fPJeu2 zSy^$yX|L!{{$?D{=4}((T<_T3_(6T~e#xJ=KG|M){zR1N$Ij>X>-+!YJt=Q_@7~6+ z=I)MQUTG`i|Jm2J6fBZrn6Q85rS1FYvbclV*O1e}R6u(Xm>4)2b}%?ZF5&0;J=y-} zU4$wcetlD?GM?wi^1XL@z)`f-)`GB@!AgY&Mz7lS?u3GzrBKwVKVQ{ z$Ek1Zetp_zDEoM|5L5l33+lITcyebsF?4bG{ER*`|98?qRh|qsh7;NcR2dGuvOiLv z`Pop{f@V*Unqxk{Js3=gf3vhWRKS6$@wDFVHC`>=j&(;x!*A4If1uD{|Nm6@ zz93dc6#))edy~4iTd${?Z@hK(&4q=|-sSfy)35q!*2}??+4_CIvb4iK?x;UjE^Y)%& zHB3{vKYxCHe)_-b8@Fe()LU_vc#Y$NP>1u`m=oW;)oPe1L;xLO{Esr-;9V z>Hgv=ThsadHixk?p1OTye`f0JxyQdxHDGl5bz@_4s2kJo0}~hXGBT_~Pkq7+KfdYL zMXJ7gTD)m?{yxjw`(CYD&GmoZ(HgrO35*OM)P6ME|EO=aV`ezSoY2<7u<<9unj8O5 zZi`|Fc)6O3Q6gOS@>)iQ@D-hlx9y4ClD%@~p6s^F`&%xYiQjiXEn)Jb`OXX#ObRj# zmW&#a3dfe|&d*G{V|v@?zSV7=TbqvPyp2n|b?a6Z1A`+>ncDQDu}lnw3=1}IzTB0j zHfik*`K$FsySSL9^xd;r;LPx0-p9xUMFxk@8~SH4D?AfqW4Lf}A~^Y(6-eEku`@4W zYas(e6NAdXpJpr;odtQIT}cXw3?M5qSr}@L^Y>onU6WSA>}$UA(huGp+5az8y!g}h zO+Cu;uTW0wtiTyRv+y2t>tZL(#{^P8+@tXD14>-z&b5-$roT&AzxooIXv7qu+ z_Q_mMgC~XtesT;l3>RK!F+4c?>AopDgTR%T1&4Pylpm|zRJ*Z%o4x#Vx4-)yKi-?n zW1gwae&tZ=r@_+7`W!wLG$hzahk>+ae-DdjwZ*Oj1z9r%NAD=gu&#K$nZ!TUd z%g$g?P!qb%zTWoHj~#_dj%UNKUu@6i-S9x>^XE5K|8JO={8{vP;p@#WDqehF`r^HR zMmFdO*1b;#~mhK(f@)B32t{87`|;@T%i-W zdqXTUgOjLE!Q1+;tK-)i>54OmPdTA!P#wN|=4H$C)8dsk>PvsESC(Pe_-Cq0qa;Iv z?~9zR#}9s7A=Q=hMRLRbv-|~$o(Wux9XyN*{0ui9TZg5{F%+lopIylmv9RXLGsZbA zA403{ho(Av{+ax7(;^n@J#q|s5B=Ys(iR5q{bOTiD7bs1ke?y*4zq*s1C>LjAN3g< zy#M@WV34&p@iU)s>#TNtyd(GB1-riS_qLzQ QP+@t%6#u%Ivodd1#Pj19A28eR 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