diff --git a/Gemfile b/Gemfile index 4a15cae3b9..8bff9e8475 100644 --- a/Gemfile +++ b/Gemfile @@ -15,7 +15,7 @@ gem 'rake', '~> 12.3' gem 'rspec', '~> 3.7.0' gem 'timezone', '~> 1.2.10' gem 'dentaku', '~>3.1' -gem 'json', '~> 2.1.0' +gem 'json', '~> 2.3.1' gem 'redcarpet', '~> 3.4.0' gem 'sinatra', '~> 2.0.4' gem 'thin', '~> 1.7.2' diff --git a/Gemfile.lock b/Gemfile.lock index 1a3da4ccea..0d8b804ad9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -46,7 +46,7 @@ GEM i18n (0.9.5) concurrent-ruby (~> 1.0) jmespath (1.4.0) - json (2.1.0) + json (2.3.1) libv8 (3.16.14.19) log4r (1.1.10) minitest (5.14.1) @@ -120,7 +120,7 @@ DEPENDENCIES handlebars (~> 0.8.0) htmlentities (~> 4.3.4) i18n (~> 0.7) - json (~> 2.1.0) + json (~> 2.3.1) log4r (~> 1.1.10) ohm (~> 3.1.1) rack (>= 2.1.4) diff --git a/engine/aresmush/commands/dispatcher.rb b/engine/aresmush/commands/dispatcher.rb index e1dec70c69..8356b78a2a 100644 --- a/engine/aresmush/commands/dispatcher.rb +++ b/engine/aresmush/commands/dispatcher.rb @@ -118,7 +118,7 @@ def on_event(event) AresMUSH.with_error_handling(nil, "Handling #{event_name}.") do handler_class = p.get_event_handler(event_name) if (handler_class) - if (event_name != "CronEvent" && event_name != "ConnectionEstablishedEvent") + if (event_name != "CronEvent" && event_name != "ConnectionEstablishedEvent" && event_name != "PoseEvent") Global.logger.debug "#{handler_class} handling #{event_name}." end handler = handler_class.new diff --git a/engine/styles/ares.scss b/engine/styles/ares.scss index fbfedadc29..d8eb81faff 100644 --- a/engine/styles/ares.scss +++ b/engine/styles/ares.scss @@ -939,8 +939,8 @@ SCENES SCREEN background-color: #337ab7; } -.scene-privacy-limited { - background-color: #f0ad4e; +.scene-privacy-openstar { + background-color: #5bc0de; } .log-box { @@ -1477,6 +1477,14 @@ JOBS SCREEN border-color: #faebcc; } +.job-participant-edit-box { + padding-left: 15px; +} + +.job-template-editor { + margin: 0px; +} + /* ------------------------- OTHER ------------------------- */ diff --git a/install/game.distr/config/fs3skills_misc.yml b/install/game.distr/config/fs3skills_misc.yml index 407134f989..f4a8660b3b 100644 --- a/install/game.distr/config/fs3skills_misc.yml +++ b/install/game.distr/config/fs3skills_misc.yml @@ -25,3 +25,4 @@ fs3skills: fs3_roll: type: fs3 message: Rolled a skill. + job_on_luck_spend: true diff --git a/install/game.distr/config/scenes.yml b/install/game.distr/config/scenes.yml index 9826443b85..262bc299f7 100644 --- a/install/game.distr/config/scenes.yml +++ b/install/game.distr/config/scenes.yml @@ -73,4 +73,4 @@ scenes: - 2 unshared_scene_deletion_days: 20 unshared_scene_warning_days: 13 - use_custom_char_cards: false + use_custom_char_cards: false \ No newline at end of file diff --git a/install/init_db.rb b/install/init_db.rb index ef4c2c6098..02af4cca28 100644 --- a/install/init_db.rb +++ b/install/init_db.rb @@ -137,13 +137,13 @@ def self.init_db announce: false, description: "Public chit-chat", color: "%xy") - channel.default_alias = [ 'c', 'ch', 'cha' ] + channel.default_alias = [ 'ch', 'cha' ] channel.save channel = AresMUSH::Channel.create(name: "Questions", color: "%xg", description: "Questions and answers.") - channel.default_alias = [ 'q', 'qu', 'que' ] + channel.default_alias = [ 'qu', 'que' ] channel.save channel = AresMUSH::Channel.create(name: "Game", @@ -167,7 +167,7 @@ def self.init_db channel = AresMUSH::Channel.create(name: "Admin", description: "Admin business.", color: "%xr") - channel.default_alias = [ 'a', 'ad', 'adm' ] + channel.default_alias = [ 'adm' ] channel.join_roles.add admin_role channel.talk_roles.add admin_role channel.save diff --git a/install/migrations/0061_beta83_update.rb b/install/migrations/0061_beta83_update.rb new file mode 100644 index 0000000000..04005a3ae7 --- /dev/null +++ b/install/migrations/0061_beta83_update.rb @@ -0,0 +1,17 @@ +module AresMUSH + + module Migrations + class MigrationBeta83Update + def require_restart + false + end + + def migrate + Global.logger.debug "Adding luck job option." + config = DatabaseMigrator.read_config_file("fs3skills_misc.yml") + config['fs3skills']['job_on_luck_spend'] = true + config = DatabaseMigrator.write_config_file("fs3skills_misc.yml", config) + end + end + end +end \ No newline at end of file diff --git a/install/scripts/reset_headwiz_password.rb b/install/scripts/reset_headwiz_password.rb index d9935742ef..3d50d79ca1 100644 --- a/install/scripts/reset_headwiz_password.rb +++ b/install/scripts/reset_headwiz_password.rb @@ -13,6 +13,7 @@ module AresMUSH end Game.master.master_admin.change_password(new_password) + Game.master.master_admin.update(login_failures: 0) puts "Script complete! Password is now #{new_password}" end \ No newline at end of file diff --git a/plugins/arescentral/helpers/handle_mgmt.rb b/plugins/arescentral/helpers/handle_mgmt.rb index e5aa82f33c..77c4fabcd5 100644 --- a/plugins/arescentral/helpers/handle_mgmt.rb +++ b/plugins/arescentral/helpers/handle_mgmt.rb @@ -25,6 +25,7 @@ def self.sync_handle(char) char.update(ascii_mode_enabled: response.data["ascii_only"]) char.update(screen_reader: response.data["screen_reader"]) char.handle.update(friends: response.data["friends"]) + char.handle.update(profile: response.data["profile"]) return nil else char.handle.delete diff --git a/plugins/arescentral/locales/locale_en.yml b/plugins/arescentral/locales/locale_en.yml index fa43763eb4..3eef95d7a0 100644 --- a/plugins/arescentral/locales/locale_en.yml +++ b/plugins/arescentral/locales/locale_en.yml @@ -19,4 +19,5 @@ en: game_registered: "You register this game with AresCentral. Note: The game will only appear in the directory if you have the game marked as public in the game configuration." game_not_registered: "This game hasn't been registered with AresCentral, so handle functionality is currently disabled. Please contact your local game admin for more information." - unlink_failed: "There was a problem unlinking the character to your handle: %{error}" \ No newline at end of file + unlink_failed: "There was a problem unlinking the character to your handle: %{error}" + view_full_profile: "View full profile (including other linked characters) at [AresCentral](https://arescentral.aresmush.com/handle/%{name})." \ No newline at end of file diff --git a/plugins/arescentral/public/arescentral_char.rb b/plugins/arescentral/public/arescentral_char.rb index 02477b6485..bfec2c527b 100644 --- a/plugins/arescentral/public/arescentral_char.rb +++ b/plugins/arescentral/public/arescentral_char.rb @@ -4,4 +4,8 @@ def alts AresCentral.alts(self) end end + + class Handle + attribute :profile + end end \ No newline at end of file diff --git a/plugins/arescentral/specs/char_connected_event_specs.rb b/plugins/arescentral/specs/char_connected_event_specs.rb index 2446bd2055..c2996e47d5 100644 --- a/plugins/arescentral/specs/char_connected_event_specs.rb +++ b/plugins/arescentral/specs/char_connected_event_specs.rb @@ -34,7 +34,7 @@ module AresCentral allow(@char).to receive(:handle) { @handle } allow(@char).to receive(:name) { "Bob" } allow(@char).to receive(:id) { 111 } - response = { "status" => "success", "data" => { "linked" => true, "autospace" => "x", "timezone" => "t", "friends" => "f", "quote_color" => "q", "page_color" => "pc", "page_autospace" => "ps", "ascii_only" => true, "screen_reader" => true }} + response = { "status" => "success", "data" => { "linked" => true, "autospace" => "x", "timezone" => "t", "friends" => "f", "quote_color" => "q", "page_color" => "pc", "page_autospace" => "ps", "ascii_only" => true, "screen_reader" => true, "profile" => "handle profile" }} expect(@connector).to receive(:sync_handle).with(123, "Bob", 111) { AresCentral::AresResponse.new(response) } @@ -45,6 +45,7 @@ module AresCentral expect(@char).to receive(:update).with({:timezone => "t"}) expect(@char).to receive(:update).with(ascii_mode_enabled: true) expect(@char).to receive(:update).with(screen_reader: true) + expect(@handle).to receive(:update).with(profile: "handle profile") expect(@handle).to receive(:update).with(friends: "f") expect(@client).to receive(:emit_success).with("arescentral.handle_synced") @handler.on_event(@event) diff --git a/plugins/arescentral/web/get_player_handler.rb b/plugins/arescentral/web/get_player_handler.rb index dd4c48cdd3..cc68be5e72 100644 --- a/plugins/arescentral/web/get_player_handler.rb +++ b/plugins/arescentral/web/get_player_handler.rb @@ -13,6 +13,25 @@ def handle(request) error = Website.check_login(request, true) return error if error + profile = player.profile.each_with_index.map { |(section, data), index| + { + name: section.titlecase, + key: section.parameterize(), + text: Website.format_markdown_for_html(data), + active_class: index == 0 ? 'active' : '' # Stupid bootstrap hack + } + } + if (player.handle && !player.handle.profile.blank?) + handle_profile = "#{player.handle.profile}\n\n----\n*#{t('arescentral.view_full_profile', :name => player.handle.name)}*" + handle_profile_data = { + name: "Handle Profile", + key: "arescentral", + text: Website.format_markdown_for_html(handle_profile), + active_class: profile.empty? ? "active" : "" + } + profile << handle_profile_data + end + { id: player.id, handle: player.handle ? player.handle.name : nil, @@ -21,7 +40,8 @@ def handle(request) alts: player.alts.map { |a| {name: a.name, icon: Website.icon_for_char(a)} }, can_manage: enactor && Profile.can_manage_char_profile?(enactor, player), achievements: Achievements.is_enabled? ? Achievements.build_achievements(player) : nil, - admin_role_title: player.role_admin_note + admin_role_title: player.role_admin_note, + profile: profile } end end diff --git a/plugins/channels/commands/management/channel_attribute_cmd.rb b/plugins/channels/commands/management/channel_attribute_cmd.rb index 157bc6dc42..8a6243fec5 100644 --- a/plugins/channels/commands/management/channel_attribute_cmd.rb +++ b/plugins/channels/commands/management/channel_attribute_cmd.rb @@ -81,9 +81,9 @@ def handle Channels.with_a_channel(name, client) do |channel| if (cmd.switch_is?("joinroles")) - channel.set_join_roles(roles) + channel.set_roles(roles, :join) else - channel.set_talk_roles((roles)) + channel.set_roles(roles, :talk) end Channels.emit_to_channel channel, t('channels.roles_changed_by', :name => enactor_name) diff --git a/plugins/channels/help/en/manage_channels.md b/plugins/channels/help/en/manage_channels.md index 4cca1f50a9..e9b09edd5d 100644 --- a/plugins/channels/help/en/manage_channels.md +++ b/plugins/channels/help/en/manage_channels.md @@ -8,42 +8,30 @@ summary: Managing channels. ## Creating and Deleting Channels -If you have the appropriate permissions, you can create and delete channels. +Channel admins can create, edit and delete channels. Go to [Admin -> Setup -> Setup Channels](/channels-manage). Channel properties include: + +* Name - A unique name for the channel. +* Description - An optional description. +* Default Color - The default channel color, as an ansi code (e.g., \%xh\%xr). Players can set their own custom colors. +* Default Alias - A space-separated list of commands for talking on the channel (e.g., 'c ch cha' for the chat channel). If you don't specify this, the system will default to the first few letters of the channel. +* Join Roles - Players with this [role](/help/roles) can join the channel. Use 'everyone' to allow everybody to talk. +* Talk Roles - Players with this [role](/help/roles) can talk on the channel. Use 'everyone' to allow everybody to talk. + +> **Note:** Take care to avoid ambiguous channel aliases (like 'g' if you have both a Galactica and Global channel) and aliases that overlap with other command (like 'n' for north or 'p' for page). Remember that AresMUSH ignores prefixes like '+' on commands. `channel/create ` `channel/delete ` `channel/rename =` - Be cautious renaming channels since peoples' aliases may no longer make sense. `channel/clear ` - Clears the recall history. - -## Adding and Removing Characters - -Channel administrators can add or remove characters from a channel: - -`channel/addchar =` -`channel/removechar =` - -## Channel Appearance - -You can set a description for the channel (which will appear in the channels list) and the color used for its name. - `channel/describe =` `channel/defaultcolor =` - Sets a channel's default color. - -> **Tip:** Use full ansi code(s) not just the color name. For example: \%xc You can use multiple codes. For example: \%xh\%xr - -## Roles - -You can control who is allowed to use a channel by assigning roles to it. Only people with one of the specified roles will be allowed to join or talk on the channel. - -If you change the roles for an existing channel, anyone who no longer has permission to be there will automatically leave the channel. - `channel/joinroles =` - Use commas to separate multiple roles. Use "none" to clear existing roles. `channel/talkroles =` - Use commas to separate multiple roles. Use "none" to clear existing roles. +`channel/defaultalias =` - Sets the default aliases for a channel. -## Default Aliases - -The system will automatically use the first couple letters of the channel name for its default aliases (if the player doesn't specify their own). You can change this by specifying a list of space-separated names. For example: 'c ch cha' for the chat channel. +## Adding and Removing Characters -`channel/defaultalias =` - Sets the default aliases for a channel. +Channel administrators can add or remove characters from a channel: -> **Tip:** Take care to avoid ambiguous channel aliases (like 'g' if you have both a Galactica and Global channel) and aliases that overlap with other command (like 'n' for north or 'p' for page). Remember that AresMUSH ignores prefixes like '+' on commands. \ No newline at end of file +`channel/addchar =` +`channel/removechar =` \ No newline at end of file diff --git a/plugins/channels/public/channel.rb b/plugins/channels/public/channel.rb index 57c3a42ba1..7437fc2d95 100644 --- a/plugins/channels/public/channel.rb +++ b/plugins/channels/public/channel.rb @@ -82,36 +82,16 @@ def sorted_channel_messages self.channel_messages.to_a.sort_by { |m| m.created_at } end - def set_talk_roles(role_names) - role_names_upcase = role_names.map { |r| r.upcase } - self.talk_roles.each do |r| - if (!role_names_upcase.include?(r.name_upcase)) - self.talk_roles.delete r - end - end - - role_names.each do |r| - role = Role.find_one_by_name(r) - if (!self.talk_roles.include?(role)) - self.talk_roles.add role - end - end - end - - def set_join_roles(role_names) - role_names_upcase = role_names.map { |r| r.upcase } - self.join_roles.each do |r| - if (!role_names_upcase.include?(r.name_upcase)) - self.join_roles.delete r - end - end - + def set_roles(role_names, role_type) + role_list = role_type == :talk ? self.talk_roles : self.join_roles + new_roles = [] role_names.each do |r| role = Role.find_one_by_name(r) - if (!self.join_roles.include?(role)) - self.join_roles.add role + if (role) + new_roles << role end end + role_list.replace new_roles end end end diff --git a/plugins/channels/web/chat_manage_request_handler.rb b/plugins/channels/web/chat_manage_request_handler.rb index 67053f9d23..5b2f1fc94c 100644 --- a/plugins/channels/web/chat_manage_request_handler.rb +++ b/plugins/channels/web/chat_manage_request_handler.rb @@ -14,7 +14,7 @@ def handle(request) id: c.id, name: c.name, color: c.color, - desc: c.description, + desc: Website.format_markdown_for_html(c.description), can_join: c.join_roles.map { |r| r.name }, can_talk: c.talk_roles.map { |r| r.name } }} diff --git a/plugins/channels/web/create_channel_request_handler.rb b/plugins/channels/web/create_channel_request_handler.rb index 74bf26cb53..7e1ac4fa9f 100644 --- a/plugins/channels/web/create_channel_request_handler.rb +++ b/plugins/channels/web/create_channel_request_handler.rb @@ -30,8 +30,8 @@ def handle(request) Global.logger.info "Channel #{name} created by #{enactor.name}." channel = Channel.create(name: name, description: Website.format_input_for_mush(desc), color: color) - channel.set_join_roles(can_join) - channel.set_talk_roles(can_talk) + channel.set_roles(can_join, :join) + channel.set_roles(can_talk, :talk) {} end diff --git a/plugins/channels/web/save_channel_request_handler.rb b/plugins/channels/web/save_channel_request_handler.rb index ff32908d14..964967c75e 100644 --- a/plugins/channels/web/save_channel_request_handler.rb +++ b/plugins/channels/web/save_channel_request_handler.rb @@ -31,8 +31,8 @@ def handle(request) color = "%xh" end - channel.set_join_roles(can_join) - channel.set_talk_roles(can_talk) + channel.set_roles(can_join, :join) + channel.set_roles(can_talk, :talk) channel.update(name: name, color: color, description: desc ? Website.format_input_for_mush(desc) : "") diff --git a/plugins/forum/commands/management/forum_create_category_cmd.rb b/plugins/forum/commands/management/forum_create_category_cmd.rb index c7d5dbe484..3282838f8c 100644 --- a/plugins/forum/commands/management/forum_create_category_cmd.rb +++ b/plugins/forum/commands/management/forum_create_category_cmd.rb @@ -6,7 +6,7 @@ class ForumCreateCategoryCmd attr_accessor :name def parse_args - self.name = titlecase_arg(cmd.args) + self.name = trim_arg(cmd.args) end def required_args diff --git a/plugins/forum/forum.rb b/plugins/forum/forum.rb index 68bc201a53..fc475463f0 100644 --- a/plugins/forum/forum.rb +++ b/plugins/forum/forum.rb @@ -107,6 +107,8 @@ def self.get_web_request_handler(request) return ForumHideRequestHandler when "forumList" return ForumListRequestHandler + when "forumMove" + return ForumMoveTopicRequestHandler when "forumMute" return ForumMuteRequestHandler when "forumPin" @@ -117,6 +119,16 @@ def self.get_web_request_handler(request) return ForumUnreadRequestHandler when "forumRecent" return RecentForumPostsRequestHandler + when "createForum" + return CreateForumRequestHandler + when "deleteForum" + return DeleteForumRequestHandler + when "editForum" + return EditForumRequestHandler + when "manageForum" + return ManageForumRequestHandler + when "saveForum" + return SaveForumRequestHandler when "searchForum" return SearchForumRequestHandler end diff --git a/plugins/forum/help/en/manage_forum.md b/plugins/forum/help/en/manage_forum.md index cff7a5d3f2..763aa526da 100644 --- a/plugins/forum/help/en/manage_forum.md +++ b/plugins/forum/help/en/manage_forum.md @@ -10,38 +10,24 @@ aliases: > **Permission Required:** These commands require the Admin role or the permission: manage\_forum -Those with admin privileges are create and manage categories. +## Managing Categories -## Creating and Deleting Categories +Forum admins can create and delete categories on the web portal. Go to [Admin -> Setup -> Setup Forum](/forum-manage). Category properties include: -Forum admins can create and delete categories. Each category must be uniquely identified by name and can be given a description with the category's purpose. You can change a category's name at any time. +* Name - A unique name for the category. You can change the name at any time. +* Description - Optional description for the category's purpose. +* Order - The order that categories are displayed on the forum index. If there are multiple forums with the same order number, they will be sorted by name. +* Read Roles - People with these [roles](/help/roles) are allowed to see the forum. You can use 'everyone' to let everybody read. +* Write Roles - People with these [roles](/help/roles) are allowed to post and reply to the forum. To prevent abuse, it is advised that you minimally lock each forum to the 'approved' role. `forum/createcat ` `forum/describe =` -`forum/rename =` - Renames a forum. You can use this to change capitalization on the name as well as changing the name itself. +`forum/rename =` `forum/deletecat ` - -## Changing Board Order - -You can change the order that the categories are displayed in by assigning each an order number. - `forum/order =` - -## Access Roles - -Access to forums is done by role. You can define a role in the [Roles System](/help/roles) and then give that role read and/or write permissions to a forum category. - -Posting and replying to a forum requires 'write' permissions. - `forum/readroles =` `forum/writeroles =` -## Archiving a Board - -You can archive the messages from a forum category for offline storage. The default format is suitable for a wiki page. - -`forum/archive ` - Prints out messages so you can log them to a file. - ## Editing, Moving and Deleting Posts You can edit and delete other peoples' posts as well as their own. You can also mass delete posts to clean up categories. @@ -54,4 +40,10 @@ You can edit and delete other peoples' posts as well as their own. You can also Posts can be pinned (or made sticky) so they show up first regardless of when they were posted. -`forum/pin /<#>=` \ No newline at end of file +`forum/pin /<#>=` + +## Archiving a Board + +You can archive the messages from a forum category for offline storage. The default format is suitable for a wiki page. + +`forum/archive ` - Prints out messages so you can log them to a file. diff --git a/plugins/forum/locales/locale_en.yml b/plugins/forum/locales/locale_en.yml index ba2af109a9..fc227cfc36 100644 --- a/plugins/forum/locales/locale_en.yml +++ b/plugins/forum/locales/locale_en.yml @@ -64,4 +64,6 @@ en: forum_unmuted: "Forum notifications have been unmuted." message_pinned: "Message pinned." - message_unpinned: "Message unpinned." \ No newline at end of file + message_unpinned: "Message unpinned." + + name_required: "Category name is required." \ No newline at end of file diff --git a/plugins/forum/public/bbs_board.rb b/plugins/forum/public/bbs_board.rb index 0ab5f937e5..54726d0e0f 100644 --- a/plugins/forum/public/bbs_board.rb +++ b/plugins/forum/public/bbs_board.rb @@ -63,5 +63,17 @@ def last_post_with_activity sorted_posts = self.bbs_posts.to_a.sort_by { |p| p.last_updated } sorted_posts[-1] end + + def set_roles(role_names, role_type) + role_list = role_type == :read ? self.read_roles : self.write_roles + new_roles = [] + role_names.each do |r| + role = Role.find_one_by_name(r) + if (role) + new_roles << role + end + end + role_list.replace new_roles + end end end diff --git a/plugins/forum/web/create_forum_request_handler.rb b/plugins/forum/web/create_forum_request_handler.rb new file mode 100644 index 0000000000..fbae24899f --- /dev/null +++ b/plugins/forum/web/create_forum_request_handler.rb @@ -0,0 +1,49 @@ +module AresMUSH + module Forum + class CreateForumRequestHandler + def handle(request) + enactor = request.enactor + name = request.args[:name] + desc = request.args[:desc] + order = request.args[:order] + can_read = request.args[:can_read] || [] + can_write = request.args[:can_write] || [] + + error = Website.check_login(request) + return error if error + + return { error: t('dispatcher.not_allowed') } if !Forum.can_manage_forum?(enactor) + + if (name.blank?) + return { error: t('forum.name_required')} + end + + if (order.blank?) + order = BbsBoard.all.count + 1 + else + order = order.to_i + end + + other_forum = BbsBoard.named(name) + if (other_forum) + return { error: t('forum.category_already_exists') } + end + + approved_role = Role.find_one_by_name('approved') + if (can_write.empty? && approved_role) + can_write = [ "approved" ] + end + + Global.logger.info "Forum #{name} created by #{enactor.name}." + + forum = BbsBoard.create(name: name, description: Website.format_input_for_mush(desc), order: order) + forum.set_roles(can_read, :read) + forum.set_roles(can_write, :write) + + {} + end + end + end +end + + diff --git a/plugins/forum/web/delete_forum_request_handler.rb b/plugins/forum/web/delete_forum_request_handler.rb new file mode 100644 index 0000000000..ec37e7a310 --- /dev/null +++ b/plugins/forum/web/delete_forum_request_handler.rb @@ -0,0 +1,25 @@ +module AresMUSH + module Forum + class DeleteForumRequestHandler + def handle(request) + enactor = request.enactor + id = request.args[:id] + + error = Website.check_login(request) + return error if error + + return { error: t('dispatcher.not_allowed') } if !Forum.can_manage_forum?(enactor) + + forum = BbsBoard[id] + return { error: t('webportal.not_found') } if !forum + + Global.logger.info "Forum #{forum.name} deleted by #{enactor.name}." + forum.delete + + {} + end + end + end +end + + diff --git a/plugins/forum/web/edit_forum_request_handler.rb b/plugins/forum/web/edit_forum_request_handler.rb new file mode 100644 index 0000000000..a23c3c1d8c --- /dev/null +++ b/plugins/forum/web/edit_forum_request_handler.rb @@ -0,0 +1,32 @@ +module AresMUSH + module Forum + class EditForumRequestHandler + def handle(request) + enactor = request.enactor + id = request.args[:id] + + error = Website.check_login(request) + return error if error + + return { error: t('dispatcher.not_allowed') } if !Forum.can_manage_forum?(enactor) + + forum = BbsBoard[id] + return { error: t('webportal.not_found') } if !forum + + { + category: { + id: forum.id, + name: forum.name, + order: forum.order, + desc: Website.format_input_for_html(forum.description), + can_read: forum.read_roles.map { |r| r.name }, + can_write: forum.write_roles.map { |r| r.name } + }, + roles: Role.all.to_a.sort_by { |r| r.name }.map { |r| r.name } + } + end + end + end +end + + diff --git a/plugins/forum/web/forum_category_request_handler.rb b/plugins/forum/web/forum_category_request_handler.rb index 8a120acbb9..d24b292aa8 100644 --- a/plugins/forum/web/forum_category_request_handler.rb +++ b/plugins/forum/web/forum_category_request_handler.rb @@ -35,7 +35,7 @@ def handle(request) { id: category.id, name: category.name, - description: category.description, + description: Website.format_markdown_for_html(category.description), can_post: Forum.can_write_to_category?(enactor, category), posts: posts, authors: Forum.get_authorable_chars(enactor, category) diff --git a/plugins/forum/web/forum_list_request_handler.rb b/plugins/forum/web/forum_list_request_handler.rb index 523ee83024..5e694ac73c 100644 --- a/plugins/forum/web/forum_list_request_handler.rb +++ b/plugins/forum/web/forum_list_request_handler.rb @@ -13,7 +13,7 @@ def handle(request) .map { |b| { id: b.id, name: b.name, - description: b.description, + description: Website.format_markdown_for_html(b.description), unread: enactor && b.has_unread?(enactor), last_activity: last_activity(b, enactor), can_read: Forum.can_read_category?(enactor, b) diff --git a/plugins/forum/web/forum_manage_request_handler.rb b/plugins/forum/web/forum_manage_request_handler.rb new file mode 100644 index 0000000000..48da86df76 --- /dev/null +++ b/plugins/forum/web/forum_manage_request_handler.rb @@ -0,0 +1,31 @@ +module AresMUSH + module Forum + class ManageForumRequestHandler + def handle(request) + enactor = request.enactor + + error = Website.check_login(request) + return error if error + + return { error: t('dispatcher.not_allowed') } if !Forum.can_manage_forum?(enactor) + + categories = BbsBoard.all.to_a.sort_by { |c| [ c.order, c.name ] }.map { |c| + { + id: c.id, + name: c.name, + order: c.order, + desc: Website.format_markdown_for_html(c.description), + can_read: c.read_roles.map { |r| r.name }, + can_write: c.write_roles.map { |r| r.name } + }} + + { + categories: categories, + roles: Role.all.to_a.sort_by { |r| r.name }.map { |r| r.name } + } + end + end + end +end + + diff --git a/plugins/forum/web/forum_move_topic_request_handler.rb b/plugins/forum/web/forum_move_topic_request_handler.rb new file mode 100644 index 0000000000..65c9210534 --- /dev/null +++ b/plugins/forum/web/forum_move_topic_request_handler.rb @@ -0,0 +1,35 @@ +module AresMUSH + module Forum + class ForumMoveTopicRequestHandler + def handle(request) + + topic_id = request.args[:topic_id] + category_id = request.args[:category_id] + enactor = request.enactor + + error = Website.check_login(request, true) + return error if error + + topic = BbsPost[topic_id.to_i] + if (!topic) + return { error: t('webportal.not_found') } + end + + category = BbsBoard[category_id.to_i] + if (!category) + return { error: t('webportal.not_found') } + end + + if (!Forum.can_write_to_category?(enactor, category)) + return { error: t('dispatcher.not_allowed') } + end + + topic.update(bbs_board: category) + { + post_id: topic.id, + category_id: category.id + } + end + end + end +end \ No newline at end of file diff --git a/plugins/forum/web/forum_topic_request_handler.rb b/plugins/forum/web/forum_topic_request_handler.rb index 0a72ba2967..9ec75a93f7 100644 --- a/plugins/forum/web/forum_topic_request_handler.rb +++ b/plugins/forum/web/forum_topic_request_handler.rb @@ -35,12 +35,22 @@ def handle(request) } } + categories = BbsBoard.all + .select { |cat| Forum.can_write_to_category?(enactor, cat) } + .map { |cat| + { + id: cat.id, + name: cat.name + } + } + { id: topic.id, title: topic.subject, category: { id: category.id, name: category.name }, + categories: categories, date: topic.created_date_str(enactor), author: { name: topic.author_name, diff --git a/plugins/forum/web/save_forum_request_handler.rb b/plugins/forum/web/save_forum_request_handler.rb new file mode 100644 index 0000000000..9e3cdd8a98 --- /dev/null +++ b/plugins/forum/web/save_forum_request_handler.rb @@ -0,0 +1,46 @@ +module AresMUSH + module Forum + class SaveForumRequestHandler + def handle(request) + enactor = request.enactor + id = request.args[:id] + name = request.args[:name] + desc = request.args[:desc] + order = request.args[:order] || "" + can_read = request.args[:can_read] || [] + can_write = request.args[:can_write] || [] + + error = Website.check_login(request) + return error if error + + return { error: t('dispatcher.not_allowed') } if !Forum.can_manage_forum?(enactor) + + forum = BbsBoard[id] + return { error: t('webportal.not_found') } if !forum + + if (name.blank?) + return { error: t('forum.name_required')} + end + + other_forum = BbsBoard.named(name) + if (other_forum && other_forum != forum) + return { error: t('forum.category_already_exists') } + end + + approved_role = Role.find_one_by_name('approved') + if (can_write.empty? && approved_role) + can_write = [ "approved" ] + end + + forum.set_roles(can_read, :read) + forum.set_roles(can_write, :write) + + forum.update(name: name, description: Website.format_input_for_mush(desc), order: order.to_i) + + {} + end + end + end +end + + diff --git a/plugins/fs3combat/commands/general/combat_mod_cmd.rb b/plugins/fs3combat/commands/general/combat_mod_cmd.rb index 64a4a5f62d..52298240a1 100644 --- a/plugins/fs3combat/commands/general/combat_mod_cmd.rb +++ b/plugins/fs3combat/commands/general/combat_mod_cmd.rb @@ -15,6 +15,8 @@ def parse_args self.mod_type = :defense when "lethalmod" self.mod_type = :damage + when "initmod" + self.mod_type = :initiative else self.mod_type = :attack end @@ -37,6 +39,8 @@ def handle combatant.update(defense_mod: self.value) when :damage combatant.update(damage_lethality_mod: self.value) + when :initiative + combatant.update(initiative_mod: self.value) else combatant.update(attack_mod: self.value) end diff --git a/plugins/fs3combat/fs3combat.rb b/plugins/fs3combat/fs3combat.rb index f0d1e80dbb..c2675a657f 100644 --- a/plugins/fs3combat/fs3combat.rb +++ b/plugins/fs3combat/fs3combat.rb @@ -72,7 +72,7 @@ def self.get_cmd_handler(client, cmd, enactor) return CombatListCmd when "ammo" return CombatAmmoCmd - when "attackmod", "defensemod", "lethalmod" + when "attackmod", "defensemod", "lethalmod", "initmod" return CombatModCmd when "armor" return CombatArmorCmd diff --git a/plugins/fs3combat/help/en/combat_org.md b/plugins/fs3combat/help/en/combat_org.md index 2d9782db41..21c7505d21 100644 --- a/plugins/fs3combat/help/en/combat_org.md +++ b/plugins/fs3combat/help/en/combat_org.md @@ -11,6 +11,7 @@ aliases: - combat_attackmod - combat_defensemod - combat_lethalmod +- combat_initmod - combat_team - combat_stop - combat_newturn @@ -41,9 +42,10 @@ This is a quick reference for combat organizer commands. `combat/unko` - Un-KO's someone who shouldn't have been -`combat/attackmod =` - Gives the combatant a modifier to attack -`combat/defensemod =` - Gives the combatant a modifier to defend -`combat/lethalmod =` - Gives the combatant a modifier to lethality on damage TAKEN +`combat/attackmod =` - Gives the combatant a modifier to attack. +`combat/defensemod =` - Gives the combatant a modifier to defend. +`combat/lethalmod =` - Gives the combatant a modifier to lethality on damage TAKEN. +`combat/initmod =` - Gives the combatant a modifier to initiative. `combat/ammo =` - Adjusts remaining ammo. `combat/transfer ` - Transfer organizer powers to another person in combat. diff --git a/plugins/fs3combat/helpers/combatant_helper.rb b/plugins/fs3combat/helpers/combatant_helper.rb index 2cd87c0e09..98ca90ba0e 100644 --- a/plugins/fs3combat/helpers/combatant_helper.rb +++ b/plugins/fs3combat/helpers/combatant_helper.rb @@ -125,9 +125,10 @@ def self.roll_initiative(combatant, ability) action_mod = 3 end weapon_mod = FS3Combat.weapon_stat(combatant.weapon, "init_mod") || 0 - roll = combatant.roll_ability(ability, weapon_mod + action_mod + luck_mod + combatant.total_damage_mod) + gm_mod = combatant.initiative_mod + roll = combatant.roll_ability(ability, weapon_mod + action_mod + luck_mod + combatant.total_damage_mod + gm_mod) - combatant.log "Initiative roll for #{combatant.name} ability=#{ability} action=#{action_mod} weapon=#{weapon_mod} luck=#{luck_mod} roll=#{roll}" + combatant.log "Initiative roll for #{combatant.name} ability=#{ability} action=#{action_mod} weapon=#{weapon_mod} luck=#{luck_mod} gm=#{gm_mod} roll=#{roll}" roll end diff --git a/plugins/fs3combat/public/combatant.rb b/plugins/fs3combat/public/combatant.rb index aa83bfe372..9d9a52f828 100644 --- a/plugins/fs3combat/public/combatant.rb +++ b/plugins/fs3combat/public/combatant.rb @@ -27,7 +27,8 @@ class Combatant < Ohm::Model attribute :damage_lethality_mod, :type => DataType::Integer, :default => 0 attribute :defense_mod, :type => DataType::Integer, :default => 0 attribute :attack_mod, :type => DataType::Integer, :default => 0 - + attribute :initiative_mod, :type => DataType::Integer, :default => 0 + reference :subdued_by, "AresMUSH::Combatant" reference :aim_target, "AresMUSH::Combatant" reference :character, "AresMUSH::Character" diff --git a/plugins/fs3combat/specs/combatant_helper_specs.rb b/plugins/fs3combat/specs/combatant_helper_specs.rb index 364cb87f1d..8a7c73ead7 100644 --- a/plugins/fs3combat/specs/combatant_helper_specs.rb +++ b/plugins/fs3combat/specs/combatant_helper_specs.rb @@ -350,6 +350,7 @@ module FS3Combat allow(@combatant).to receive(:action_klass) { "" } allow(@combatant).to receive(:weapon) { "" } allow(FS3Combat).to receive(:weapon_stat) { 0 } + allow(@combatant).to receive(:initiative_mod) { 0 } end it "should roll the init ability" do @@ -412,6 +413,12 @@ module FS3Combat expect(@combatant).to receive(:roll_ability).with("init", 0) { 2 } expect(FS3Combat.roll_initiative(@combatant, "init")).to eq 2 end + + it "should add in a gm modifier" do + allow(@combatant).to receive(:initiative_mod) { 2 } + expect(@combatant).to receive(:roll_ability).with("init", 2) { 3 } + expect(FS3Combat.roll_initiative(@combatant, "init")).to eq 3 + end end describe :check_ammo do diff --git a/plugins/fs3skills/commands/xp_undo_cmd.rb b/plugins/fs3skills/commands/xp_undo_cmd.rb index 3234fc3ec7..4cdeb4662d 100644 --- a/plugins/fs3skills/commands/xp_undo_cmd.rb +++ b/plugins/fs3skills/commands/xp_undo_cmd.rb @@ -51,7 +51,7 @@ def handle ability.update(last_learned: nil) end end - model.award_xp 1 + model.update(fs3_xp: model.xp + 1) client.emit_success t('fs3skills.xp_undone', :name => model.name, :skill => self.skill) end end diff --git a/plugins/fs3skills/helpers/luck.rb b/plugins/fs3skills/helpers/luck.rb index c9b912d97d..61eff11965 100644 --- a/plugins/fs3skills/helpers/luck.rb +++ b/plugins/fs3skills/helpers/luck.rb @@ -24,9 +24,14 @@ def self.spend_luck(char, reason, scene) end Achievements.award_achievement(char, "fs3_luck_spent") - - category = Jobs.system_category - Jobs.create_job(category, t('fs3skills.luck_job_title', :name => char.name), message, Game.master.system_character) + + if (Global.read_config('fs3skills', 'job_on_luck_spend')) + category = Jobs.system_category + status = Jobs.create_job(category, t('fs3skills.luck_job_title', :name => char.name), message, Game.master.system_character) + if (status[:job]) + Jobs.close_job(Game.master.system_character, status[:job]) + end + end Global.logger.info "#{char.name} spent luck on #{reason}." end diff --git a/plugins/fs3skills/helpers/parse_rolls.rb b/plugins/fs3skills/helpers/parse_rolls.rb index ff6264956b..cbaf2bd8dc 100644 --- a/plugins/fs3skills/helpers/parse_rolls.rb +++ b/plugins/fs3skills/helpers/parse_rolls.rb @@ -51,8 +51,7 @@ def self.parse_and_roll(char, roll_str) # Parses a roll string in the form Ability+Attr(+ or -)Modifier, where # everything except "Ability" is optional. - # Technically it can be Ability+Ability, or Attribute+Attribute or Attribute+Ability; - # the code doesn't care. + # Can also do Attr+Attr or Attr+Attr. def self.parse_roll_params(str) match = /^(?[^\+\-]+)\s*(?[\+]\s*[A-Za-z\s]+)?\s*(?[\+\-]\s*\d+)?$/.match(str) return nil if !match @@ -69,6 +68,13 @@ def self.parse_roll_params(str) linked_attr = tmp end + if (linked_attr) + ability_type = FS3Skills.get_ability_type(linked_attr) + if (ability_type != :attribute) + return nil + end + end + return RollParams.new(ability, modifier, linked_attr) end diff --git a/plugins/fs3skills/helpers/xp.rb b/plugins/fs3skills/helpers/xp.rb index 6b7b642872..589c2eeae6 100644 --- a/plugins/fs3skills/helpers/xp.rb +++ b/plugins/fs3skills/helpers/xp.rb @@ -104,7 +104,10 @@ def self.learn_ability(char, name) def self.create_xp_job(char, ability) message = t('fs3skills.xp_raised_job', :name => char.name, :ability => ability.name, :rating => ability.rating) category = Jobs.system_category - Jobs.create_job(category, t('fs3skills.xp_job_title', :name => char.name), message, Game.master.system_character) + status = Jobs.create_job(category, t('fs3skills.xp_job_title', :name => char.name), message, Game.master.system_character) + if (status[:job]) + Jobs.close_job(Game.master.system_character, status[:job]) + end end def self.max_dots_in_action diff --git a/plugins/fs3skills/locales/locale_en.yml b/plugins/fs3skills/locales/locale_en.yml index ec05bf78f1..09e8e741f9 100644 --- a/plugins/fs3skills/locales/locale_en.yml +++ b/plugins/fs3skills/locales/locale_en.yml @@ -67,7 +67,7 @@ en: simple_roll_result: "%xb%xn %{name} rolls %{roll}: %{success} (%{dice})" opposed_roll_result: "%xb%xn %{name1} rolls %{roll1} (%{dice1}) vs %{name2}'s %{roll2} (%{dice2})%R%xb%xn%T%T%{result}" - unknown_roll_params: "Unable to figure out what you're trying to roll." + unknown_roll_params: "Can't figure out what you're trying to roll. You can specify a single ability, an optional ruling attribute, and an optional modifier. See 'help roll' for more details." npc: "%{name} (a NPC)" numbers_only_for_npc_skills: "Use a number for a NPC skill, like Badguy/3." diff --git a/plugins/fs3skills/specs/parse_roll_specs.rb b/plugins/fs3skills/specs/parse_roll_specs.rb index 4d1dbd953e..d11eb1af18 100644 --- a/plugins/fs3skills/specs/parse_roll_specs.rb +++ b/plugins/fs3skills/specs/parse_roll_specs.rb @@ -33,12 +33,12 @@ module FS3Skills describe :can_parse_roll_params do before do allow(FS3Skills).to receive(:get_ability_type) { :action } + allow(FS3Skills).to receive(:get_ability_type).with("ATTR") { :attribute } end it "should handle attribute by itself" do - allow(FS3Skills).to receive(:get_ability_type).with("A") { :attribute } - params = FS3Skills.parse_roll_params("A") - check_params(params, "A", 0, nil) + params = FS3Skills.parse_roll_params("ATTR") + check_params(params, "ATTR", 0, nil) end it "should handle abiliity and positive modifier" do @@ -67,38 +67,48 @@ module FS3Skills end it "should handle ability with modifier and linked attr and space" do - params = FS3Skills.parse_roll_params("A B + C D + 3") - check_params(params, "A B", 3, "C D") + allow(FS3Skills).to receive(:get_ability_type).with("AT TR") { :attribute } + params = FS3Skills.parse_roll_params("A B + AT TR + 3") + check_params(params, "A B", 3, "AT TR") end it "should handle ability and linked attr" do - params = FS3Skills.parse_roll_params("A+B") - check_params(params, "A", 0, "B") + params = FS3Skills.parse_roll_params("A+ATTR") + check_params(params, "A", 0, "ATTR") end it "should handle ability and linked attr and modifier" do - params = FS3Skills.parse_roll_params("A+B-2") - check_params(params, "A", -2, "B") + params = FS3Skills.parse_roll_params("A+ATTR-2") + check_params(params, "A", -2, "ATTR") end it "should handle bad string with negative ruling attr" do - params = FS3Skills.parse_roll_params("A-B+2") + params = FS3Skills.parse_roll_params("A-ATTR+2") expect(params).to be_nil end it "should swap attr and ability if backwards" do - allow(FS3Skills).to receive(:get_ability_type).with("Y") { :attribute } - params = FS3Skills.parse_roll_params("Y+X") - check_params(params, "X", 0, "Y") + params = FS3Skills.parse_roll_params("ATTR+X") + check_params(params, "X", 0, "ATTR") + end + + it "should not allow two abilities" do + params = FS3Skills.parse_roll_params("A+B") + expect(params).to be_nil + end + + it "should allow two attributes" do + params = FS3Skills.parse_roll_params("ATTR+ATTR") + check_params(params, "ATTR", 0, "ATTR") end it "should handle bad string with a non-digit modifier" do - params = FS3Skills.parse_roll_params("A+B+C") + params = FS3Skills.parse_roll_params("A+ATTR+C") expect(params).to be_nil end it "should handle bad string with too many params" do - params = FS3Skills.parse_roll_params("A+B+2+D") + params = FS3Skills.parse_roll_params("A+ATTR+2+D") expect(params).to be_nil end end diff --git a/plugins/jobs/commands/management/job_delete_category_cmd.rb b/plugins/jobs/commands/management/job_delete_category_cmd.rb index 0f06924ef1..5c677228db 100644 --- a/plugins/jobs/commands/management/job_delete_category_cmd.rb +++ b/plugins/jobs/commands/management/job_delete_category_cmd.rb @@ -25,6 +25,11 @@ def handle return end + if (JobCategory.all.count == 1) + client.emit_failure t('jobs.cant_delete_only_category') + return + end + category.delete client.emit_success t('jobs.category_deleted') end diff --git a/plugins/jobs/help/en/jobs.md b/plugins/jobs/help/en/jobs.md index 41a6474deb..d1b186a35c 100644 --- a/plugins/jobs/help/en/jobs.md +++ b/plugins/jobs/help/en/jobs.md @@ -34,9 +34,11 @@ The Jobs system is used by the game administrators to track work requests and to ## Changing Job Status -`job/assign <#>=` `job/handle <#>` -`job/status <#>=` `job/cat <#>=` -`job/title <#>=` +`job/handle <#>` - Assign a job to yourself. +`job/assign <#>=<player>` - Assign a job to someone else. +`job/status <#>=<status>` - Change job status. +`job/cat <#>=<category>` - Change job category. +`job/title <#>=<title>` - Change job title. `jobs/catchup` - Marks all jobs as read. `jobs/catchup <number>` - Mark a specific job as read. diff --git a/plugins/jobs/help/en/manage_jobs.md b/plugins/jobs/help/en/manage_jobs.md index 79e692a2ef..b72b580005 100644 --- a/plugins/jobs/help/en/manage_jobs.md +++ b/plugins/jobs/help/en/manage_jobs.md @@ -3,31 +3,23 @@ toc: ~admin~ Managing the Game summary: Managing the jobs system. --- +# Managing Job Categories + For general help on the jobs system, see [Jobs](/help/jobs). > **Permission Required:** These commands require the Admin role or the permission: manage\_jobs -You can use in-game commands to manage your job categories. +Job admins can create, edit and delete job categories. Go to [Admin -> Setup -> Setup Job Categories](/jobcat-manage). Job category properties include: + +* Name - A unique name for the category. Job categories will automatically be all-uppercase. +* Color - The color for the in-game job display, as an ansi code (e.g., \%xh\%xr). For the colors on the web portal, you can use custom CSS as described in the configuration tutorial on https://aresmush.com/tutorials/config/jobs.html#categories). +* Roles - All admins have access to all jobs. You can also allow other roles (such as builders or app staff) access to specific job categories. +* Template - Each category may have a template, which is used as starter text when someone creates a job with that category from the web portal. You can use this to highlight information that you want to see in that kind of job. `job/categories` - Lists categories. `job/createcategory <name>` - Creates a new category. `job/deletecategory <name>` - Deletes a category. You must first move jobs to a different category. `job/renamecategory <old name>=<new name>` - Renames a category. - -## Category Color - -You can give each category a color (used by the in-game job display; for web colors you can use custom CSS as described in the configuration tutorial on https://aresmush.com/tutorials/config/jobs.html) - `job/categorycolor <name>=<ansi code>` - Sets the category color. - -## Category Roles - -All game admins have access to all jobs. You can allow other roles (such as builders or app staff) access to specific job categories as well. - `job/categoryroles <name>=<comma-separated roles list>` - Allow roles to access jobs in a category. Game admins always have access; there is no need to specify them. - -## Category Templates - -Each category may have a template, which is used as starter text when someone creates a job with that category from the web portal. You can use this to highlight information that you want to see in that kind of job. - `job/categorytemplate <name>=<text>` - Sets default job text for a category. \ No newline at end of file diff --git a/plugins/jobs/jobs.rb b/plugins/jobs/jobs.rb index 8dfdc1b7b3..af9cb3821e 100644 --- a/plugins/jobs/jobs.rb +++ b/plugins/jobs/jobs.rb @@ -138,6 +138,16 @@ def self.get_web_request_handler(request) return JobCreateRequestHandler when "jobReply" return JobReplyRequestHandler + when "jobCategoryCreate" + return CreateJobCategoryRequestHandler + when "jobCategoryDelete" + return DeleteJobCategoryRequestHandler + when "jobCategoryEdit" + return EditJobCategoryRequestHandler + when "jobCategorySave" + return SaveJobCategoryRequestHandler + when "jobCategoriesManage" + return ManageJobCategoriesRequestHandler when "jobClose" return JobCloseRequestHandler when "jobDeleteReply" diff --git a/plugins/jobs/locales/locale_en.yml b/plugins/jobs/locales/locale_en.yml index 1f282dde1e..d05a23a342 100644 --- a/plugins/jobs/locales/locale_en.yml +++ b/plugins/jobs/locales/locale_en.yml @@ -98,4 +98,7 @@ en: cant_view_job: "You don't have permission to view that job." jobs_subscription_on: "New job notices on." - jobs_subscription_off: "New job notices off." \ No newline at end of file + jobs_subscription_off: "New job notices off." + + category_name_required: "Category name is required." + cant_delete_only_category: "Can't delete the only job category." \ No newline at end of file diff --git a/plugins/jobs/public/job_category.rb b/plugins/jobs/public/job_category.rb index 1182d9b165..d6212e062a 100644 --- a/plugins/jobs/public/job_category.rb +++ b/plugins/jobs/public/job_category.rb @@ -18,6 +18,18 @@ class JobCategory < Ohm::Model def save_upcase self.name_upcase = self.name ? self.name.upcase : nil end + + def set_roles(role_names) + new_roles = [] + role_names.each do |r| + role = Role.find_one_by_name(r) + if (role) + new_roles << role + end + end + self.roles.replace new_roles + end + end end \ No newline at end of file diff --git a/plugins/jobs/web/create_job_category_request_handler.rb b/plugins/jobs/web/create_job_category_request_handler.rb new file mode 100644 index 0000000000..ba0859eb9d --- /dev/null +++ b/plugins/jobs/web/create_job_category_request_handler.rb @@ -0,0 +1,36 @@ +module AresMUSH + module Jobs + class CreateJobCategoryRequestHandler + def handle(request) + enactor = request.enactor + name = (request.args[:name] || "").upcase + color = request.args[:color] + roles = request.args[:roles] || [] + template = request.args[:template] || "" + + error = Website.check_login(request) + return error if error + + return { error: t('dispatcher.not_allowed') } if !Jobs.can_manage_jobs?(enactor) + + if (name.blank?) + return { error: t('jobs.category_name_required')} + end + + other_cat = JobCategory.named(name) + if (other_cat) + return { error: t('jobs.category_already_exists') } + end + + Global.logger.info "Job Category #{name} created by #{enactor.name}." + + category = JobCategory.create(name: name, color: color, template: template) + category.set_roles(roles) + + {} + end + end + end +end + + diff --git a/plugins/jobs/web/delete_job_category_request_handler.rb b/plugins/jobs/web/delete_job_category_request_handler.rb new file mode 100644 index 0000000000..4ba1e2eb87 --- /dev/null +++ b/plugins/jobs/web/delete_job_category_request_handler.rb @@ -0,0 +1,33 @@ +module AresMUSH + module Jobs + class DeleteJobCategoryRequestHandler + def handle(request) + enactor = request.enactor + id = request.args[:id] + + error = Website.check_login(request) + return error if error + + return { error: t('dispatcher.not_allowed') } if !Jobs.can_manage_jobs?(enactor) + + category = JobCategory[id] + return { error: t('webportal.not_found') } if !category + + if (JobCategory.all.count == 1) + return { error: t('jobs.cant_delete_only_category') } + end + + if (category.jobs.count > 0) + return { error: t('jobs.only_delete_empty_categories') } + end + + Global.logger.info "Job Category #{category.name} deleted by #{enactor.name}." + category.delete + + {} + end + end + end +end + + diff --git a/plugins/jobs/web/edit_job_category_request_handler.rb b/plugins/jobs/web/edit_job_category_request_handler.rb new file mode 100644 index 0000000000..26e62a697f --- /dev/null +++ b/plugins/jobs/web/edit_job_category_request_handler.rb @@ -0,0 +1,31 @@ +module AresMUSH + module Jobs + class EditJobCategoryRequestHandler + def handle(request) + enactor = request.enactor + id = request.args[:id] + + error = Website.check_login(request) + return error if error + + return { error: t('dispatcher.not_allowed') } if !Jobs.can_manage_jobs?(enactor) + + category = JobCategory[id] + return { error: t('webportal.not_found') } if !category + + { + category: { + id: category.id, + name: category.name, + color: category.color, + roles: category.roles.map { |r| r.name }, + template: Website.format_input_for_html(category.template) + }, + roles: Role.all.to_a.sort_by { |r| r.name }.map { |r| r.name } + } + end + end + end +end + + diff --git a/plugins/jobs/web/manage_job_categories_request_handler.rb b/plugins/jobs/web/manage_job_categories_request_handler.rb new file mode 100644 index 0000000000..da9fb3b516 --- /dev/null +++ b/plugins/jobs/web/manage_job_categories_request_handler.rb @@ -0,0 +1,29 @@ +module AresMUSH + module Jobs + class ManageJobCategoriesRequestHandler + def handle(request) + enactor = request.enactor + + error = Website.check_login(request) + return error if error + + return { error: t('dispatcher.not_allowed') } if !Jobs.can_manage_jobs?(enactor) + + categories = JobCategory.all.to_a.sort_by { |c| c.name }.map { |c| + { + id: c.id, + name: c.name, + color: c.color, + roles: c.roles.map { |r| r.name } + }} + + { + categories: categories, + roles: Role.all.to_a.sort_by { |r| r.name }.map { |r| r.name } + } + end + end + end +end + + diff --git a/plugins/jobs/web/save_job_category_request_handler.rb b/plugins/jobs/web/save_job_category_request_handler.rb new file mode 100644 index 0000000000..e2f8b56295 --- /dev/null +++ b/plugins/jobs/web/save_job_category_request_handler.rb @@ -0,0 +1,39 @@ +module AresMUSH + module Jobs + class SaveJobCategoryRequestHandler + def handle(request) + enactor = request.enactor + id = request.args[:id] + name = (request.args[:name] || "").upcase + color = request.args[:color] + roles = request.args[:roles] || [] + template = request.args[:template] || "" + + error = Website.check_login(request) + return error if error + + return { error: t('dispatcher.not_allowed') } if !Jobs.can_manage_jobs?(enactor) + + category = JobCategory[id] + return { error: t('webportal.not_found') } if !category + + if (name.blank?) + return { error: t('jobs.category_name_required')} + end + + other_cat = JobCategory.named(name) + if (other_cat && other_cat != category) + return { error: t('jobs.category_already_exists') } + end + + category.update(name: name, color: color, template: Website.format_input_for_mush(template)) + category.set_roles(roles) + + + {} + end + end + end +end + + diff --git a/plugins/page/helpers.rb b/plugins/page/helpers.rb index c197c3bf0e..bf8bf30cc9 100644 --- a/plugins/page/helpers.rb +++ b/plugins/page/helpers.rb @@ -32,6 +32,7 @@ def self.format_recipient_indicator(recipients) return t('page.recipient_indicator', :recipients => names.join(", ")) end + # NO LONGER USED. Keeping for reference. def self.send_afk_message(client, other_client, other_char) if (!other_client) #client.emit_ooc t('page.recipient_is_offline', :name => other_char.name) diff --git a/plugins/profile/commands/relationships_order_cmd.rb b/plugins/profile/commands/relationships_order_cmd.rb index bb0e2b9f38..48ba3265e1 100644 --- a/plugins/profile/commands/relationships_order_cmd.rb +++ b/plugins/profile/commands/relationships_order_cmd.rb @@ -19,8 +19,9 @@ def check_for_commas end def handle - enactor.update(relationships_category_order: self.list) - client.emit_success t('profile.relationships_order', :categories => self.list.join(",")) + order = self.list.map { |o| o.strip } + enactor.update(relationships_category_order: order) + client.emit_success t('profile.relationships_order', :categories => order.join(",")) end end end diff --git a/plugins/profile/web/profile_edit_request_handler.rb b/plugins/profile/web/profile_edit_request_handler.rb index b44bf8c398..0919caa265 100644 --- a/plugins/profile/web/profile_edit_request_handler.rb +++ b/plugins/profile/web/profile_edit_request_handler.rb @@ -68,6 +68,7 @@ def handle(request) name: char.name, demographics: demographics, background: Website.format_input_for_html(char.background), + is_profile_manager: manager, rp_hooks: Website.format_input_for_html(char.rp_hooks), desc: Website.format_input_for_html(char.description), shortdesc: char.shortdesc ? char.shortdesc : '', diff --git a/plugins/profile/web/profile_save_request_handler.rb b/plugins/profile/web/profile_save_request_handler.rb index 4ddb083f81..59fd9b6d7d 100644 --- a/plugins/profile/web/profile_save_request_handler.rb +++ b/plugins/profile/web/profile_save_request_handler.rb @@ -13,17 +13,19 @@ def handle(request) return { error: t('webportal.not_found') } end + manager = Profile.can_manage_profiles?(enactor) + if (!Profile.can_manage_char_profile?(enactor, char)) return { error: t('dispatcher.not_allowed') } end - - if (!char.is_approved?) + + if (!char.is_approved? && !manager) return { error: t('profile.not_yet_approved') } end request.args[:demographics].each do |name, value| if (value.blank? && Demographics.required_demographics.include?(name)) - return { error: t('webportal.missing_required_fields') } + return { error: t('webportal.missing_required_field', :name => name) } end if (name == 'birthdate') # Standard db format @@ -50,6 +52,10 @@ def handle(request) char.update(profile_gallery: gallery) char.update(profile_tags: tags) + if (manager) + char.update(cg_background: Website.format_input_for_mush(request.args[:background])) + end + relationships = {} (request.args[:relationships] || {}).each do |name, data| relationships[name] = { @@ -66,7 +72,7 @@ def handle(request) char.update(bg_shared: request.args[:bg_shared].to_bool) char.update(idle_lastwill: Website.format_input_for_mush(request.args[:lastwill])) - relation_category_order = (request.args[:relationships_category_order] || "").split(',') + relation_category_order = (request.args[:relationships_category_order] || "").split(',').map { |o| o.strip } char.update(relationships_category_order: relation_category_order) Describe.save_web_descs(char, request.args['descs']) diff --git a/plugins/scenes/commands/scene_info_cmd.rb b/plugins/scenes/commands/scene_info_cmd.rb index abfc4fc69a..111f237c9d 100644 --- a/plugins/scenes/commands/scene_info_cmd.rb +++ b/plugins/scenes/commands/scene_info_cmd.rb @@ -53,7 +53,7 @@ def handle when "type" success = set_type(scene) - when "limit" + when "limit", "note", "notes" scene.update(limit: self.value.downcase == "none" ? nil : self.value) success = true end diff --git a/plugins/scenes/help/en/scenes.md b/plugins/scenes/help/en/scenes.md index 77559bc5f8..795a405393 100644 --- a/plugins/scenes/help/en/scenes.md +++ b/plugins/scenes/help/en/scenes.md @@ -32,7 +32,7 @@ While you can always do free-form RP on the grid or in the RP room, there are a `scene/start` - Starts a scene in your current room. `scene/start [<area>/]<location name>=<private/open>` - Starts a scene in a temp room. `scene/webstart [<area>/]<location name>=<private/open>` - Starts a scene that you intend to play on the web, and doesn't move you there. -`scene/limit [scene#]=<limit notice>` - Indicates that an open scene has some participation limit - by quantity, character type, etc. +`scene/notes [scene#]=<notes>` - Notes for those joining a scene, like participation limit - by quantity, character type, etc. or other special considerations. ## Joining and Leaving Scenes diff --git a/plugins/scenes/help/en/scenes_tutorial.md b/plugins/scenes/help/en/scenes_tutorial.md index 60db258e5c..94c9a95c1e 100644 --- a/plugins/scenes/help/en/scenes_tutorial.md +++ b/plugins/scenes/help/en/scenes_tutorial.md @@ -38,7 +38,7 @@ A scene can either be open (anyone's invited) or private. Scenes on the grid ar > **Note:** Ares has no built-in commands to support admins spying on players. Just as with any online service, though, **any** data transmitted to the server and/or stored in the database is ultimately accessible to the game owner and anyone they choose to share it with. See [Privacy](/help/privacy). -An open scene can include a 'limited participation' notice. You can use this to note that a scene is open only to certain types of characters (Imperials only, Viper Pilots only, etc.), only a certain number of characters, or even to note that players can just come and watch. This is an advisory only--the scene is still open in all meaningful respects, it just alerts players that there may be some conditions to participating. +An open scene can include a note if there are any special considerations - like if a scene is open only to certain types of characters (Imperials only, Viper Pilots only, etc.), only a certain number of characters, or even to note that players can just come and watch. This is an advisory only--the scene is still open in all meaningful respects, it just alerts players that there may be some conditions to participating. ## Joining Scenes diff --git a/plugins/scenes/locales/locale_en.yml b/plugins/scenes/locales/locale_en.yml index 0f15523fa6..6e3b43f018 100644 --- a/plugins/scenes/locales/locale_en.yml +++ b/plugins/scenes/locales/locale_en.yml @@ -34,8 +34,8 @@ en: scene_nag: "There's no scene running here. Starting a scene will enable repose, playing from the web portal, and other features. Just do `scene/start` if you want to start one." open: "Open" private: "Private" - limited: "Limited" - limited_participation: "Limited Participation" + limited: "Open*" + scene_notes: "Notes" scene_already_going: "There's already a scene going in this room. You have to stop it first." private_scene_in_public_room: "Having a private scene on the public grid is poor form, unless of course you're in a private residence." amended_pose: "%{name} has amended %{pronoun} last pose." diff --git a/plugins/scenes/public/scene.rb b/plugins/scenes/public/scene.rb index bf2de0b7a4..5efa9b473e 100644 --- a/plugins/scenes/public/scene.rb +++ b/plugins/scenes/public/scene.rb @@ -86,7 +86,7 @@ def delete_poses_and_log end def all_info_set? - missing_fields = self.title.blank? || self.location.blank? || self.scene_type.blank? || self.summary.blank? + missing_fields = self.title.blank? || self.location.blank? || self.scene_type.blank? || self.summary.blank? || self.icdate.blank? !missing_fields end @@ -147,7 +147,7 @@ def url "#{Game.web_portal_url}/scene/#{self.id}" end - def limited_participation? + def has_notes? !self.limit.blank? end diff --git a/plugins/scenes/scenes.rb b/plugins/scenes/scenes.rb index c3f10b8928..5ee9a7276d 100644 --- a/plugins/scenes/scenes.rb +++ b/plugins/scenes/scenes.rb @@ -80,7 +80,7 @@ def self.get_cmd_handler(client, cmd, enactor) return SceneHomeCmd when "join" return SceneJoinCmd - when "location", "privacy", "summary", "title", "type", "icdate", "plot", "limit" + when "location", "privacy", "summary", "title", "type", "icdate", "plot", "limit", "notes", "note" return SceneInfoCmd when "delete" return SceneDeleteCmd diff --git a/plugins/scenes/templates/scenes_list.erb b/plugins/scenes/templates/scenes_list.erb index 261ccb4524..f554275a9a 100644 --- a/plugins/scenes/templates/scenes_list.erb +++ b/plugins/scenes/templates/scenes_list.erb @@ -7,8 +7,8 @@ <% if scene.summary && !scene.private_scene %> <%= summary(scene) %> <% end %> -<% if scene.limited_participation? %> -%xh%xy<%= t('scenes.limited_participation') %>:%xn <%= scene.limit %> +<% if scene.has_notes? %> +%xh%xc<%= t('scenes.scene_notes') %>:%xn <%= scene.limit %> <% end %> <% end %> <%= divider %> diff --git a/plugins/scenes/templates/scenes_list_template.rb b/plugins/scenes/templates/scenes_list_template.rb index b2b9702ba5..68ded08772 100644 --- a/plugins/scenes/templates/scenes_list_template.rb +++ b/plugins/scenes/templates/scenes_list_template.rb @@ -51,8 +51,8 @@ def privacy(scene) if (scene.private_scene) color = "%xr" message = t('scenes.private') - elsif (scene.limited_participation?) - color = "%xy" + elsif (scene.has_notes?) + color = "%xc" message = t('scenes.limited') else color = "%xg" diff --git a/plugins/scenes/web/live_scenes_handler.rb b/plugins/scenes/web/live_scenes_handler.rb index f6b37bc80c..12cd827cd2 100644 --- a/plugins/scenes/web/live_scenes_handler.rb +++ b/plugins/scenes/web/live_scenes_handler.rb @@ -31,7 +31,7 @@ def handle(request) last_posed: s.last_posed == p }}, scene_type: s.scene_type ? s.scene_type.titlecase : 'Unknown', likes: s.likes, - is_unread: can_read?(enactor, s) && s.participants.include?(enactor) && s.is_unread?(enactor), + is_unread: can_read?(enactor, s) && enactor && s.participants.include?(enactor) && s.is_unread?(enactor), updated: can_read?(enactor, s) ? OOCTime.local_long_timestr(enactor, s.last_activity) : nil, watching: Scenes.is_watching?(s, enactor), participating: Scenes.is_participant?(s, enactor), @@ -56,7 +56,7 @@ def handle(request) end def can_read?(enactor, s) - enactor && Scenes.can_read_scene?(enactor, s) + Scenes.can_read_scene?(enactor, s) end def sort_scene(s1, s2, enactor) diff --git a/plugins/status/events/afk_cron_handler.rb b/plugins/status/events/afk_cron_handler.rb index f4cd33a94a..5572e5ebac 100644 --- a/plugins/status/events/afk_cron_handler.rb +++ b/plugins/status/events/afk_cron_handler.rb @@ -1,16 +1,15 @@ module AresMUSH module Status class AfkCronHandler - include CommandHandler - def on_cron_event(event) - config = Global.read_config("status") - return if !Cron.is_cron_match?(config['afk_cron'], event.time) + def on_event(event) + #return if !Cron.is_cron_match?(Global.read_config('status', 'afk_cron'), event.time) + + minutes_before_idle_disconnect = "#{Global.read_config('status', 'minutes_before_idle_disconnect')}".to_i + return if !minutes_before_idle_disconnect Global.client_monitor.logged_in_clients.each do |client| - minutes_before_idle_disconnect = config['minutes_before_idle_disconnect'] - if (minutes_before_idle_disconnect && - (client.idle_secs > minutes_before_idle_disconnect * 60)) + if (client.idle_secs > minutes_before_idle_disconnect * 60) client.emit_ooc t('status.afk_disconnect') client.disconnect end diff --git a/plugins/status/public/status_api.rb b/plugins/status/public/status_api.rb index f789618982..879ac4935f 100644 --- a/plugins/status/public/status_api.rb +++ b/plugins/status/public/status_api.rb @@ -9,7 +9,7 @@ def self.status_color(status) end def self.is_idle?(client) - minutes_before_idle = Global.read_config("status", "minutes_before_idle") + minutes_before_idle = "#{Global.read_config("status", "minutes_before_idle")}".to_i return false if !minutes_before_idle return client.idle_secs > minutes_before_idle * 60 end diff --git a/plugins/status/status.rb b/plugins/status/status.rb index 63f5c6ed38..976d716dbb 100644 --- a/plugins/status/status.rb +++ b/plugins/status/status.rb @@ -36,6 +36,8 @@ def self.get_event_handler(event_name) case event_name when "CharDisconnectedEvent" return CharDisconnectedEventHandler + when "CronEvent" + return AfkCronHandler end nil end diff --git a/plugins/status/status_config_validator.rb b/plugins/status/status_config_validator.rb index 132794bc92..588471b134 100644 --- a/plugins/status/status_config_validator.rb +++ b/plugins/status/status_config_validator.rb @@ -10,9 +10,8 @@ def initialize def validate @validator.check_cron('afk_cron') @validator.require_hash('colors') - @validator.require_int('minutes_before_idle', 10) - @validator.require_int('minutes_before_idle_disconnect', 10) - + @validator.require_int('minutes_before_idle') + @validator.require_int('minutes_before_idle_disconnect') @validator.errors end diff --git a/plugins/website/locales/locale_en.yml b/plugins/website/locales/locale_en.yml index 005511be59..90c7ae7e5d 100644 --- a/plugins/website/locales/locale_en.yml +++ b/plugins/website/locales/locale_en.yml @@ -3,6 +3,7 @@ en: login_required: "You must log in first." unexpected_error: "Ooops! Something went wrong. Please try again." missing_required_fields: "Missing required fields." + missing_required_field: "Missing required field: %{name}" not_found: "The requested item was not found." session_expired: "Your session has expired. Please log out and log in again." website_address: "Visit %{portal} to access the game's web portal." diff --git a/plugins/website/public/website_api.rb b/plugins/website/public/website_api.rb index 56bbe01ed9..93a6ddb33f 100644 --- a/plugins/website/public/website_api.rb +++ b/plugins/website/public/website_api.rb @@ -11,6 +11,7 @@ def self.format_markdown_for_html(output) text end + # Weird edge case that you might want to ignore markdown formatting but still do MUSH format codes. def self.format_output_for_html(output) return nil if !output diff --git a/plugins/website/web/get_folders_request_handler.rb b/plugins/website/web/get_folders_request_handler.rb index ea45392772..ad75f10e35 100644 --- a/plugins/website/web/get_folders_request_handler.rb +++ b/plugins/website/web/get_folders_request_handler.rb @@ -11,7 +11,7 @@ def handle(request) folders = Dir[File.join(AresMUSH.website_uploads_path, "*")] - folders.map { |f| { + folders.sort.map { |f| { name: f.gsub(AresMUSH.website_uploads_path, '').gsub('/', '') } } end diff --git a/plugins/website/wiki_markdown/tag_match_helper.rb b/plugins/website/wiki_markdown/tag_match_helper.rb index be21899051..64943dcd2b 100644 --- a/plugins/website/wiki_markdown/tag_match_helper.rb +++ b/plugins/website/wiki_markdown/tag_match_helper.rb @@ -4,7 +4,8 @@ class TagMatchHelper attr_accessor :or_tags, :required_tags, :exclude_tags def initialize(input) - tags = (input || "").split(" ").map { |t| t.downcase } + # Need to unescape HTML because it's already been run through the markdown processor. + tags = CGI.unescapeHTML(input || "").split(" ").map { |t| t.downcase } @or_tags = tags.select { |tag| !tag.start_with?("-") && !tag.start_with?("+")} diff --git a/version.txt b/version.txt index e6e9cf41cc..08fec8881e 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.82 +0.83