diff --git a/locale/en/pirates.cfg b/locale/en/pirates.cfg index 0f3cfd7e..4872e4ff 100644 --- a/locale/en/pirates.cfg +++ b/locale/en/pirates.cfg @@ -105,6 +105,8 @@ victory_continue_reminder=If you wish to continue the game, click up top. crew_disband_tick_message=The crew will disband in 20 seconds. +private_run_lock_expired=Private lock of __1__ run has expired. Anyone can join this crew now. + plank=__1__ planked __2__! plank_error_invalid_player=Command error: Player is not a crewmember. plank_error_self=Command error: Can't plank yourself. @@ -354,6 +356,7 @@ crew_leave=__1__ left the crew. crew_launch=[__1__] Launched. crew_disband=[__1__] Disbanded after __2__. proposal_propose=__1__ proposed the run __2__ [Capacity __3__]. +proposal_propose_private=__1__ proposed the private run __2__ [Capacity __3__]. proposal_retracted=Proposal __1__ retracted. proposal_abandoned=Proposal __1__ abandoned. @@ -510,6 +513,17 @@ gui_runs_proposal_maker_capacity=Capacity gui_runs_proposal_maker_capacity_disabled=This capacity setting isn't available at the moment. gui_runs_proposal_maker_propose=Propose gui_runs_proposal_maker_no_limit=No limit +gui_runs_proposal_maker_private=Private +gui_runs_proposal_maker_private_tooltip=Sets your run to private, which protects the run by password.\nOnce the run has launched, only people who know password will be able to join the crew.\nPrivate run becomes public if the crew is empty or inactive for __1__ hours. +gui_runs_proposal_maker_password=Password +gui_runs_proposal_maker_confirm_password=Confirm Password +gui_runs_proposal_maker_error_private_run_limit=All private run slots are occupied. Wait until private run slots free up to create your own. +gui_runs_proposal_maker_error_private_run_password_no_match=Passwords do not match. +gui_runs_proposal_maker_error_private_run_password_empty=Passwords can't be empty. +gui_join_private_run_info=This run is private and protected.\nPlease enter a password to join the crew.\nThis run will become public in __1__:__2__:__3__ +gui_join_private_run_error_wrong_password=The password you've entered is incorrect. + + gui_runs_launch=Launch run gui_runs_launch_error_1=Gather endorsements from more pirates. diff --git a/maps/pirates/api_events.lua b/maps/pirates/api_events.lua index 771460c7..1daba961 100644 --- a/maps/pirates/api_events.lua +++ b/maps/pirates/api_events.lua @@ -1384,7 +1384,7 @@ local function event_on_player_joined_game(event) local ages = {} for _, memory in pairs(global_memory.crew_memories) do if Common.is_id_valid(memory.id) - and memory.crewstatus + and (not memory.run_is_private) and memory.crewstatus == Crew.enum.ADVENTURING and memory.capacity and memory.crewplayerindices diff --git a/maps/pirates/api_on_tick.lua b/maps/pirates/api_on_tick.lua index cf138498..b49fb692 100644 --- a/maps/pirates/api_on_tick.lua +++ b/maps/pirates/api_on_tick.lua @@ -1623,4 +1623,22 @@ function Public.revealed_buried_treasure_distance_check() end end +function Public.update_private_run_lock_timer(tickinterval) + local memory = Memory.get_crew_memory() + if memory.run_is_private then + if Common.activecrewcount() <= 0 then + if memory.private_run_lock_timer > 0 then + memory.private_run_lock_timer = memory.private_run_lock_timer - tickinterval + + if memory.private_run_lock_timer <= 0 then + Common.notify_game({'pirates.private_run_lock_expired', memory.name}) + memory.run_is_private = false + end + end + else + memory.private_run_lock_timer = 60 * 60 * 60 * CoreData.private_run_lock_amount_hr + end + end +end + return Public \ No newline at end of file diff --git a/maps/pirates/common.lua b/maps/pirates/common.lua index c5d98e34..9fa3ebde 100644 --- a/maps/pirates/common.lua +++ b/maps/pirates/common.lua @@ -19,6 +19,7 @@ local Public = {} -- Public.active_crews_cap = 1 Public.activeCrewsCap = 2 +Public.private_run_cap = 1 Public.minimumCapacitySliderValue = 1 Public.minimum_run_capacity_to_enforce_space_for = 22 -- auto-disbanding when there are no players left in the crew: diff --git a/maps/pirates/coredata.lua b/maps/pirates/coredata.lua index 93177332..8570f104 100644 --- a/maps/pirates/coredata.lua +++ b/maps/pirates/coredata.lua @@ -20,6 +20,8 @@ Public.total_max_biters = 2000 Public.lobby_surface_name = '000-000-Lobby' +Public.private_run_lock_amount_hr = 24 -- how many hours need to pass, when crew is empty or inactive, until private run becomes public + Public.colors = { coal = {r=0.5, g=0.5, b=0.5}, wood = {r=204, g=158, b=67}, diff --git a/maps/pirates/crew.lua b/maps/pirates/crew.lua index 4069269e..5a15d0d1 100644 --- a/maps/pirates/crew.lua +++ b/maps/pirates/crew.lua @@ -720,6 +720,9 @@ function Public.initialise_crew(accepted_proposal) memory.difficulty = CoreData.difficulty_options[accepted_proposal.difficulty_option].value memory.capacity = CoreData.capacity_options[accepted_proposal.capacity_option].value -- memory.mode = CoreData.mode_options[accepted_proposal.mode_option].value + memory.run_is_private = accepted_proposal.run_is_private + memory.private_run_password = accepted_proposal.private_run_password + memory.private_run_lock_timer = 60 * 60 * 60 * CoreData.private_run_lock_amount_hr memory.destinationsvisited_indices = {} memory.stored_fuel = Balance.starting_fuel diff --git a/maps/pirates/gui/classes.lua b/maps/pirates/gui/classes.lua index 92f9157a..6f39890f 100644 --- a/maps/pirates/gui/classes.lua +++ b/maps/pirates/gui/classes.lua @@ -235,6 +235,9 @@ end function Public.click(event) + if not event.element then return end + if not event.element.valid then return end + local player = game.players[event.element.player_index] if not player.gui.screen[window_name .. '_piratewindow'] then return end diff --git a/maps/pirates/gui/crew.lua b/maps/pirates/gui/crew.lua index 2bb73200..52443db2 100644 --- a/maps/pirates/gui/crew.lua +++ b/maps/pirates/gui/crew.lua @@ -403,6 +403,9 @@ end function Public.click(event) + -- This is only needed since we call click on every single GUI element and if element gets destroyed, it's no good (these checks wouldn't be needed (I think) if GUI was purely event driven) + if not event.element then return end + if not event.element.valid then return end local player = game.players[event.element.player_index] diff --git a/maps/pirates/gui/fuel.lua b/maps/pirates/gui/fuel.lua index 51af525e..6063ea4d 100644 --- a/maps/pirates/gui/fuel.lua +++ b/maps/pirates/gui/fuel.lua @@ -175,6 +175,8 @@ end function Public.click(event) + if not event.element then return end + if not event.element.valid then return end local player = game.players[event.element.player_index] diff --git a/maps/pirates/gui/info.lua b/maps/pirates/gui/info.lua index 316816db..e68f6220 100644 --- a/maps/pirates/gui/info.lua +++ b/maps/pirates/gui/info.lua @@ -186,6 +186,8 @@ end function Public.click(event) + if not event.element then return end + if not event.element.valid then return end local player = game.players[event.element.player_index] -- local name = 'info' diff --git a/maps/pirates/gui/minimap.lua b/maps/pirates/gui/minimap.lua index 2304a25e..4ff8bd2b 100644 --- a/maps/pirates/gui/minimap.lua +++ b/maps/pirates/gui/minimap.lua @@ -149,6 +149,8 @@ end function Public.click(event) + if not event.element then return end + if not event.element.valid then return end local player = game.players[event.element.player_index] diff --git a/maps/pirates/gui/runs.lua b/maps/pirates/gui/runs.lua index f50a3b81..11ba1801 100644 --- a/maps/pirates/gui/runs.lua +++ b/maps/pirates/gui/runs.lua @@ -191,6 +191,25 @@ function Public.toggle_window(player) flow3.style.margin = 2 flow3.style.horizontally_stretchable = true + flow3 = flow2.add({ + name = 'join_crew_info', + type = 'label', + caption = {'pirates.gui_join_private_run_info', 0, 0, 0}, + visible = false, + }) + flow3.style.single_line = false + + flow3 = flow2.add({ + name = 'password_namefield', + type = 'textfield', + text = '', + visible = false, + }) + flow3.style.width = 150 + flow3.style.height = 24 + flow3.style.top_margin = -3 + flow3.style.bottom_margin = 3 + flow3 = flow2.add({ name = 'flow_buttons', type = 'flow', @@ -314,6 +333,52 @@ function Public.toggle_window(player) flow5.style.top_margin = -3 flow5.style.bottom_margin = 3 + -- PRIVATE RUN ELEMENTS -- + + flow5 = flow4.add({ + name = 'private_checkbox', + type = 'checkbox', + caption = {'pirates.gui_runs_proposal_maker_private'}, + state = false, + tooltip = {'pirates.gui_runs_proposal_maker_private_tooltip', CoreData.private_run_lock_amount_hr} + }) + + flow5 = flow4.add({ + name = 'password_label', + type = 'label', + caption = {'pirates.gui_runs_proposal_maker_password'}, + }) + flow5.style.font = 'heading-3' + + flow5 = flow4.add({ + name = 'password_namefield', + type = 'textfield', + text = '', + }) + flow5.style.width = 150 + flow5.style.height = 24 + flow5.style.top_margin = -3 + flow5.style.bottom_margin = 3 + + flow5 = flow4.add({ + name = 'confirm_password_label', + type = 'label', + caption = {'pirates.gui_runs_proposal_maker_confirm_password'}, + }) + flow5.style.font = 'heading-3' + + flow5 = flow4.add({ + name = 'confirm_password_namefield', + type = 'textfield', + text = '', + }) + flow5.style.width = 150 + flow5.style.height = 24 + flow5.style.top_margin = -3 + flow5.style.bottom_margin = 3 + + -- CREW SIZE LIMIT SLIDER -- + flow5 = flow4.add({ name = 'options', type = 'table', @@ -448,6 +513,12 @@ function Public.full_update(player) if not selected_joinable_bool then flow.ongoing_runs.body.ongoing_runs_listbox.selected_index = 0 end flow.ongoing_runs.body.leaving_prompt.visible = playercrew_status.leaving + + + local show_password_info = crewid and global_memory.crew_memories[crewid].run_is_private + + flow.ongoing_runs.body.join_crew_info.visible = show_password_info + flow.ongoing_runs.body.password_namefield.visible = show_password_info end flow.proposals.visible = (memory.crewstatus == nil and not playercrew_status.leaving) @@ -485,6 +556,11 @@ function Public.full_update(player) flow.proposals.body.flow_proposal_launch.launch_crew.visible = playercrew_status.proposal_can_launch + local checkbox_state = flow.proposals.body.proposal_maker.body.private_checkbox.state + flow.proposals.body.proposal_maker.body.password_label.visible = checkbox_state + flow.proposals.body.proposal_maker.body.password_namefield.visible = checkbox_state + flow.proposals.body.proposal_maker.body.confirm_password_label.visible = checkbox_state + flow.proposals.body.proposal_maker.body.confirm_password_namefield.visible = checkbox_state end @@ -504,6 +580,21 @@ function Public.full_update(player) -- wrappedmemories[#wrappedmemories + 1] = {'pirates.run_displayform', mem.id, mem.name, Utils.spritepath_to_richtext(CoreData.difficulty_options[mem.difficulty_option].icon), count, CoreData.capacity_options[mem.capacity_option].text2, ' [item=rail] ', mem.overworldx or 0} end GuiCommon.update_listbox(flow.ongoing_runs.body.ongoing_runs_listbox, wrappedmemories) + + local crewid = nil + local bool1 = (not playercrew_status.leaving) and (not playercrew_status.adventuring) and (not playercrew_status.spectating) and (flow.ongoing_runs.body.ongoing_runs_listbox.selected_index ~= 0) + if bool1 then + crewid = tonumber((flow.ongoing_runs.body.ongoing_runs_listbox.get_item(flow.ongoing_runs.body.ongoing_runs_listbox.selected_index))[2]) + end + + -- Update timer when run will become public + if crewid and flow.ongoing_runs.body.join_crew_info.visible then + local lock_timer = global_memory.crew_memories[crewid].private_run_lock_timer + local sec = Math.floor((lock_timer / (60)) % 60) + local min = Math.floor((lock_timer / (60 * 60)) % 60) + local hrs = Math.floor((lock_timer / (60 * 60 * 60)) % 60) + flow.ongoing_runs.body.join_crew_info.caption = {'pirates.gui_join_private_run_info', hrs, min, sec} + end end if flow.proposals.visible then @@ -542,11 +633,12 @@ function Public.full_update(player) -- end -- end end - end function Public.click(event) + if not event.element then return end + if not event.element.valid then return end local player = game.players[event.element.player_index] @@ -573,12 +665,56 @@ function Public.click(event) if eventname == 'join_crew' then local listbox = flow.ongoing_runs.body.ongoing_runs_listbox + local crewid = tonumber(listbox.get_item(listbox.selected_index)[2]) + + -- If run is private + if global_memory.crew_memories[crewid].run_is_private then + if global_memory.crew_memories[crewid].private_run_password == flow.ongoing_runs.body.password_namefield.text then + Crew.join_crew(player, crewid) + flow.ongoing_runs.body.join_crew_info.visible = false + flow.ongoing_runs.body.password_namefield.visible = false + else + Common.notify_player_error(player, {'pirates.gui_join_private_run_error_wrong_password'}) + end + else + Crew.join_crew(player, crewid) + end - Crew.join_crew(player, tonumber(listbox.get_item(listbox.selected_index)[2])) return end if eventname == 'propose_crew' then + -- If proposal was set as private + local run_is_private = flow.proposals.body.proposal_maker.body.private_checkbox.state + if run_is_private then + -- Count private runs + local private_run_count = 0 + for _, id in pairs(global_memory.crew_active_ids) do + if global_memory.crew_memories[id].run_is_private then + private_run_count = private_run_count + 1 + end + end + + -- Make sure private run can be created + if private_run_count >= global_memory.private_run_cap then + Common.notify_player_error(player, {'pirates.gui_runs_proposal_maker_error_private_run_limit'}) + return + end + + -- Check if passwords match + if flow.proposals.body.proposal_maker.body.password_namefield.text ~= flow.proposals.body.proposal_maker.body.confirm_password_namefield.text then + Common.notify_player_error(player, {'pirates.gui_runs_proposal_maker_error_private_run_password_no_match'}) + return + end + + -- Check if passwords aren't empty + if flow.proposals.body.proposal_maker.body.password_namefield.text == '' then + Common.notify_player_error(player, {'pirates.gui_runs_proposal_maker_error_private_run_password_empty'}) + return + end + end + + local private_run_password = flow.proposals.body.proposal_maker.body.password_namefield.text local proposal_name = flow.proposals.body.proposal_maker.body.namefield.text -- local proposal_name = string.sub(flow.proposals.body.proposal_maker.body.namefield.text, 1, 30) @@ -626,14 +762,19 @@ function Public.click(event) capacity_option = capacity_option, -- mode_option = mode_option, endorserindices = {player.index}, + run_is_private = run_is_private, + private_run_password = private_run_password, } global_memory.crewproposals[#global_memory.crewproposals + 1] = proposal - local message = {'pirates.proposal_propose', player.name, proposal_name, CoreData.capacity_options[capacity_option].text3} + if run_is_private then + Common.notify_lobby({'pirates.proposal_propose_private', player.name, proposal_name, CoreData.capacity_options[capacity_option].text3}) + else + Common.notify_lobby({'pirates.proposal_propose', player.name, proposal_name, CoreData.capacity_options[capacity_option].text3}) + end -- local message = player.name .. ' proposed the run ' .. proposal_name .. ' (difficulty ' .. CoreData.difficulty_options[difficulty_option].text .. ', capacity ' .. CoreData.capacity_options[capacity_option].text3 .. ').' - Common.notify_lobby(message) return end @@ -669,6 +810,22 @@ function Public.click(event) if GuiCommon.crew_overall_state_bools(player.index).proposal_can_launch then --double check for k, proposal in pairs(global_memory.crewproposals) do if #proposal.endorserindices > 0 and proposal.endorserindices[1] == player.index then + -- Make sure private run can be created + if proposal.run_is_private then + -- NOTE: I didn't want to add this check in "proposal_can_launch", because different error message would get displayed (I think?). + local private_run_count = 0 + for _, id in pairs(global_memory.crew_active_ids) do + if global_memory.crew_memories[id].run_is_private then + private_run_count = private_run_count + 1 + end + end + + if private_run_count >= global_memory.private_run_cap then + Common.notify_player_error(player, {'pirates.gui_runs_proposal_maker_error_private_run_limit'}) + return + end + end + Crew.initialise_crew(proposal) global_memory.crewproposals[k] = nil Progression.set_off_from_starting_dock() diff --git a/maps/pirates/highscore.lua b/maps/pirates/highscore.lua index a5c6aed1..f801a9b3 100644 --- a/maps/pirates/highscore.lua +++ b/maps/pirates/highscore.lua @@ -528,12 +528,10 @@ end local score_gui_token = Token.register(score_gui) local function on_gui_click(event) - local element = event.element - if not element or not element.valid then - return - end + if not event.element then return end + if not event.element.valid then return end - local player = game.get_player(element.player_index) + local player = game.get_player(event.element.player_index) local frame = Gui.get_player_active_frame(player) if not frame then diff --git a/maps/pirates/main.lua b/maps/pirates/main.lua index 95e1ab2e..e24be828 100644 --- a/maps/pirates/main.lua +++ b/maps/pirates/main.lua @@ -110,6 +110,8 @@ local function on_init() Common.init_game_settings(Balance.technology_price_multiplier) global_memory.active_crews_cap = Common.activeCrewsCap + global_memory.private_run_cap = Common.private_run_cap + global_memory.minimumCapacitySliderValue = Common.minimumCapacitySliderValue Surfaces.Lobby.create_starting_dock_surface() @@ -184,6 +186,7 @@ local function crew_tick() PiratesApiOnTick.check_for_cliff_explosives_in_hold_wooden_chests() PiratesApiOnTick.equalise_fluid_storages() -- Made the update less often for small performance gain, but frequency can be increased if players complain PiratesApiOnTick.revealed_buried_treasure_distance_check() + PiratesApiOnTick.update_private_run_lock_timer(60) PiratesApiOnTick.victory_continue_reminder() Kraken.overall_kraken_tick()