diff --git a/mods/mc_core/Hooks.lua b/mods/mc_core/Hooks.lua index d797ea6c..94cc6bf8 100644 --- a/mods/mc_core/Hooks.lua +++ b/mods/mc_core/Hooks.lua @@ -2,9 +2,6 @@ minetest.register_privilege("teacher", { give_to_singleplayer = true }) -minetest.register_privilege("student", { - give_to_singleplayer = true -}) -- Ensures that we can use the server and Server usernames minetest.register_on_prejoinplayer(function(name, ip) diff --git a/mods/mc_core/README.md b/mods/mc_core/README.md index 7b165805..55f15f96 100644 --- a/mods/mc_core/README.md +++ b/mods/mc_core/README.md @@ -291,7 +291,7 @@ Returns `true` if the player is frozen, `false` otherwise ### `mc_core.temp_unfreeze_and_run(player, func, ...)` Temporarily unfreezes `player` if they are frozen, runs `func`, then refreezes `player` if they should be frozen -This should be used when applying forced movement to a player, since frozen players can not be teleported normally +This should be used when applying forced movement to a player, since frozen players can not be teleported or moved normally Optional arguments after `func` will be passed into `func` when it runs (similarly to how `minetest.after` passes arguments) - Parameters: diff --git a/mods/mc_core/freeze.lua b/mods/mc_core/freeze.lua index 6e9425f4..1bb743e4 100644 --- a/mods/mc_core/freeze.lua +++ b/mods/mc_core/freeze.lua @@ -1,5 +1,6 @@ ---Adapted from freeze.lua in rubenwardy's classroom mod ---@see https://gitlab.com/rubenwardy/classroom/-/blob/master/freeze.lua +---@license MIT: https://gitlab.com/rubenwardy/classroom/-/blob/1e7b11f824c03c882d74d5079d8275f3e297adea/LICENSE.txt -- Frozen players minetest.register_entity("mc_core:frozen_player", { diff --git a/mods/mc_student/gui.lua b/mods/mc_student/gui.lua index 15552e7e..d55588ba 100644 --- a/mods/mc_student/gui.lua +++ b/mods/mc_student/gui.lua @@ -90,10 +90,10 @@ function mc_student.show_notebook_fs(player, tab) "style_type[textarea;font=mono,bold;textcolor=#000000]", "textarea[", text_spacer, ",1;", panel_width - 2*text_spacer, ",1;;;Welcome to Minetest Classroom!]", - "textarea[", text_spacer, ",4.6;", panel_width - 2*text_spacer, ",1;;;Server Rules]", + "textarea[", text_spacer, ",4.6;", panel_width - 2*text_spacer, ",1;;;Server rules]", "style_type[textarea;font=mono]", - "textarea[", text_spacer, ",1.4;", panel_width - 2*text_spacer, ",3;;;", minetest.formspec_escape("This is the Student Notebook, your tool for accessing classrooms and other features."), - "\n", minetest.formspec_escape("You cannot drop or delete the Student Notebook, so you will never lose it. However, you can move it out of your hotbar and into your inventory or the toolbox."), "]", + "textarea[", text_spacer, ",1.4;", panel_width - 2*text_spacer, ",3;;;", minetest.formspec_escape("This is the student notebook, your tool for accessing classrooms and other features."), + "\n", minetest.formspec_escape("You cannot drop or delete the student notebook, so you will never lose it. However, you can move it out of your hotbar and into your inventory or the toolbox."), "]", "textarea[", text_spacer, ",5;", panel_width - 2*text_spacer, ",4.8;;;", minetest.formspec_escape(rules), "]", "hypertext[", panel_width + spacer + 1.8, ",0.9;5.35,1.8;;Classrooms\n", minetest.formspec_escape("View classrooms and players"), "]", @@ -119,7 +119,7 @@ function mc_student.show_notebook_fs(player, tab) local cat = realm:getCategory().key if not realm:isHidden() and ((context.selected_c_tab == mc_teacher.CTAB.PUBLIC and cat ~= mc_teacher.R.CAT_MAP[mc_teacher.R.CAT_KEY.INSTANCED]) or (context.selected_c_tab == mc_teacher.CTAB.PRIVATE and cat == mc_teacher.R.CAT_MAP[mc_teacher.R.CAT_KEY.INSTANCED])) then - table.insert(classroom_list, table.concat({minetest.formspec_escape(realm.Name or ""), " (", playerCount, " player", playerCount == 1 and "" or "s", ")"})) + table.insert(classroom_list, table.concat({minetest.formspec_escape(mc_teacher.get_realm_prefix(realm, cat) or ""), minetest.formspec_escape(realm.Name or ""), " (", playerCount, " player", playerCount == 1 and "" or "s", ")"})) table.insert(context.realm_i_to_id, id) end end @@ -225,7 +225,7 @@ function mc_student.show_notebook_fs(player, tab) "hypertext[", text_spacer, ",0.1;", panel_width - 2*text_spacer, ",1;;]", "hypertext[", panel_width + text_spacer, ",0.1;", panel_width - 2*text_spacer, ",1;;]", "style_type[textarea;font=mono,bold;textcolor=#000000]", - "textarea[", text_spacer, ",1;", panel_width - 2*text_spacer, ",1;;;Surrounding Area]", + "textarea[", text_spacer, ",1;", panel_width - 2*text_spacer, ",1;;;Surrounding area]", "image[", map_x - 0.025, ",", map_y - 0.025, ";7.1,7.1;mc_pixel.png^[multiply:#000000]", "image[", map_x, ",", map_y, ";7.05,7.05;mc_pixel.png^[multiply:#808080]", } @@ -275,13 +275,13 @@ function mc_student.show_notebook_fs(player, tab) table.insert(fs, table.concat({ "image[", 3.95 + (pos.x - round_px)*0.15, ",", 4.75 - (pos.z - round_pz)*0.15, ";0.4,0.4;mc_mapper_d", yaw, ".png^[transformFY", rotate ~= 0 and ("R"..rotate) or "", "]", - "textarea[", text_spacer, ",8.6;", panel_width - 2*text_spacer, ",1;;;Coordinate and Elevation Display]", + "textarea[", text_spacer, ",8.6;", panel_width - 2*text_spacer, ",1;;;Coordinate display]", "style_type[button,image_button;border=false;font=mono,bold;bgimg=mc_pixel.png^[multiply:", mc_core.col.b.default, "]", "button[", spacer, ",9;1.7,0.8;utmcoords;UTM]", "button[", spacer + 1.8, ",9;1.7,0.8;latloncoords;Lat/Lon]", "button[", spacer + 3.6, ",9;1.7,0.8;classroomcoords;Local]", "button[", spacer + 5.4, ",9;1.7,0.8;coordsoff;Off]", - "textarea[", panel_width + text_spacer, ",1;", panel_width - 2*text_spacer, ",1;;;Saved Coordinates]", + "textarea[", panel_width + text_spacer, ",1;", panel_width - 2*text_spacer, ",1;;;Saved coordinates]", })) local coord_list = get_saved_coords(player) @@ -415,7 +415,7 @@ textarea[0.55,0.1;7.2,1;;;Overview] textarea[8.75,0.1;7.2,1;;;Dashboard] textarea[0.55,1;7.1,1;;;Welcome to Minetest Classroom!] textarea[0.55,1.4;7.2,3;;;This is the student notebook!] -textarea[0.55,4.6;7.2,1;;;Server Rules] +textarea[0.55,4.6;7.2,1;;;Server rules] textarea[0.55,5;7.2,4.8;;;These are the server rules!] textarea[10.7,0.9;5.3,1.8;;;Classrooms View classrooms and players] image_button[8.9,1;1.7,1.6;mc_teacher_classrooms.png;classrooms;;false;true] @@ -435,7 +435,7 @@ box[8.275,0;0.05,10.4;#000000] image_button_exit[0.2,0.05;0.4,0.4;mc_x.png;exit;;false;false] textarea[0.55,0.1;7.2,1;;;Classrooms] textarea[8.75,0.1;7.2,1;;;Online Players] -textarea[0.55,1;7.2,1;;;Available Classrooms] +textarea[0.55,1;7.2,1;;;Available classrooms] textlist[0.6,1.4;7.1,7.5;classroomlist;;1;false] button[0.6,9;7.1,0.8;teleportrealm;Teleport] textarea[8.85,1;7.2,1;;;Teachers] @@ -456,16 +456,16 @@ box[8.275,0;0.05,10.4;#000000] image_button_exit[0.2,0.05;0.4,0.4;mc_x.png;exit;;false;false] textarea[0.55,0.1;7.1,1;;;Map] textarea[8.85,0.1;7.1,1;;;Coordinates] -textarea[0.55,1;7.1,1;;;Surrounding Area] +textarea[0.55,1;7.1,1;;;Surrounding area] box[0.6,1.4;7.1,7.1;#000000] box[0.625,1.425;7.05,7.05;#808080] image[4,4.8;0.3,0.3;] -textarea[0.55,8.6;7.1,1;;;Coordinate and Elevation Display] +textarea[0.55,8.6;7.1,1;;;Coordinate display] button[0.6,9;1.7,0.8;utmcoords;UTM] button[2.4,9;1.7,0.8;latloncoords;Lat/Long] button[4.2,9;1.7,0.8;classroomcoords;Local] button[6,9;1.7,0.8;coordsoff;Off] -textarea[8.85,1;7.1,1;;;Saved Coordinates] +textarea[8.85,1;7.1,1;;;Saved coordinates] textlist[8.9,1.4;7.1,4.4;coordlist;;8;false] textarea[8.85,8.5;7.1,1;;;Save current coordinates] image_button[15.1,8.9;0.9,0.9;blank.png;;Save;false;true] diff --git a/mods/mc_student/tools.lua b/mods/mc_student/tools.lua index 1ffb4af0..c8adf9d0 100644 --- a/mods/mc_student/tools.lua +++ b/mods/mc_student/tools.lua @@ -1,20 +1,18 @@ minetest.register_tool("mc_student:notebook" , { - description = "Notebook for students", - inventory_image = "mc_student_notebook.png", - on_use = function(itemstack, player, pointed_thing) - local pmeta = player:get_meta() - if mc_core.checkPrivs(player, {student = true}) then - if pmeta:get_string("default_student_tab") ~= "" then - mc_student.show_notebook_fs(player,pmeta:get_string("default_student_tab")) - else - mc_student.show_notebook_fs(player, mc_student.TABS.OVERVIEW) - end - end - end, - on_drop = function(itemstack, dropper, pos) - end, + description = "Notebook for students", + inventory_image = "mc_student_notebook.png", + on_use = function(itemstack, player, pointed_thing) + local pmeta = player:get_meta() + if pmeta:get_string("default_student_tab") ~= "" then + mc_student.show_notebook_fs(player,pmeta:get_string("default_student_tab")) + else + mc_student.show_notebook_fs(player, mc_student.TABS.OVERVIEW) + end + end, + on_drop = function(itemstack, dropper, pos) + end, }) if minetest.get_modpath("mc_toolhandler") then - mc_toolhandler.register_tool_manager("mc_student:notebook", {privs = {student = true}, inv_override = "main"}) + mc_toolhandler.register_tool_manager("mc_student:notebook", {privs = {}, inv_override = "main"}) end \ No newline at end of file diff --git a/mods/mc_teacher/callbacks.lua b/mods/mc_teacher/callbacks.lua index 517b859c..db51ca9d 100644 --- a/mods/mc_teacher/callbacks.lua +++ b/mods/mc_teacher/callbacks.lua @@ -17,9 +17,9 @@ minetest.register_on_joinplayer(function(player) local pmeta = player:get_meta() local priv_format = pmeta:get_int("priv_format") - if not priv_format or priv_format < 3 then - mc_worldManager.grantUniversalPriv(player, {"student"}) - pmeta:set_int("priv_format", 3) + if not priv_format or priv_format < 4 then + pmeta:set_int("priv_format", 4) + mc_worldManager.revokeUniversalPriv(player, {"student"}) local realm = Realm.GetRealmFromPlayer(player) if realm then realm:ApplyPrivileges(player) end end @@ -93,26 +93,22 @@ local function get_players_to_update(player, context, override) return list end -local function pluralize(count, role_string) +local function pluralize(count, role) local map = { - [mc_teacher.ROLES.NONE] = { - [true] = role_string, - [false] = role_string, - }, [mc_teacher.ROLES.STUDENT] = { - [true] = "a "..role_string, - [false] = role_string.."s", + [true] = "a student", + [false] = "students", }, [mc_teacher.ROLES.TEACHER] = { - [true] = "a "..role_string, - [false] = role_string.."s", + [true] = "a teacher", + [false] = "teachers", }, [mc_teacher.ROLES.ADMIN] = { - [true] = "an "..role_string, - [false] = role_string.."s", + [true] = "an administrator", + [false] = "administrators", } } - return role_string and map[role_string] and map[role_string][count == 1] or "???" + return role and map[role] and map[role][count == 1] or "???" end minetest.register_on_player_receive_fields(function(player, formname, fields) @@ -209,6 +205,26 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) end context.p_list = nil mc_teacher.show_controller_fs(player, context.tab) + elseif formname == "mc_teacher:confirm_group_delete" then + ------------------------ + -- DELETE GROUP POPUP -- + ------------------------ + if fields.confirm then + local pname = player:get_player_name() + local pmeta = player:get_meta() + local groups = mc_teacher.get_player_tab_groups(player) + local index = mc_teacher.get_group_index(context.selected_p_tab) + if groups[index] and pmeta then + local removed = table.remove(groups, index) + pmeta:set_string("mc_teacher:groups", minetest.serialize(groups)) + context.selected_p_tab = nil + minetest.chat_send_player(pname, minetest.colorize(mc_core.col.log, "[Minetest Classroom] Successfully deleted group "..removed.name..".")) + else + minetest.chat_send_player(pname, minetest.colorize(mc_core.col.log, "[Minetest Classroom] Selected group could not be deleted.")) + end + end + context.p_list = nil + mc_teacher.show_controller_fs(player, context.tab) elseif formname == "mc_teacher:confirm_shutdown_schedule" and has_server_privs then ----------------------------- -- SCHEDULE SHUTDOWN POPUP -- @@ -274,8 +290,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) end end mc_teacher.show_controller_fs(player, context.tab) - elseif formname == "mc_teacher:role_change_"..mc_teacher.ROLES.NONE or formname == "mc_teacher:role_change_"..mc_teacher.ROLES.STUDENT - or (has_server_privs and (formname == "mc_teacher:role_change_"..mc_teacher.ROLES.TEACHER or formname == "mc_teacher:role_change_"..mc_teacher.ROLES.ADMIN)) then + elseif has_server_privs and string.sub(formname or "", 1, 23) == "mc_teacher:role_change_" then ----------------------- -- ROLE CHANGE POPUP -- ----------------------- @@ -290,19 +305,15 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) if not p_obj or not p_obj:is_player() then minetest.chat_send_player(pname, minetest.colorize(mc_core.col.log, "[Minetest Classroom] Could not change server role of player "..tostring(p).." (they are probably offline).")) else - if formname == "mc_teacher:role_change_"..mc_teacher.ROLES.NONE then - mc_worldManager.revokeUniversalPriv(p_obj, {"student", "teacher", "server"}) - mc_teacher.register_student(p) - elseif formname == "mc_teacher:role_change_"..mc_teacher.ROLES.STUDENT then - mc_worldManager.grantUniversalPriv(p_obj, {"student"}) + if formname == "mc_teacher:role_change_"..mc_teacher.ROLES.STUDENT then mc_worldManager.revokeUniversalPriv(p_obj, {"teacher", "server"}) mc_teacher.register_student(p) - elseif has_server_privs and formname == "mc_teacher:role_change_"..mc_teacher.ROLES.TEACHER then - mc_worldManager.grantUniversalPriv(p_obj, {"student", "teacher"}) + elseif formname == "mc_teacher:role_change_"..mc_teacher.ROLES.TEACHER then + mc_worldManager.grantUniversalPriv(p_obj, {"teacher"}) mc_worldManager.revokeUniversalPriv(p_obj, {"server"}) mc_teacher.register_teacher(p) - elseif has_server_privs and formname == "mc_teacher:role_change_"..mc_teacher.ROLES.ADMIN then - mc_worldManager.grantUniversalPriv(p_obj, {"student", "teacher", "server"}) + elseif formname == "mc_teacher:role_change_"..mc_teacher.ROLES.ADMIN then + mc_worldManager.grantUniversalPriv(p_obj, {"teacher", "server"}) mc_teacher.register_teacher(p) end @@ -403,6 +414,10 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) if fields.erealm_cat and fields.erealm_cat ~= context.edit_realm.type then context.edit_realm.type = fields.erealm_cat + elseif fields.erealm_skybox and tonumber(fields.erealm_skybox) ~= tonumber(context.edit_realm.skybox) then + context.edit_realm.skybox = tonumber(fields.erealm_skybox) + elseif fields.erealm_music and tonumber(fields.erealm_music) ~= tonumber(context.edit_realm.music) then + context.edit_realm.music = tonumber(fields.erealm_music) elseif fields.save_realm or fields.cancel or fields.quit then if fields.save_realm then local realm = Realm.GetRealm(context.edit_realm.id) @@ -431,16 +446,85 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) if fields.erealm_name then context.edit_realm.name = minetest.formspec_escape(fields.erealm_name) end mc_teacher.show_edit_popup(player, context.edit_realm.id) end + elseif formname == "mc_teacher:edit_group" then + ----------------- + -- GROUP POPUP -- + ----------------- + local reload = false + if fields.non_members then + local event = minetest.explode_textlist_event(fields.non_members) + if event.type == "CHG" then + context.edit_group.sel_nm = event.index + elseif event.type == "DCL" then + context.edit_group.sel_nm = event.index + fields.member_add = "" -- trigger add button + end + end + if fields.members then + local event = minetest.explode_textlist_event(fields.members) + if event.type == "CHG" then + context.edit_group.sel_m = event.index + elseif event.type == "DCL" then + context.edit_group.sel_m = event.index + fields.member_delete = "" -- trigger delete button + end + end + + if fields.member_add then + local selected_player = table.remove(context.edit_group.p_list, context.edit_group.sel_nm) + table.insert(context.edit_group.members, selected_player) + context.edit_group.sel_nm = math.max(1, math.min(context.edit_group.sel_nm, #context.edit_group.p_list)) + reload = true + elseif fields.member_delete then + local selected_player = table.remove(context.edit_group.members, context.edit_group.sel_m) + table.insert(context.edit_group.p_list, selected_player) + context.edit_group.sel_m = math.max(1, math.min(context.edit_group.sel_m, #context.edit_group.members)) + reload = true + elseif fields.save or fields.cancel or fields.quit then + if fields.save then + local pname = player:get_player_name() + local pmeta = player:get_meta() + local groups = mc_teacher.get_player_tab_groups(player) + if groups and pmeta then + local group_to_save = {name = fields.group_name or context.edit_group.name, members = context.edit_group.members or {}} + if context.edit_group.id == 0 then + table.insert(groups, group_to_save) + context.selected_p_tab = (#groups + mc_teacher.PTAB.N) + pmeta:set_string("mc_teacher:groups", minetest.serialize(groups)) + minetest.chat_send_player(player:get_player_name(), minetest.colorize(mc_core.col.log, "[Minetest Classroom] Group created!")) + else + groups[context.edit_group.id] = group_to_save + pmeta:set_string("mc_teacher:groups", minetest.serialize(groups)) + minetest.chat_send_player(player:get_player_name(), minetest.colorize(mc_core.col.log, "[Minetest Classroom] Group updated!")) + end + else + minetest.chat_send_player(player:get_player_name(), minetest.colorize(mc_core.col.log, "[Minetest Classroom] The group could not be found, so no changes were made to it.")) + end + else + minetest.chat_send_player(player:get_player_name(), minetest.colorize(mc_core.col.log, "[Minetest Classroom] No changes were made to the group.")) + end + + context.edit_group = nil + context.p_list = nil + return mc_teacher.show_controller_fs(player, context.tab) + end + + if reload then + if fields.group_name then context.edit_group.name = minetest.formspec_escape(fields.group_name) end + mc_teacher.show_group_popup(player) + end elseif formname == "mc_teacher:controller_fs" then ------------- -- GENERAL -- ------------- if fields.record_nav then context.tab = fields.record_nav + context.p_list = nil reload = true end if fields.default_tab then pmeta:set_string("default_teacher_tab", context.tab) + context.p_list = nil reload = true end @@ -518,9 +602,15 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) context.realm_dec = fields.realm_decorator reload = true end + if fields.realm_skybox and tonumber(fields.realm_skybox) ~= tonumber(context.selected_skybox) then + context.selected_skybox = tonumber(fields.realm_skybox) + end + if fields.realm_music and tonumber(fields.realm_music) ~= tonumber(context.selected_music) then + context.selected_music = tonumber(fields.realm_music) + end if fields.c_newrealm then - if mc_core.checkPrivs(player,{teacher = true}) then + if mc_core.checkPrivs(player, {teacher = true}) then local realm_name = fields.realmname or context.realmname or "" local new_realm local errors = {} @@ -616,7 +706,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) new_realm = Realm:NewFromDEM(realm_name, context.selected_dem) end - new_realm:set_data("owner", player:get_player_name()) + new_realm:AddOwner(player:get_player_name()) if context.selected_realm_type == mc_teacher.R.CAT_KEY.SPAWN then mc_worldManager.SetSpawnRealm(new_realm) minetest.chat_send_player(player:get_player_name(), minetest.colorize(mc_core.col.log, "[Minetest Classroom] Server spawn classroom updated!")) @@ -624,6 +714,9 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) new_realm:setCategoryKey(mc_teacher.R.CAT_MAP[context.selected_realm_type or mc_teacher.R.CAT_KEY.CLASSROOM]) end new_realm:UpdateRealmPrivilege(context.selected_privs) + new_realm:UpdateSkybox(context.skyboxes[context.selected_skybox]) + -- TODO: allow volume to be set + new_realm:UpdateMusic(context.music[context.selected_music], 100) minetest.chat_send_player(player:get_player_name(),minetest.colorize(mc_core.col.log, "[Minetest Classroom] Your requested classroom was successfully created.")) reload = true end @@ -771,7 +864,28 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) reload = true end - if fields.p_priv_update or fields.p_priv_reset then + if fields.p_group_new then + return mc_teacher.show_group_popup(player, nil) + elseif fields.p_group_edit then + if tonumber(context.selected_p_tab) > mc_teacher.PTAB.N then + return mc_teacher.show_group_popup(player, tonumber(context.selected_p_tab)) + else + minetest.chat_send_player(player:get_player_name(), minetest.colorize(mc_core.col.log, "[Minetest Classroom] This group can not be edited.")) + end + context.p_list = nil + reload = true + elseif fields.p_group_delete then + if tonumber(context.selected_p_tab) > mc_teacher.PTAB.N then + return mc_teacher.show_confirm_popup(player, "confirm_group_delete", { + action = "Are you sure you want to delete this group?", + button = "Delete group", irreversible = true, + }) + else + minetest.chat_send_player(player:get_player_name(), minetest.colorize(mc_core.col.log, "[Minetest Classroom] This group can not be deleted.")) + end + context.p_list = nil + reload = true + elseif fields.p_priv_update or fields.p_priv_reset then local players_to_update = get_players_to_update(player, context, true) local realm = Realm.GetRealmFromPlayer(player) if realm and #players_to_update > 0 then @@ -804,6 +918,8 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) else return mc_teacher.show_kick_popup(player, #players_to_update) end + context.p_list = nil + reload = true elseif fields.p_ban then local players_to_update = get_players_to_update(player, context) local pname = player:get_player_name() @@ -818,6 +934,8 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) button = "Ban player"..(#players_to_update == 1 and "" or "s") }, {y = 3.8}) end + context.p_list = nil + reload = true elseif fields.p_teleport then local pname = player:get_player_name() local sel_pname = context.p_list[context.selected_p_player] @@ -826,10 +944,14 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) if not mc_core.is_frozen(player) then local destination = sel_pobj:get_pos() local realm = Realm.GetRealmFromPlayer(sel_pobj) - if realm and not realm:isDeleted() and realm:getCategory().joinable(realm, player) then - realm:TeleportPlayer(player) - player:set_pos(destination) - minetest.chat_send_player(pname, minetest.colorize(mc_core.col.log, "[Minetest Classroom] Teleported to player "..tostring(sel_pname).."!")) + if realm and realm:Joinable(player) then + if mc_teacher.is_in_timeout(player) and realm.ID ~= (Realm.GetRealmFromPlayer(player) or {ID = 0}).ID then + minetest.chat_send_player(player:get_player_name(), minetest.colorize(mc_core.col.log, "[Minetest Classroom] You can not join other classrooms while in timeout.")) + else + realm:TeleportPlayer(player) + player:set_pos(destination) + minetest.chat_send_player(pname, minetest.colorize(mc_core.col.log, "[Minetest Classroom] Teleported to player "..tostring(sel_pname).."!")) + end else minetest.chat_send_player(pname, minetest.colorize(mc_core.col.log, "[Minetest Classroom] Could not teleport to player "..tostring(sel_pname)..".")) end @@ -852,9 +974,11 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) end for _, p in pairs(players_to_update) do local p_obj = minetest.get_player_by_name(p) - if p_obj and destRealm:getCategory().joinable(destRealm, player) (not destRealm:isHidden() or mc_core.checkPrivs(p_obj, {teacher = true})) then - mc_core.temp_unfreeze_and_run(p_obj, destRealm.TeleportPlayer, destRealm, player) - p_obj:set_pos(destination) + if destRealm:Joinable(p_obj) then + mc_core.temp_unfreeze_and_run(p_obj, function() + destRealm:TeleportPlayer(p_obj) + p_obj:set_pos(destination) + end) else minetest.chat_send_player(pname, minetest.colorize(mc_core.col.log, "[Minetest Classroom] Player "..tostring(p).." does not have access to your current classroom. Please check your current classroom's category and try again.")) end @@ -864,6 +988,26 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) end context.p_list = nil reload = true + elseif fields.p_audience then + ---Audience action adapted from actions.lua in rubenwardy's classroom mod + ---@see https://gitlab.com/rubenwardy/classroom/-/blob/master/actions.lua + ---@license MIT: https://gitlab.com/rubenwardy/classroom/-/blob/1e7b11f824c03c882d74d5079d8275f3e297adea/LICENSE.txt + + local players_to_update = get_players_to_update(player, context) + local pname = player:get_player_name() + local realm = Realm.GetRealmFromPlayer(player) + if #players_to_update <= 0 then + minetest.chat_send_player(pname, minetest.colorize(mc_core.col.log, "[Minetest Classroom] There are no players selected.")) + elseif #players_to_update == 1 and players_to_update[1] == pname then + minetest.chat_send_player(pname, minetest.colorize(mc_core.col.log, "[Minetest Classroom] You can not create an audience containing only yourself.")) + elseif realm and realm:Joinable(player) then + local eye_height = player:get_properties().eye_height + local teacher_pos = vector.add(player:get_pos(), {x = 0, y = eye_height, z = 0}) + local res = mc_teacher.create_audience(players_to_update, realm, player:get_player_name(), teacher_pos, player:get_look_dir()) + minetest.chat_send_player(pname, minetest.colorize(mc_core.col.log, "[Minetest Classroom] "..(res and "Audience created!" or "Could not fully create the requested audience, use the bring action instead."))) + else + minetest.chat_send_player(pname, minetest.colorize(mc_core.col.log, "[Minetest Classroom] You no longer have access to this classroom.")) + end elseif fields.p_mute or fields.p_unmute then local players_to_update = get_players_to_update(player, context) local pname = player:get_player_name() @@ -914,6 +1058,30 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) end context.p_list = nil reload = true + elseif fields.p_timeout or fields.p_endtimeout then + local players_to_update = get_players_to_update(player, context) + local pname = player:get_player_name() + if #players_to_update <= 0 then + minetest.chat_send_player(pname, minetest.colorize(mc_core.col.log, "[Minetest Classroom] There are no players selected.")) + end + for _,p in pairs(players_to_update) do + if p ~= pname then + local p_obj = minetest.get_player_by_name(p) + if p_obj then + if fields.p_timeout then + mc_teacher.timeout(p_obj) + else + mc_teacher.end_timeout(p_obj) + end + else + minetest.chat_send_player(pname, minetest.colorize(mc_core.col.log, "[Minetest Classroom] Could not "..(fields.p_timeout and "timeout" or "end timeout for").." player "..tostring(p).." (they are probably offline).")) + end + else + minetest.chat_send_player(pname, minetest.colorize(mc_core.col.log, "[Minetest Classroom] You can not "..(fields.p_timeout and "put yourself in timeout" or "end your own timeout")..".")) + end + end + context.p_list = nil + reload = true elseif fields.p_deactivate or fields.p_reactivate then local players_to_update = get_players_to_update(player, context) local pname = player:get_player_name() @@ -940,7 +1108,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) end context.p_list = nil reload = true - elseif fields.p_role_none or fields.p_role_student then + elseif has_server_privs and (fields.p_role_student or fields.p_role_teacher or fields.p_role_admin) then local players_to_update = get_players_to_update(player, context) local pname = player:get_player_name() if #players_to_update <= 0 then @@ -949,26 +1117,20 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) minetest.chat_send_player(pname, minetest.colorize(mc_core.col.log, "[Minetest Classroom] You can not change your own server role.")) else local p_count_string = (#players_to_update == 1 and "this player" or "these "..tostring(#players_to_update).." players") - local role_string = fields.p_role_student and mc_teacher.ROLES.STUDENT or mc_teacher.ROLES.NONE - return mc_teacher.show_confirm_popup(player, "role_change_"..role_string, - {action = "Are you sure you want "..p_count_string.." to be "..pluralize(#players_to_update, role_string).."?"} - ) - end - elseif has_server_privs and (fields.p_role_teacher or fields.p_role_admin) then - local players_to_update = get_players_to_update(player, context) - local pname = player:get_player_name() - if #players_to_update <= 0 then - minetest.chat_send_player(pname, minetest.colorize(mc_core.col.log, "[Minetest Classroom] There are no players selected.")) - elseif #players_to_update == 1 and players_to_update[1] == pname then - minetest.chat_send_player(pname, minetest.colorize(mc_core.col.log, "[Minetest Classroom] You can not change your own server role.")) - else - local p_count_string = (#players_to_update == 1 and "this player" or "these "..tostring(#players_to_update).." players") - local role_string = fields.p_role_admin and mc_teacher.ROLES.ADMIN or mc_teacher.ROLES.TEACHER - return mc_teacher.show_confirm_popup(player, "role_change_"..role_string, - {action = "Are you sure you want "..p_count_string.." to be "..pluralize(#players_to_update, role_string).."?\nThis will give them access to tools which can be used to modify the server."}, - {y = 4.3} - ) + if fields.p_role_student then + return mc_teacher.show_confirm_popup(player, "role_change_"..mc_teacher.ROLES.STUDENT, + {action = "Are you sure you want "..p_count_string.." to be "..pluralize(#players_to_update, mc_teacher.ROLES.STUDENT).."?"} + ) + else + local role = fields.p_role_admin and mc_teacher.ROLES.ADMIN or mc_teacher.ROLES.TEACHER + return mc_teacher.show_confirm_popup(player, "role_change_"..role, + {action = "Are you sure you want "..p_count_string.." to be "..pluralize(#players_to_update, role).."?\nThis will give them access to tools which can be used to modify the server."}, + {y = 4.3} + ) + end end + context.p_list = nil + reload = true end ---------------- @@ -1157,14 +1319,12 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) minetest.chat_send_all(minetest.colorize(mc_core.col.log, "[Minetest Classroom] The scheduled server restart has been cancelled.")) reload = true elseif fields.server_shutdown_schedule then - -- TODO: make popup context.server_shutdown_timer = fields.server_shutdown_timer return mc_teacher.show_confirm_popup(player, "confirm_shutdown_schedule", { action = "Are you sure you want to schedule a server shutdown in "..context.server_shutdown_timer.." from now?\nClassrooms will be saved prior to the shutdown.", button = "Schedule" }, {x = 9.2, y = 3.9}) elseif fields.server_shutdown_now then - -- TODO: make popup return mc_teacher.show_confirm_popup(player, "confirm_shutdown_now", { action = "Are you sure you want to perform a server shutdown right now?\nClassrooms will be saved prior to the shutdown." }, {x = 9.2, y = 3.5}) @@ -1174,19 +1334,23 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) context.selected_s_dyn = event.index end reload = true - elseif fields.unban then + elseif fields.server_unban then context.selected_s_dyn = context.selected_s_dyn or 1 - local bans = mc_core.split(ban_string, ",") - if bans[context.selected_s_dyn] then - local ban_split = mc_core.split(bans[context.selected_s_dyn], "|") - if ban_split and ban_split[1] then - minetest.unban_player_or_ip(ban_split[1]) - minetest.chat_send_player(player:get_player_name(), minetest.colorize(mc_core.col.log, "[Minetest Classroom] Player unbanned!")) + if mc_core.trim(context.server_dyn_list or "") ~= "" then + local bans = mc_core.split(context.server_dyn_list, ",") + if bans[context.selected_s_dyn] and mc_core.trim(bans[context.selected_s_dyn]) ~= "" then + local ban_split = mc_core.split(bans[context.selected_s_dyn], "|") + if ban_split and ban_split[1] then + minetest.unban_player_or_ip(ban_split[1]) + minetest.chat_send_player(player:get_player_name(), minetest.colorize(mc_core.col.log, "[Minetest Classroom] Player unbanned!")) + else + minetest.chat_send_player(player:get_player_name(), minetest.colorize(mc_core.col.log, "[Minetest Classroom] Player could not be unbanned. Please unban this player by using the /unban command instead.")) + end else minetest.chat_send_player(player:get_player_name(), minetest.colorize(mc_core.col.log, "[Minetest Classroom] Player could not be unbanned. Please unban this player by using the /unban command instead.")) end else - minetest.chat_send_player(player:get_player_name(), minetest.colorize(mc_core.col.log, "[Minetest Classroom] Player could not be unbanned.")) + minetest.chat_send_player(player:get_player_name(), minetest.colorize(mc_core.col.log, "[Minetest Classroom] There are no players to unban.")) end reload = true elseif fields.server_whitelist then @@ -1206,6 +1370,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) if context.selected_privs_mode == mc_teacher.TABS.PLAYERS then context.selected_privs = nil end + context.skyboxes = nil context.tab = nil elseif reload then -- CLASSROOM -- @@ -1276,11 +1441,13 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) if not context.selected_realm then context.selected_realm = 1 end local realm = Realm.GetRealm(context.realm_i_to_id[context.selected_realm]) if realm and not realm:isDeleted() and (not realm:isHidden() or mc_core.checkPrivs(player, {teacher = true})) then - if not mc_core.is_frozen(player) then + if mc_core.is_frozen(player) then + minetest.chat_send_player(player:get_player_name(), minetest.colorize(mc_core.col.log, "[Minetest Classroom] You can not move while frozen.")) + elseif mc_teacher.is_in_timeout(player) then + minetest.chat_send_player(player:get_player_name(), minetest.colorize(mc_core.col.log, "[Minetest Classroom] You can not join other classrooms while in timeout.")) + else realm:TeleportPlayer(player) context.selected_realm = 1 - else - minetest.chat_send_player(player:get_player_name(), minetest.colorize(mc_core.col.log, "[Minetest Classroom] You can not move while frozen.")) end else minetest.chat_send_player(player:get_player_name(), minetest.colorize(mc_core.col.log, "[Minetest Classroom] The classroom you requested is no longer available.")) @@ -1331,15 +1498,11 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) local note_name = context.coord_i_to_note[context.selected_coord] local note_i = pdata.note_map[note_name] local realm = Realm.GetRealm(pdata.realms[note_i]) - if realm then - if realm:getCategory().joinable(realm, player) and (not destRealm:isHidden() or mc_core.checkPrivs(player, {teacher = true})) then - realm:TeleportPlayer(player) - player:set_pos(pdata.coords[note_i]) - else - minetest.chat_send_player(player:get_player_name(), minetest.colorize(mc_core.col.log, "[Minetest Classroom] You no longer have access to this classroom.")) - end + if realm and realm:Joinable(player)then + realm:TeleportPlayer(player) + player:set_pos(pdata.coords[note_i]) else - minetest.chat_send_player(player:get_player_name(), minetest.colorize(mc_core.col.log, "[Minetest Classroom] This classroom no longer exists.")) + minetest.chat_send_player(player:get_player_name(), minetest.colorize(mc_core.col.log, "[Minetest Classroom] You no longer have access to this classroom.")) end else minetest.chat_send_player(player:get_player_name(), minetest.colorize(mc_core.col.log, "[Minetest Classroom] Selected coordinate not found! Please report this issue to a server administrator.")) @@ -1361,7 +1524,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) for _,p_obj in pairs(minetest.get_connected_players()) do if p_obj:get_player_name() ~= player:get_player_name() or not mc_core.is_frozen(p_obj) then local p_realm = Realm.GetRealmFromPlayer(p_obj) - if p_realm and p_realm.ID == tonumber(pdata.realms[note_i]) and realm:getCategory().joinable(realm, p_obj) and (not destRealm:isHidden() or mc_core.checkPrivs(player, {teacher = true})) then + if p_realm and p_realm.ID == tonumber(pdata.realms[note_i]) and realm:Joinable(p_obj) then realm:TeleportPlayer(p_obj) p_obj:set_pos(pdata.coords[note_i]) end diff --git a/mods/mc_teacher/functions.lua b/mods/mc_teacher/functions.lua index d8b21886..2074ec3f 100644 --- a/mods/mc_teacher/functions.lua +++ b/mods/mc_teacher/functions.lua @@ -152,9 +152,7 @@ end function mc_teacher.get_server_role(player) local pname = (type(player) == "string" and player) or (player:is_player() and player:get_player_name()) or "" local privs = minetest.get_player_privs(pname) - if not privs["student"] then - return mc_teacher.ROLES.NONE - elseif not privs["teacher"] then + if not privs["teacher"] then return mc_teacher.ROLES.STUDENT elseif not privs["server"] then return mc_teacher.ROLES.TEACHER @@ -167,7 +165,7 @@ function mc_teacher.save_realm(player, context, fields) local realm = Realm.GetRealm(context.edit_realm.id) if realm then if not fields.no_cat_override then - if context.edit_realm.type == mc_teacher.R.CAT_KEY.SPAWN then + if context.edit_realm.type == mc_teacher.R.CAT_KEY.SPAWN and realm.ID ~= mc_worldManager.GetSpawnRealm().ID then if realm:isHidden() or realm:isDeleted() then minetest.chat_send_player(player:get_player_name(), minetest.colorize(mc_core.col.log, "[Minetest Classroom] This classroom is currently "..(realm:isHidden() and "hidden" or "being deleted"..", so it could not be set as the server's spawn classroom."))) else @@ -180,6 +178,8 @@ function mc_teacher.save_realm(player, context, fields) end realm.Name = (mc_core.trim(fields.erealm_name or "") ~= "" and fields.erealm_name) or (mc_core.trim(context.edit_realm.name or "") ~= "" and context.edit_realm.name) or "Unnamed classroom" realm:UpdateRealmPrivilege(context.edit_realm.privs or {}) + realm:UpdateSkybox(context.skyboxes[context.edit_realm.skybox]) + realm:UpdateMusic(context.music[context.edit_realm.music], 100) -- update players in realm local players_in_realm = realm:GetPlayersAsArray() @@ -187,8 +187,136 @@ function mc_teacher.save_realm(player, context, fields) local p_obj = minetest.get_player_by_name(p) if p_obj then realm:ApplyPrivileges(p_obj) + realm:ApplySkybox(p_obj) + realm:ApplyMusic(p_obj) end end minetest.chat_send_player(player:get_player_name(), minetest.colorize(mc_core.col.log, "[Minetest Classroom] Classroom updated!")) end -end \ No newline at end of file +end + +function mc_teacher.timeout(player) + local pmeta = player and player:is_player() and player:get_meta() + if pmeta then + local realm = Realm.GetRealmFromPlayer(player) or {ID = 0} + local spawn = mc_worldManager.GetSpawnRealm() + if realm.ID ~= spawn.ID then + mc_core.temp_unfreeze_and_run(player, spawn.TeleportPlayer, spawn, player) + end + pmeta:set_string("mc_teacher:timeout", minetest.serialize(true)) + end +end + +function mc_teacher.end_timeout(player) + local pmeta = player and player:is_player() and player:get_meta() + if pmeta then + pmeta:set_string("mc_teacher:timeout", "") + end +end + +function mc_teacher.is_in_timeout(player) + local pmeta = player and player:is_player() and player:get_meta() + return pmeta and minetest.deserialize(pmeta:get("mc_teacher:timeout")) or false +end + +function mc_teacher.get_player_tab_groups(player) + local pmeta = player and player:is_player() and player:get_meta() + return pmeta and minetest.deserialize(pmeta:get("mc_teacher:groups")) or {} +end + +function mc_teacher.get_group_index(group_id) + return tonumber(group_id or mc_teacher.PTAB.N) - mc_teacher.PTAB.N +end + +function mc_teacher.get_realm_prefix(realm, category) + if not realm or not category then + return "" + elseif category == mc_teacher.R.CAT_MAP[mc_teacher.R.CAT_KEY.INSTANCED] then + local raw_owners = realm:GetOwners() or {} + local owners = {} + for p,_ in pairs(raw_owners) do + table.insert(owners, p) + end + if next(owners) then + return "["..table.concat(owners, ", ").."] " + else + return "" + end + elseif category == mc_teacher.R.CAT_MAP[mc_teacher.R.CAT_KEY.SPAWN] then + return "[SPAWN] " + else + return "" + end +end + +---Audience utilities (mc_teacher.create_audience, find_audience_center, place_player_if_pos_clear) adapted from actions.lua in rubenwardy's classroom mod +---@see https://gitlab.com/rubenwardy/classroom/-/blob/master/actions.lua +---@license MIT: https://gitlab.com/rubenwardy/classroom/-/blob/1e7b11f824c03c882d74d5079d8275f3e297adea/LICENSE.txt + +local function find_audience_center(start, direction) + local endp = vector.add(start, vector.multiply(direction, 10)) + local rc = minetest.raycast(start, endp, false, true) + local first = rc:next() + if first then + return vector.subtract(first.under, direction) + else + return endp + end +end + +local function place_player_if_pos_clear(player, pos, realm, face_pos) + -- Move down to ground + local rc = minetest.raycast(pos, vector.add(pos, { x = 0, y = -20, z = 0 }), false, true) + local first = rc:next() + if first then + pos = vector.add(first.under, { x = 0, y = 1, z = 0 }) + end + + -- Check teacher is visible and audience position is within realm + if not minetest.line_of_sight(pos, face_pos) or not realm:ContainsCoordinate(pos) then + return false + end + + mc_core.temp_unfreeze_and_run(player, function() + realm:TeleportPlayer(player) + player:set_pos(pos) + local delta = vector.subtract(face_pos, pos) + player:set_look_horizontal(math.atan2(delta.z, delta.x) - math.pi / 2) + end) + return true +end + +function mc_teacher.create_audience(players, realm, focus_pname, focus_pos, direction) + local center = find_audience_center(focus_pos, direction) + local dir_perp = vector.normalize(vector.new(direction.z, direction.y, -direction.x)) + local row = 0 + local raw_column = 0 + local is_single_row = #players <= 5 + + while #players > 0 do + local p = table.remove(players, #players) + local p_obj = minetest.get_player_by_name(p) + if p ~= focus_pname and realm:Joinable(p_obj) then + -- calculate player position + local column = math.floor(raw_column / 2) * ((raw_column % 2) * 2 - 1) + raw_column % 2 + local delta = vector.add(vector.multiply(direction, row), vector.multiply(dir_perp, column)) + local pos = vector.add(center, delta) + if not place_player_if_pos_clear(p_obj, pos, realm, focus_pos) then + table.insert(players, p) + end + -- adjust position variables + row = is_single_row and row or ((row + 1) % 2) + if row ~= 1 then + raw_column = raw_column + 1 + end + if raw_column >= 200 and raw_column % 100 == 1 then + is_single_row = true + row = math.floor(raw_column / 100) + end + if row > 10 then + return false + end + end + end + return true +end diff --git a/mods/mc_teacher/gui.lua b/mods/mc_teacher/gui.lua index be3b42f2..dc2af404 100644 --- a/mods/mc_teacher/gui.lua +++ b/mods/mc_teacher/gui.lua @@ -59,14 +59,13 @@ local function get_privs(player) return privs end -local function role_to_fs_elem(role_string, caller_has_server_privs) +local function role_to_fs_elem(role, caller_has_server_privs) local map = { - [mc_teacher.ROLES.NONE] = {[true] = "p_role_none", [false] = "p_role_none"}, - [mc_teacher.ROLES.STUDENT] = {[true] = "p_role_student", [false] = "p_role_student"}, + [mc_teacher.ROLES.STUDENT] = {[true] = "p_role_student", [false] = "blocked_role_student"}, [mc_teacher.ROLES.TEACHER] = {[true] = "p_role_teacher", [false] = "blocked_role_teacher"}, [mc_teacher.ROLES.ADMIN] = {[true] = "p_role_admin", [false] = "blocked_role_admin"}, } - return role_string and map[role_string] and map[role_string][caller_has_server_privs] or "" + return role and map[role] and map[role][caller_has_server_privs] or "" end local function generate_player_table(p_list, p_priv_list) @@ -86,6 +85,8 @@ local function generate_player_table(p_list, p_priv_list) table.insert(combined_list, 0) end end + -- TODO: add role icon + table.insert(combined_list, 1) -- temp spacer table.insert(combined_list, player) end return table.concat(combined_list, ",") @@ -117,12 +118,18 @@ local function get_priv_button_states(p_list, p_priv_list) end for _,player in pairs(p_list) do local p_obj = minetest.get_player_by_name(player) - -- freeze if states["frozen"] ~= "mixed" and p_obj then if mc_core.is_frozen(p_obj) then - states["frozen"] = (states[priv] == true and "mixed") or false + states["frozen"] = (states["frozen"] == true and "mixed") or false else - states["frozen"] = (states[priv] == false and "mixed") or true + states["frozen"] = (states["frozen"] == false and "mixed") or true + end + end + if states["timeout"] ~= "mixed" and p_obj then + if mc_teacher.is_in_timeout(p_obj) then + states["timeout"] = (states["timeout"] == true and "mixed") or false + else + states["timeout"] = (states["timeout"] == false and "mixed") or true end end end @@ -229,7 +236,7 @@ function mc_teacher.show_whitelist_popup(player) "style_type[textlist,field;font=mono]", "style_type[button;border=false;font=mono,bold;bgimg=mc_pixel.png^[multiply:", mc_core.col.b.default, "]", - "textarea[", text_spacer, ",0.5;", text_width, ",1;;;Whitelisted IPv4 Addresses]", + "textarea[", text_spacer, ",0.5;", text_width, ",1;;;Whitelisted IPv4 addresses]", } if context.show_whitelist then @@ -272,7 +279,7 @@ end --[[ WHITELIST POPUP (LIST SHOWN) formspec_version[6] size[12.2,7.4] -textarea[0.55,0.5;5.4,1;;;Whitelisted IPv4 Addresses] +textarea[0.55,0.5;5.4,1;;;Whitelisted IPv4 addresses] textlist[0.6,0.9;5.3,5.9;whitelist;;1;false] button[6.3,4.2;5.3,0.8;toggle;ENABLE whitelist] button[6.3,5.1;5.3,0.8;remove;Delete selected] @@ -287,7 +294,7 @@ button[6.3,6;5.3,0.8;exit;Return] -- (LIST HIDDEN) formspec_version[6] size[12.2,7.4] -textarea[0.55,0.5;5.4,1;;;Whitelisted IPv4 Addresses] +textarea[0.55,0.5;5.4,1;;;Whitelisted IPv4 addresses] textarea[0.55,0.9;5.4,5;;;X IPv4 addresses in whitelist (including 127.0.0.1) - Whitelisted IP addresses are hidden by default to prevent lag when opening the whitelist popup when the whitelist is very large ] button[0.6,6;5.3,0.8;show_list;Show IP addresses] ]] @@ -320,6 +327,35 @@ function mc_teacher.show_edit_popup(player, realmID) privs = {interact = "nil", shout = "nil", fast = "nil", fly = "nil", noclip = "nil", give = "nil"}, id = realmID, } + + if not context.skyboxes then + context.skyboxes = {} + for _,sky in pairs(skybox.get_skies()) do + local sky_name = sky[1] + if sky_name then + table.insert(context.skyboxes, sky_name) + end + end + table.sort(context.skyboxes) + table.insert(context.skyboxes, "None") + end + local sky_name_to_i = {} + for i, sky in pairs(context.skyboxes) do + sky_name_to_i[sky] = i + end + context.edit_realm.skybox = sky_name_to_i[realm:GetSkybox() or skybox.get_default_sky()] or 1 + + if not context.music then + context.music = Realm.GetRegisteredMusic() + table.sort(context.music) + table.insert(context.music, "none") + end + local music_name_to_i = {} + for i, music in pairs(context.music) do + music_name_to_i[music] = i + end + context.edit_realm.music = music_name_to_i[realm:GetMusic() or "none"] + for priv,v in pairs(realm.Permissions or {interact = true, shout = true, fast = true}) do if context.edit_realm.privs[priv] ~= nil then context.edit_realm.privs[priv] = v @@ -340,7 +376,7 @@ function mc_teacher.show_edit_popup(player, realmID) "hypertext[", text_spacer + button_width + 0.1, ",1.82;", button_width + 0.1, ",1;;Internal ID]", "hypertext[", text_spacer + button_width + 0.1, ",2.22;", button_width + 0.1, ",2;;#", realmID, "]", - "textarea[", text_spacer, ",3.1;", width - 2*text_spacer, ",1;;;Default Privileges]", + "textarea[", text_spacer, ",3.1;", width - 2*text_spacer, ",1;;;Default privileges]", "style_type[textarea;font=mono]", "textarea[", text_spacer + 1.3, ",3.9;2.3,1;;;interact]", "textarea[", text_spacer + 1.3, ",4.3;2.3,1;;;shout]", @@ -350,12 +386,12 @@ function mc_teacher.show_edit_popup(player, realmID) "textarea[", text_spacer + button_width + 1.4, ",4.7;2.3,1;;;give]", "style_type[textarea;font=mono,bold]", - "image[", text_spacer, ",3.5;0.4,0.4;mc_teacher_check.png^[colorize:#56bf5f:alpha]", - "image[", text_spacer + 0.4, ",3.5;0.4,0.4;mc_teacher_ignore.png]", - "image[", text_spacer + 0.8, ",3.5;0.4,0.4;mc_teacher_delete.png]", - "image[", text_spacer + button_width + 0.1, ",3.5;0.4,0.4;mc_teacher_check.png^[colorize:#56bf5f:alpha]", - "image[", text_spacer + button_width + 0.5, ",3.5;0.4,0.4;mc_teacher_ignore.png]", - "image[", text_spacer + button_width + 0.9, ",3.5;0.4,0.4;mc_teacher_delete.png]", + "image[", spacer - 0.1, ",3.45;0.5,0.5;mc_teacher_check.png^[colorize:#56bf5f:alpha]", + "image[", spacer + 0.3, ",3.45;0.5,0.5;mc_teacher_ignore.png]", + "image[", spacer + 0.7, ",3.45;0.5,0.5;mc_teacher_delete.png]", + "image[", spacer + button_width, ",3.45;0.5,0.5;mc_teacher_check.png^[colorize:#56bf5f:alpha]", + "image[", spacer + button_width + 0.4, ",3.45;0.5,0.5;mc_teacher_ignore.png]", + "image[", spacer + button_width + 0.8, ",3.45;0.5,0.5;mc_teacher_delete.png]", "tooltip[", text_spacer, ",3.5;0.4,0.4;ALLOW: Privilege will be granted\n(does NOT override universal privileges);#404040;#ffffff]", "tooltip[", text_spacer + 0.4, ",3.5;0.4,0.4;IGNORE: Privilege will be unaffected;#404040;#ffffff]", "tooltip[", text_spacer + 0.8, ",3.5;0.4,0.4;DENY: Privilege will not be granted\n(overrides universal privileges);#404040;#ffffff]", @@ -382,10 +418,10 @@ function mc_teacher.show_edit_popup(player, realmID) "checkbox[", spacer + button_width + 0.9, ",4.5;denypriv_noclip;;", tostring(context.edit_realm.privs.noclip == false), "]", "checkbox[", spacer + button_width + 0.9, ",4.9;denypriv_give;;", tostring(context.edit_realm.privs.give == false), "]", - "textarea[", text_spacer, ",5.2;", width - 2*text_spacer, ",1;;;Background Music]", - "dropdown[", spacer, " ,5.6;", width - 2*spacer, ",0.8;erealm_bgmusic;;1;true]", + "textarea[", text_spacer, ",5.2;", width - 2*text_spacer, ",1;;;Background music]", + "dropdown[", spacer, " ,5.6;", width - 2*spacer, ",0.8;erealm_music;", table.concat(context.music, ","), ";", context.edit_realm.music, ";true]", "textarea[", text_spacer, ",6.5;", width - 2*text_spacer, ",1;;;Skybox]", - "dropdown[", spacer, ",6.9;", width - 2*spacer, ",0.8;erealm_skybox;;1;true]", + "dropdown[", spacer, ",6.9;", width - 2*spacer, ",0.8;erealm_skybox;", table.concat(context.skyboxes, ","), ";", context.edit_realm.skybox, ";true]", "button[", spacer, " ,8.1;", button_width, ",0.8;save_realm;Save changes]", "button[4.2,8.1;", button_width, ",0.8;cancel;Cancel]", } @@ -400,7 +436,7 @@ field[0.6,0.9;7.1,0.8;realmname;;] textarea[0.55,1.8;3.6,1;;;Type] dropdown[0.6,2.2;3.5,0.8;realmcategory;Classroom,Spawn,Private;1;true] textarea[4.15,1.8;3.6,1;;;Internal ID] -textarea[0.55,3.1;7.2,1;;;Default Privileges] +textarea[0.55,3.1;7.2,1;;;Default privileges] textarea[1.85,3.9;2.3,1;;;interact] textarea[1.85,4.3;2.3,1;;;shout] textarea[1.85,4.7;2.3,1;;;fast] @@ -431,7 +467,7 @@ checkbox[4.6,4.9;ignorepriv_give;;true] checkbox[5,4.1;denypriv_fly;;false] checkbox[5,4.5;denypriv_noclip;;false] checkbox[5,4.9;denypriv_give;;false] -textarea[0.55,5.2;7.2,1;;;Background Music] +textarea[0.55,5.2;7.2,1;;;Background music] dropdown[0.6,5.6;7.1,0.8;bgmusic;;1;true] textarea[0.55,6.5;7.2,1;;;Skybox] dropdown[0.6,6.9;7.1,0.8;;;1;true] @@ -439,6 +475,81 @@ button[0.6,8.1;3.5,0.8;save_realm;Save changes] button[4.2,8.1;3.5,0.8;cancel;Cancel] ]] +function mc_teacher.show_group_popup(player, group_id) + local spacer = mc_teacher.fs_spacer + local text_spacer = mc_teacher.fs_t_spacer + local width = 12.2 + local height = 9.7 + local list_width = (width - 2*spacer - 1)/2 + local button_width = (width - 2*spacer - 0.1)/2 + + local pname = player:get_player_name() + local groups = mc_teacher.get_player_tab_groups(player) + local context = mc_teacher.get_fs_context(player) + + if not context.edit_group then + if group_id and group_id ~= 0 then + local index = mc_teacher.get_group_index(group_id) + context.edit_group = { + id = index, + members = groups[index].members or {}, + name = groups[index].name or "G"..tostring(index), + } + else + context.edit_group = { + id = 0, + members = {}, + name = "G"..tostring(#groups + mc_teacher.PTAB.N + 1), + } + end + + context.edit_group.sel_nm = 1 + context.edit_group.sel_m = 1 + context.edit_group.p_list = {} + for _,p in pairs(minetest.get_connected_players()) do + if p and p:is_player() and not mc_core.tableHas(context.edit_group.members, p:get_player_name()) then + table.insert(context.edit_group.p_list, p:get_player_name()) + end + end + end + + local fs = { + mc_core.draw_note_fs(width, height, {bg = "#baf5a2", accent = "#e0fccf"}), + "style_type[textarea;font=mono,bold;textcolor=#000000]", + "style_type[field;font=mono]", + "style_type[button;border=false;font=mono,bold;bgimg=mc_pixel.png^[multiply:", mc_core.col.b.default, "]", + + "textarea[", text_spacer, ",0.5;11.1,1;;;Group name]", + "field[", spacer, ",0.9;11,0.8;group_name;;", context.edit_group.name or "", "]", + "field_close_on_enter[group_name;false]", + "textarea[", text_spacer, ",1.8;5.1,1;;;Available players]", + "textlist[", spacer, ",2.2;", list_width, ",5.9;non_members;", table.concat(context.edit_group.p_list, ","), ";", context.edit_group.sel_nm, ";false]", + "textarea[", text_spacer + list_width + 1, ",1.8;5.1,1;;;Group members]", + "textlist[", spacer + list_width + 1, ",2.2;", list_width, ",5.9;members;", table.concat(context.edit_group.members, ","), ";", context.edit_group.sel_m, ";false]", + "image_button[", spacer + list_width + 0.1, ",2.2;0.8,2.9;mc_teacher_swap_arrow_add.png;member_add;;false;true]", + "image_button[", spacer + list_width + 0.1, ",5.2;0.8,2.9;mc_teacher_swap_arrow_delete.png;member_delete;;false;true]", + "button[", spacer, ",8.3;", button_width, ",0.8;save;", (context.edit_group.id == 0 and "Create new group") or "Save changes", "]", + "button[", spacer + button_width + 0.1, ",8.3;", button_width, ",0.8;cancel;Cancel]", + } + + minetest.show_formspec(pname, "mc_teacher:edit_group", table.concat(fs, "")) +end + +--[[ GROUP POPUP +formspec_version[6] +size[12.2,9.7] +textarea[0.55,0.5;11.1,1;;;Group name] +field[0.6,0.9;11,0.8;group_name;;] +textarea[0.55,1.8;5.1,1;;;Available players] +textlist[0.6,2.2;5,5.9;non_members;;1;false] +textarea[6.55,1.8;5.1,1;;;Group members] +textlist[6.6,2.2;5,5.9;members;;1;false] +image_button[5.7,2.2;0.8,2.9;blank.png;member_add;-->;false;true] +image_button[5.7,5.2;0.8,2.9;blank.png;member_delete;<--;false;true] +button[0.6,8.3;5.45,0.8;save;Save group] +button[6.15,8.3;5.45,0.8;cancel;Cancel] +]] + function mc_teacher.show_controller_fs(player, tab) local controller_width = 16.6 local controller_height = 10.4 @@ -472,10 +583,10 @@ function mc_teacher.show_controller_fs(player, tab) "style_type[textarea;font=mono,bold;textcolor=#000000]", "style_type[button;border=false;font=mono,bold;bgimg=mc_pixel.png^[multiply:", mc_core.col.b.default, "]", "textarea[", text_spacer, ",1;", panel_width - 2*text_spacer, ",1;;;Welcome to Minetest Classroom!]", - "textarea[", text_spacer, ",4.6;", panel_width - 2*text_spacer, ",1;;;Server Rules]", + "textarea[", text_spacer, ",4.6;", panel_width - 2*text_spacer, ",1;;;Server rules]", "style_type[textarea;font=mono]", - "textarea[", text_spacer, ",1.4;", panel_width - 2*text_spacer, ",3;;;", minetest.formspec_escape("This is the Teacher Controller, your tool for managing classrooms, player privileges, and server settings."), - "\n", minetest.formspec_escape("You cannot drop this tool, so you will never lose it. However, you can move it out of your hotbar and into your inventory or the toolbox."), "]", + "textarea[", text_spacer, ",1.4;", panel_width - 2*text_spacer, ",3;;;", minetest.formspec_escape("This is the teacher controller, your tool for managing classrooms, player privileges, and server settings."), + "\n", minetest.formspec_escape("You cannot drop or delete the teacher controller, so you will never lose it. However, you can move it out of your hotbar and into your inventory or the toolbox."), "]", "textarea[", text_spacer, ",5.0;", panel_width - 2*text_spacer, ",", has_server_privs and 3.9 or 4.8, ";;;", minetest.formspec_escape(rules), "]", has_server_privs and "button[0.6,9;7,0.8;server_edit_rules;Edit server rules]" or "", @@ -522,7 +633,7 @@ function mc_teacher.show_controller_fs(player, tab) if (realm:isHidden() and context.selected_c_tab == mc_teacher.CTAB.HIDDEN) or (not realm:isHidden() and ((context.selected_c_tab == mc_teacher.CTAB.PUBLIC and cat ~= mc_teacher.R.CAT_MAP[mc_teacher.R.CAT_KEY.INSTANCED]) or (context.selected_c_tab == mc_teacher.CTAB.PRIVATE and cat == mc_teacher.R.CAT_MAP[mc_teacher.R.CAT_KEY.INSTANCED]))) then - table.insert(classroom_list, table.concat({minetest.formspec_escape(realm.Name or ""), " (", playerCount, " player", playerCount == 1 and "" or "s", ")"})) + table.insert(classroom_list, table.concat({minetest.formspec_escape(mc_teacher.get_realm_prefix(realm, cat) or ""), minetest.formspec_escape(realm.Name or "Unnamed classroom"), " (", playerCount, " player", playerCount == 1 and "" or "s", ")"})) table.insert(context.realm_i_to_id, id) end end @@ -553,7 +664,7 @@ function mc_teacher.show_controller_fs(player, tab) })) if has_server_privs then table.insert(fs, table.concat({ - "textarea[", text_spacer, ",8.6;", panel_width - 2*text_spacer, ",1;;;Classroom Cleanup]", + "textarea[", text_spacer, ",8.6;", panel_width - 2*text_spacer, ",1;;;Classroom cleanup]", "button[", spacer, ",9;3.5,0.8;c_hidden_delete;Delete selected]", "button[", spacer + 3.6, ",9;3.5,0.8;c_hidden_deleteall;Delete all]", })) @@ -584,7 +695,7 @@ function mc_teacher.show_controller_fs(player, tab) if context.selected_mode == mc_teacher.MODES.EMPTY then table.insert(fs, table.concat({ - "textarea[", text_spacer, ",2.6;", panel_width - 2*text_spacer, ",1;;;Classroom Size]", + "textarea[", text_spacer, ",2.6;", panel_width - 2*text_spacer, ",1;;;Classroom size]", "textarea[", text_spacer, ",3.2;1,1;;;X =]", "textarea[", text_spacer + 2.4, ",3.2;1,1;;;Y =]", "textarea[", text_spacer + 4.8, ",3.2;1,1;;;Z =]", @@ -604,7 +715,7 @@ function mc_teacher.show_controller_fs(player, tab) if options_height >= 3.9 then table.insert(fs, table.concat({ "textarea[", text_spacer, ",5.2;3.6,1;;;Seed]", - "textarea[", text_spacer + 3.6, ",5.2;3.6,1;;;Sea Level]", + "textarea[", text_spacer + 3.6, ",5.2;3.6,1;;;Sea level]", "field[", spacer, ",5.6;3.5,0.8;realm_seed;;", minetest.formspec_escape(context.realm_seed) or "", "]", "field[", spacer + 3.6, ",5.6;3.5,0.8;realm_sealevel;;", minetest.formspec_escape(context.realm_sealevel) or "", "]", @@ -623,7 +734,7 @@ function mc_teacher.show_controller_fs(player, tab) table.insert(fs, table.concat({ "textarea[", text_spacer, ",6.5;3.6,1;;;Biome]", - "textarea[", text_spacer + 3.6, ",6.5;3.6,1;;;Chill Coefficient]", + "textarea[", text_spacer + 3.6, ",6.5;3.6,1;;;Chill coefficient]", "dropdown[", spacer, ",6.9;3.5,0.8;realm_biome;", table.concat(context.i_to_biome, ","), ";", context.realm_biome, ";true]", "field[", spacer + 3.6, ",6.9;3.5,0.8;realm_chill;;", minetest.formspec_escape(context.realm_chill) or "", "]", @@ -663,7 +774,7 @@ function mc_teacher.show_controller_fs(player, tab) context.name_to_i = name_to_i table.insert(fs, table.concat({ - "textarea[", text_spacer, ",2.6;", panel_width - 2*text_spacer, ",1;;;Digital Twin World]", + "textarea[", text_spacer, ",2.6;", panel_width - 2*text_spacer, ",1;;;Digital twin world]", "dropdown[", spacer, ",3;", panel_width - 2*spacer, ",0.8;realterrain;", table.concat(twins, ","), ";", context.name_to_i[context.selected_dem] or 1, ";false]", })) else @@ -674,7 +785,7 @@ function mc_teacher.show_controller_fs(player, tab) table.insert(fs, table.concat({ "container[0,", 2.6 + options_height, "]", - "textarea[", text_spacer, ",0;", panel_width - 2*text_spacer, ",1;;;Default Privileges]", + "textarea[", text_spacer, ",0;", panel_width - 2*text_spacer, ",1;;;Default privileges]", "style_type[textarea;font=mono]", "textarea[", text_spacer + 1.3, ",0.8;1.9,1;;;interact]", "textarea[", text_spacer + 1.3, ",1.2;1.9,1;;;shout]", @@ -682,12 +793,12 @@ function mc_teacher.show_controller_fs(player, tab) "textarea[", text_spacer + 4.9, ",0.8;1.9,1;;;fly]", "textarea[", text_spacer + 4.9, ",1.2;1.9,1;;;noclip]", "textarea[", text_spacer + 4.9, ",1.6;1.9,1;;;give]", - "image[", text_spacer, ",0.4;0.4,0.4;mc_teacher_check.png]", - "image[", text_spacer + 0.4, ",0.4;0.4,0.4;mc_teacher_ignore.png]", - "image[", text_spacer + 0.8, ",0.4;0.4,0.4;mc_teacher_delete.png]", - "image[", text_spacer + 3.6, ",0.4;0.4,0.4;mc_teacher_check.png]", - "image[", text_spacer + 4.0, ",0.4;0.4,0.4;mc_teacher_ignore.png]", - "image[", text_spacer + 4.4, ",0.4;0.4,0.4;mc_teacher_delete.png]", + "image[", spacer - 0.1, ",0.35;0.5,0.5;mc_teacher_check.png]", + "image[", spacer + 0.3, ",0.35;0.5,0.5;mc_teacher_ignore.png]", + "image[", spacer + 0.7, ",0.35;0.5,0.5;mc_teacher_delete.png]", + "image[", spacer + 3.5, ",0.35;0.5,0.5;mc_teacher_check.png]", + "image[", spacer + 3.9, ",0.35;0.5,0.5;mc_teacher_ignore.png]", + "image[", spacer + 4.3, ",0.35;0.5,0.5;mc_teacher_delete.png]", "tooltip[", text_spacer, ",0.4;0.4,0.4;ALLOW: Privilege will be granted\n(does NOT override universal privileges);#404040;#ffffff]", "tooltip[", text_spacer + 0.4, ",0.4;0.4,0.4;IGNORE: Privilege will be unaffected;#404040;#ffffff]", "tooltip[", text_spacer + 0.8, ",0.4;0.4,0.4;DENY: Privilege will not be granted\n(overrides universal privileges);#404040;#ffffff]", @@ -714,18 +825,44 @@ function mc_teacher.show_controller_fs(player, tab) "checkbox[", spacer + 4.4, ",1.4;denypriv_noclip;;", tostring(context.selected_privs.noclip == false), "]", "checkbox[", spacer + 4.4, ",1.8;denypriv_give;;", tostring(context.selected_privs.give == false), "]", "container_end[]", + })) + + if not context.skyboxes then + context.skyboxes = {} + for _,sky in pairs(skybox.get_skies()) do + local sky_name = sky[1] + if sky_name then + table.insert(context.skyboxes, sky_name) + end + end + table.sort(context.skyboxes) + table.insert(context.skyboxes, "None") + + local sky_name_to_i = {} + for i, sky in pairs(context.skyboxes) do + sky_name_to_i[sky] = i + end + context.selected_skybox = sky_name_to_i[skybox.get_default_sky()] or 1 + end + + if not context.music then + context.music = Realm.GetRegisteredMusic() + table.sort(context.music) + table.insert(context.music, "none") + context.selected_music = #context.music + end + + table.insert(fs, table.concat({ "style_type[textarea;font=mono,bold]", - "textarea[", text_spacer, ",", 4.7 + options_height, ";", panel_width - 2*text_spacer, ",1;;;Background Music]", - "dropdown[", spacer, ",", 5.1 + options_height, ";", panel_width - 2*spacer, ",0.8;bgmusic;None;1;false]", + "textarea[", text_spacer, ",", 4.7 + options_height, ";", panel_width - 2*text_spacer, ",1;;;Background music]", + "dropdown[", spacer, ",", 5.1 + options_height, ";", panel_width - 2*spacer, ",0.8;realm_music;", table.concat(context.music, ","), ";", context.selected_music, ";true]", "textarea[", text_spacer, ",", 6 + options_height, ";", panel_width - 2*text_spacer, ",1;;;Skybox]", - "dropdown[", spacer, ",", 6.4 + options_height, ";", panel_width - 2*spacer, ",0.8;skybox;Default;1;false]", + "dropdown[", spacer, ",", 6.4 + options_height, ";", panel_width - 2*spacer, ",0.8;realm_skybox;", table.concat(context.skyboxes, ","), ";", context.selected_skybox, ";true]", "scroll_container_end[]", })) return fs - -- TODO: Background Music and skyboxes - -- method: local backgroundSound = realm:get_data("background_sound")]] end, [mc_teacher.TABS.MAP] = function() local map_x = spacer + 0.025 @@ -737,7 +874,7 @@ function mc_teacher.show_controller_fs(player, tab) "hypertext[", text_spacer, ",0.1;", panel_width - 2*text_spacer, ",1;;]", "hypertext[", panel_width + text_spacer, ",0.1;", panel_width - 2*text_spacer, ",1;;]", "style_type[textarea;font=mono,bold;textcolor=#000000]", - "textarea[", text_spacer, ",1;", panel_width - 2*text_spacer, ",1;;;Surrounding Area]", + "textarea[", text_spacer, ",1;", panel_width - 2*text_spacer, ",1;;;Surrounding area]", "image[", map_x - 0.025, ",", map_y - 0.025, ";7.1,7.1;mc_pixel.png^[multiply:#000000]", "image[", map_x, ",", map_y, ";7.05,7.05;mc_pixel.png^[multiply:#808080]", } @@ -787,13 +924,13 @@ function mc_teacher.show_controller_fs(player, tab) table.insert(fs, table.concat({ "image[", 3.95 + (pos.x - round_px)*0.15, ",", 4.75 - (pos.z - round_pz)*0.15, ";0.4,0.4;mc_mapper_d", yaw, ".png^[transformFY", rotate ~= 0 and ("R"..rotate) or "", "]", - "textarea[", text_spacer, ",8.6;", panel_width - 2*text_spacer, ",1;;;Coordinate and Elevation Display]", + "textarea[", text_spacer, ",8.6;", panel_width - 2*text_spacer, ",1;;;Coordinate display]", "style_type[button,image_button;border=false;font=mono,bold;bgimg=mc_pixel.png^[multiply:", mc_core.col.b.default, "]", "button[", spacer, ",9;1.7,0.8;utmcoords;UTM]", "button[", spacer + 1.8, ",9;1.7,0.8;latloncoords;Lat/Lon]", "button[", spacer + 3.6, ",9;1.7,0.8;classroomcoords;Local]", "button[", spacer + 5.4, ",9;1.7,0.8;coordsoff;Off]", - "textarea[", panel_width + text_spacer, ",1;", panel_width - 2*text_spacer, ",1;;;Saved Coordinates]", + "textarea[", panel_width + text_spacer, ",1;", panel_width - 2*text_spacer, ",1;;;Saved coordinates]", })) local coord_list = get_saved_coords(player) @@ -810,6 +947,7 @@ function mc_teacher.show_controller_fs(player, tab) coord_list and #coord_list > 0 and "" or "style_type[button;bgimg=mc_pixel.png^[multiply:", mc_core.col.b.default, "]", "textarea[", panel_width + text_spacer, ",8.5;", panel_width - 2*text_spacer, ",1;;;Save current coordinates]", "style_type[textarea;font=mono]", + -- TODO: show coordinate info "textarea[", panel_width + text_spacer, ",7.6;", panel_width - 2*text_spacer, ",1;;;SELECTED\nLocal: (X, Y, Z)]", "textarea[", panel_width + spacer, ",8.9;6.2,0.9;note;;]", "style_type[image_button;bgimg=mc_pixel.png^[multiply:", mc_core.col.b.default, "]", @@ -833,21 +971,21 @@ function mc_teacher.show_controller_fs(player, tab) end, [mc_teacher.TABS.PLAYERS] = function() local this_realm = Realm.GetRealmFromPlayer(player) - context.selected_p_tab = context.selected_p_tab or "1" + context.selected_p_tab = context.selected_p_tab or mc_teacher.PTAB.STUDENTS context.selected_p_player = context.selected_p_player or 1 context.selected_p_mode = context.selected_p_mode or mc_teacher.PMODE.SELECTED if not context.p_list then context.p_list = {} - if context.selected_p_tab == "1" then + if context.selected_p_tab == mc_teacher.PTAB.STUDENTS then for student,_ in pairs(mc_teacher.students) do table.insert(context.p_list, student) end - elseif context.selected_p_tab == "2" then + elseif context.selected_p_tab == mc_teacher.PTAB.TEACHERS then for teacher,_ in pairs(mc_teacher.teachers) do table.insert(context.p_list, teacher) end - elseif context.selected_p_tab == "3" then + elseif context.selected_p_tab == mc_teacher.PTAB.CLASSROOM then if this_realm then for _,p in pairs(this_realm:GetPlayersAsArray() or {}) do local p_obj = minetest.get_player_by_name(p) @@ -856,6 +994,15 @@ function mc_teacher.show_controller_fs(player, tab) end end end + else + local groups = mc_teacher.get_player_tab_groups(player) + local index = mc_teacher.get_group_index(context.selected_p_tab) + for _,p in pairs(groups[index] and groups[index].members or {}) do + local p_obj = minetest.get_player_by_name(p) + if p_obj and p_obj:is_player() then + table.insert(context.p_list, p) + end + end end end local p_priv_list = {} @@ -901,6 +1048,7 @@ function mc_teacher.show_controller_fs(player, tab) r = "^[resize:25x25", o = "^[opacity:31", } + local fs = { "image[0,0;", controller_width, ",0.5;mc_pixel.png^[multiply:#737373]", "image_button_exit[0.2,0.05;0.4,0.4;mc_x.png;exit;;false;false]", @@ -909,55 +1057,63 @@ function mc_teacher.show_controller_fs(player, tab) "hypertext[", panel_width + text_spacer, ",0.1;", panel_width - 2*text_spacer, ",1;;]", "style_type[textarea;font=mono,bold;textcolor=#000000]", "style_type[button;border=false;font=mono,bold;bgimg=mc_pixel.png^[multiply:", mc_core.col.b.default, "]", - -- TODO: re-implement groups - "style[p_group_new,p_group_edit,p_group_delete;bgimg=mc_pixel.png^[multiply:", mc_core.col.b.blocked, "]", - -- TODO: re-impelment remaining actions - "style[p_audience,p_timeout;bgimg=mc_pixel.png^[multiply:", mc_core.col.b.blocked, "]", "style[p_mode_", context.selected_p_mode == mc_teacher.PMODE.ALL and "all" or context.selected_p_mode == mc_teacher.PMODE.TAB and "tab" or "selected", ";bgimg=mc_pixel.png^[multiply:", mc_core.col.b.selected, "]", context.selected_p_mode ~= mc_teacher.PMODE.SELECTED and "style[p_teleport;bgimg=mc_pixel.png^[multiply:"..mc_core.col.b.orange.."]" or "", - - "tabheader[", spacer, ",1.4;", panel_width - 2*spacer - 0.35, ",0.5;p_list_header;Students,Teachers,Classroom;", context.selected_p_tab, ";false;true]", + } + + if tonumber(context.selected_p_tab) <= mc_teacher.PTAB.N then + table.insert(fs, "style[p_group_edit,p_group_delete;bgimg=mc_pixel.png^[multiply:"..mc_core.col.b.blocked.."]") + end + local header_string = "Students,Teachers,Classroom" + local groups = mc_teacher.get_player_tab_groups(player) + for i, g in pairs(groups) do + header_string = header_string..","..g.name + end + + table.insert(fs, table.concat({ + "tabheader[", spacer, ",1.4;", panel_width - 2*spacer - 0.35, ",0.5;p_list_header;", header_string, ";", context.selected_p_tab, ";false;true]", + "button[", panel_width - spacer - 0.45, ",0.95;0.45,0.45;p_group_new;+]", "tablecolumns[image,align=center,padding=0.1,tooltip=shout,", "0=", img.shout, img.e, img.r, img.o, ",1=", img.shout, img.e, img.r, ",2=", img.shout, img.e, img.r, img.o, "^(", img.slash, img.e, img.r, "),3=", img.shout, "_o", img.e, img.r, ";", "image,align=center,padding=0.1,tooltip=interact,", "0=", img.interact, img.e, img.r, img.o, ",1=", img.interact, img.e, img.r, ",2=", img.interact, img.e, img.r, img.o, "^(", img.slash, img.e, img.r, "),3=", img.interact, "_o", img.e, img.r, ";", "image,align=center,padding=0.1,tooltip=fast,", "0=", img.fast, img.e, img.r, img.o, ",1=", img.fast, img.e, img.r, ",2=", img.fast, img.e, img.r, img.o, "^(", img.slash, img.e, img.r, "),3=", img.fast, "_o", img.e, img.r, ",4=mc_teacher_freeze", img.e, img.r, ";", "image,align=center,padding=0.1,tooltip=fly,", "0=", img.fly, img.e, img.r, img.o, ",1=", img.fly, img.e, img.r, ",2=", img.fly, img.e, img.r, img.o, "^(", img.slash, img.e, img.r, "),3=", img.fly, "_o", img.e, img.r, ";", "image,align=center,padding=0.1,tooltip=noclip,", "0=", img.noclip, img.e, img.r, img.o, ",1=", img.noclip, img.e, img.r, ",2=", img.noclip, img.e, img.r, img.o, "^(", img.slash, img.e, img.r, "),3=", img.noclip, "_o", img.e, img.r, ";", "image,align=center,padding=0.1,tooltip=give,", "0=", img.give, img.e, img.r, img.o, ",1=", img.give, img.e, img.r, ",2=", img.give, img.e, img.r, img.o, "^(", img.slash, img.e, img.r, "),3=", img.give, "_o", img.e, img.r, ";", - "text]", + "image,align=center,padding=0.45,0=mc_teacher_delete.png", img.r, ",1=mc_teacher_controller.png", img.r, ",2=mc_teacher_check.png", img.r, ";", + "text,padding=0.45]", "table[", spacer, ",1.4;", panel_width - 2*spacer, ",7.5;p_list;", generate_player_table(context.p_list, p_priv_list), ";", context.selected_p_player, "]", - - "button[", panel_width - spacer - 0.45, ",0.95;0.45,0.45;p_group_new;+]", + "button[", spacer, ",9;3.5,0.8;p_group_edit;Edit group]", "button[", spacer + 3.6, ",9;3.5,0.8;p_group_delete;Delete group]", - "textarea[", panel_width + text_spacer, ",1;", panel_width - 2*text_spacer, ",1;;;Action Mode]", + "textarea[", panel_width + text_spacer, ",1;", panel_width - 2*text_spacer, ",1;;;Action mode]", "button[", panel_width + spacer, ",1.4;2.3,0.8;p_mode_selected;Selected]", "button[", panel_width + spacer + 2.4, ",1.4;2.3,0.8;p_mode_tab;Tab]", "button[", panel_width + spacer + 4.8, ",1.4;2.3,0.8;p_mode_all;All]", - "textarea[", panel_width + text_spacer, ",2.3;", panel_width - 2*text_spacer, ",1;;;Privileges in this Classroom]", + "textarea[", panel_width + text_spacer, ",2.4;", panel_width - 2*text_spacer, ",1;;;Privileges in this classroom]", "style_type[textarea;font=mono]", - "textarea[", panel_width + text_spacer + 1.3, ",3.1;2.3,1;;;interact]", - "textarea[", panel_width + text_spacer + 1.3, ",3.5;2.3,1;;;shout]", - "textarea[", panel_width + text_spacer + 1.3, ",3.9;2.3,1;;;fast]", - "textarea[", panel_width + text_spacer + 4.9, ",3.1;2.3,1;;;fly]", - "textarea[", panel_width + text_spacer + 4.9, ",3.5;2.3,1;;;noclip]", - "textarea[", panel_width + text_spacer + 4.9, ",3.9;2.3,1;;;give]", + "textarea[", panel_width + text_spacer + 1.3, ",3.3;2.3,1;;;interact]", + "textarea[", panel_width + text_spacer + 1.3, ",3.7;2.3,1;;;shout]", + "textarea[", panel_width + text_spacer + 1.3, ",4.1;2.3,1;;;fast]", + "textarea[", panel_width + text_spacer + 4.9, ",3.3;2.3,1;;;fly]", + "textarea[", panel_width + text_spacer + 4.9, ",3.7;2.3,1;;;noclip]", + "textarea[", panel_width + text_spacer + 4.9, ",4.1;2.3,1;;;give]", "style_type[textarea;font=mono,bold]", - "image[", panel_width + text_spacer, ",2.7;0.4,0.4;mc_teacher_check.png]", - "image[", panel_width + text_spacer + 0.4, ",2.7;0.4,0.4;mc_teacher_ignore.png]", - "image[", panel_width + text_spacer + 0.8, ",2.7;0.4,0.4;mc_teacher_delete.png]", - "image[", panel_width + text_spacer + 3.6, ",2.7;0.4,0.4;mc_teacher_check.png]", - "image[", panel_width + text_spacer + 4.0, ",2.7;0.4,0.4;mc_teacher_ignore.png]", - "image[", panel_width + text_spacer + 4.4, ",2.7;0.4,0.4;mc_teacher_delete.png]", - - "image[", panel_width + spacer - 0.05 + ((priv_override_state.interact == true and 0) or (priv_override_state.interact == false and 0.8) or 0.4), ",3.1;0.4,0.4;mc_pixel.png^[multiply:", mc_core.col.t.green, "]", - "image[", panel_width + spacer - 0.05 + ((priv_override_state.shout == true and 0) or (priv_override_state.shout == false and 0.8) or 0.4), ",3.5;0.4,0.4;mc_pixel.png^[multiply:", mc_core.col.t.green, "]", - "image[", panel_width + spacer - 0.05 + ((priv_override_state.fast == true and 0) or (priv_override_state.fast == false and 0.8) or 0.4), ",3.9;0.4,0.4;mc_pixel.png^[multiply:", mc_core.col.t.green, "]", - "image[", panel_width + spacer + 3.55 + ((priv_override_state.fly == true and 0) or (priv_override_state.fly == false and 0.8) or 0.4), ",3.1;0.4,0.4;mc_pixel.png^[multiply:", mc_core.col.t.green, "]", - "image[", panel_width + spacer + 3.55 + ((priv_override_state.noclip == true and 0) or (priv_override_state.noclip == false and 0.8) or 0.4), ",3.5;0.4,0.4;mc_pixel.png^[multiply:", mc_core.col.t.green, "]", - "image[", panel_width + spacer + 3.55 + ((priv_override_state.give == true and 0) or (priv_override_state.give == false and 0.8) or 0.4), ",3.9;0.4,0.4;mc_pixel.png^[multiply:", mc_core.col.t.green, "]", - } + "image[", panel_width + spacer - 0.1, ",2.8;0.5,0.5;mc_teacher_check.png]", + "image[", panel_width + spacer + 0.3, ",2.8;0.5,0.5;mc_teacher_ignore.png]", + "image[", panel_width + spacer + 0.7, ",2.8;0.5,0.5;mc_teacher_delete.png]", + "image[", panel_width + spacer + 3.5, ",2.8;0.5,0.5;mc_teacher_check.png]", + "image[", panel_width + spacer + 3.9, ",2.8;0.5,0.5;mc_teacher_ignore.png]", + "image[", panel_width + spacer + 4.3, ",2.8;0.5,0.5;mc_teacher_delete.png]", + + "image[", panel_width + spacer - 0.05 + ((priv_override_state.interact == true and 0) or (priv_override_state.interact == false and 0.8) or 0.4), ",3.3;0.4,0.4;mc_pixel.png^[multiply:", mc_core.col.t.green, "]", + "image[", panel_width + spacer - 0.05 + ((priv_override_state.shout == true and 0) or (priv_override_state.shout == false and 0.8) or 0.4), ",3.7;0.4,0.4;mc_pixel.png^[multiply:", mc_core.col.t.green, "]", + "image[", panel_width + spacer - 0.05 + ((priv_override_state.fast == true and 0) or (priv_override_state.fast == false and 0.8) or 0.4), ",4.1;0.4,0.4;mc_pixel.png^[multiply:", mc_core.col.t.green, "]", + "image[", panel_width + spacer + 3.55 + ((priv_override_state.fly == true and 0) or (priv_override_state.fly == false and 0.8) or 0.4), ",3.3;0.4,0.4;mc_pixel.png^[multiply:", mc_core.col.t.green, "]", + "image[", panel_width + spacer + 3.55 + ((priv_override_state.noclip == true and 0) or (priv_override_state.noclip == false and 0.8) or 0.4), ",3.7;0.4,0.4;mc_pixel.png^[multiply:", mc_core.col.t.green, "]", + "image[", panel_width + spacer + 3.55 + ((priv_override_state.give == true and 0) or (priv_override_state.give == false and 0.8) or 0.4), ",4.1;0.4,0.4;mc_pixel.png^[multiply:", mc_core.col.t.green, "]", + })) if not context.selected_privs then context.selected_privs = priv_override_state or {interact = "nil", shout = "nil", fast = "nil", fly = "nil", noclip = "nil", give = "nil"} @@ -965,104 +1121,107 @@ function mc_teacher.show_controller_fs(player, tab) context.selected_privs = priv_state_to_sel_privs(context.selected_privs) if player_privs.interact ~= nil then - table.insert(fs, table.concat({"image[", panel_width + spacer - 0.05 + (player_privs.interact and 0 or 0.8), ",3.1;0.4,0.4;mc_pixel.png^[multiply:", mc_core.col.t.blue, "]"})) + table.insert(fs, table.concat({"image[", panel_width + spacer - 0.05 + (player_privs.interact and 0 or 0.8), ",3.3;0.4,0.4;mc_pixel.png^[multiply:", mc_core.col.t.blue, "]"})) end if player_privs.shout ~= nil then - table.insert(fs, table.concat({"image[", panel_width + spacer - 0.05 + (player_privs.shout and 0 or 0.8), ",3.5;0.4,0.4;mc_pixel.png^[multiply:", mc_core.col.t.blue, "]"})) + table.insert(fs, table.concat({"image[", panel_width + spacer - 0.05 + (player_privs.shout and 0 or 0.8), ",3.7;0.4,0.4;mc_pixel.png^[multiply:", mc_core.col.t.blue, "]"})) end if player_privs.fast ~= nil then - table.insert(fs, table.concat({"image[", panel_width + spacer - 0.05 + (player_privs.fast and 0 or 0.8), ",3.9;0.4,0.4;mc_pixel.png^[multiply:", mc_core.col.t.blue, "]"})) + table.insert(fs, table.concat({"image[", panel_width + spacer - 0.05 + (player_privs.fast and 0 or 0.8), ",4.1;0.4,0.4;mc_pixel.png^[multiply:", mc_core.col.t.blue, "]"})) end if player_privs.fly ~= nil then - table.insert(fs, table.concat({"image[", panel_width + spacer + 3.55 + (player_privs.fly and 0 or 0.8), ",3.1;0.4,0.4;mc_pixel.png^[multiply:", mc_core.col.t.blue, "]"})) + table.insert(fs, table.concat({"image[", panel_width + spacer + 3.55 + (player_privs.fly and 0 or 0.8), ",3.3;0.4,0.4;mc_pixel.png^[multiply:", mc_core.col.t.blue, "]"})) end if player_privs.noclip ~= nil then - table.insert(fs, table.concat({"image[", panel_width + spacer + 3.55 + (player_privs.noclip and 0 or 0.8), ",3.5;0.4,0.4;mc_pixel.png^[multiply:", mc_core.col.t.blue, "]"})) + table.insert(fs, table.concat({"image[", panel_width + spacer + 3.55 + (player_privs.noclip and 0 or 0.8), ",3.7;0.4,0.4;mc_pixel.png^[multiply:", mc_core.col.t.blue, "]"})) end if player_privs.give ~= nil then - table.insert(fs, table.concat({"image[", panel_width + spacer + 3.55 + (player_privs.give and 0 or 0.8), ",3.9;0.4,0.4;mc_pixel.png^[multiply:", mc_core.col.t.blue, "]"})) + table.insert(fs, table.concat({"image[", panel_width + spacer + 3.55 + (player_privs.give and 0 or 0.8), ",4.1;0.4,0.4;mc_pixel.png^[multiply:", mc_core.col.t.blue, "]"})) end table.insert(fs, table.concat({ - "checkbox[", panel_width + spacer, ",3.3;allowpriv_interact;;", tostring(context.selected_privs.interact == true), "]", - "checkbox[", panel_width + spacer, ",3.7;allowpriv_shout;;", tostring(context.selected_privs.shout == true), "]", - "checkbox[", panel_width + spacer, ",4.1;allowpriv_fast;;", tostring(context.selected_privs.fast == true), "]", - "checkbox[", panel_width + spacer + 0.4, ",3.3;ignorepriv_interact;;", tostring(context.selected_privs.interact == "nil"), "]", - "checkbox[", panel_width + spacer + 0.4, ",3.7;ignorepriv_shout;;", tostring(context.selected_privs.shout == "nil"), "]", - "checkbox[", panel_width + spacer + 0.4, ",4.1;ignorepriv_fast;;", tostring(context.selected_privs.fast == "nil"), "]", - "checkbox[", panel_width + spacer + 0.8, ",3.3;denypriv_interact;;", tostring(context.selected_privs.interact == false), "]", - "checkbox[", panel_width + spacer + 0.8, ",3.7;denypriv_shout;;", tostring(context.selected_privs.shout == false), "]", - "checkbox[", panel_width + spacer + 0.8, ",4.1;denypriv_fast;;", tostring(context.selected_privs.fast == false), "]", - "checkbox[", panel_width + spacer + 3.6, ",3.3;allowpriv_fly;;", tostring(context.selected_privs.fly == true), "]", - "checkbox[", panel_width + spacer + 3.6, ",3.7;allowpriv_noclip;;", tostring(context.selected_privs.noclip == true), "]", - "checkbox[", panel_width + spacer + 3.6, ",4.1;allowpriv_give;;", tostring(context.selected_privs.give == true), "]", - "checkbox[", panel_width + spacer + 4.0, ",3.3;ignorepriv_fly;;", tostring(context.selected_privs.fly == "nil"), "]", - "checkbox[", panel_width + spacer + 4.0, ",3.7;ignorepriv_noclip;;", tostring(context.selected_privs.noclip == "nil"), "]", - "checkbox[", panel_width + spacer + 4.0, ",4.1;ignorepriv_give;;", tostring(context.selected_privs.give == "nil"), "]", - "checkbox[", panel_width + spacer + 4.4, ",3.3;denypriv_fly;;", tostring(context.selected_privs.fly == false), "]", - "checkbox[", panel_width + spacer + 4.4, ",3.7;denypriv_noclip;;", tostring(context.selected_privs.noclip == false), "]", - "checkbox[", panel_width + spacer + 4.4, ",4.1;denypriv_give;;", tostring(context.selected_privs.give == false), "]", - "button[", panel_width + spacer, ",4.4;3.5,0.8;p_priv_update;Update privs]", - "button[", panel_width + spacer + 3.6, ",4.4;3.5,0.8;p_priv_reset;Reset privs]", - - "textarea[", panel_width + text_spacer, ",5.3;", panel_width - 2*text_spacer, ",1;;;Actions]", - "button[", panel_width + spacer, ",5.7;2.3,0.8;p_teleport;Teleport]", - "button[", panel_width + spacer + 2.4, ",5.7;2.3,0.8;p_bring;Bring]", - "button[", panel_width + spacer + 4.8, ",5.7;2.3,0.8;p_audience;Audience]", - "button[", panel_width + spacer + 2.4, ",7.5;2.3,0.8;p_kick;Kick]", - "button[", panel_width + spacer + 4.8, ",7.5;2.3,0.8;p_ban;Ban]", - create_state_button(panel_width + spacer, 6.6, 2.3, 0.8, {[true] = "p_mute", [false] = "p_unmute"}, + "checkbox[", panel_width + spacer, ",3.5;allowpriv_interact;;", tostring(context.selected_privs.interact == true), "]", + "checkbox[", panel_width + spacer, ",3.9;allowpriv_shout;;", tostring(context.selected_privs.shout == true), "]", + "checkbox[", panel_width + spacer, ",4.3;allowpriv_fast;;", tostring(context.selected_privs.fast == true), "]", + "checkbox[", panel_width + spacer + 0.4, ",3.5;ignorepriv_interact;;", tostring(context.selected_privs.interact == "nil"), "]", + "checkbox[", panel_width + spacer + 0.4, ",3.9;ignorepriv_shout;;", tostring(context.selected_privs.shout == "nil"), "]", + "checkbox[", panel_width + spacer + 0.4, ",4.3;ignorepriv_fast;;", tostring(context.selected_privs.fast == "nil"), "]", + "checkbox[", panel_width + spacer + 0.8, ",3.5;denypriv_interact;;", tostring(context.selected_privs.interact == false), "]", + "checkbox[", panel_width + spacer + 0.8, ",3.9;denypriv_shout;;", tostring(context.selected_privs.shout == false), "]", + "checkbox[", panel_width + spacer + 0.8, ",4.3;denypriv_fast;;", tostring(context.selected_privs.fast == false), "]", + "checkbox[", panel_width + spacer + 3.6, ",3.5;allowpriv_fly;;", tostring(context.selected_privs.fly == true), "]", + "checkbox[", panel_width + spacer + 3.6, ",3.9;allowpriv_noclip;;", tostring(context.selected_privs.noclip == true), "]", + "checkbox[", panel_width + spacer + 3.6, ",4.3;allowpriv_give;;", tostring(context.selected_privs.give == true), "]", + "checkbox[", panel_width + spacer + 4.0, ",3.5;ignorepriv_fly;;", tostring(context.selected_privs.fly == "nil"), "]", + "checkbox[", panel_width + spacer + 4.0, ",3.9;ignorepriv_noclip;;", tostring(context.selected_privs.noclip == "nil"), "]", + "checkbox[", panel_width + spacer + 4.0, ",4.3;ignorepriv_give;;", tostring(context.selected_privs.give == "nil"), "]", + "checkbox[", panel_width + spacer + 4.4, ",3.5;denypriv_fly;;", tostring(context.selected_privs.fly == false), "]", + "checkbox[", panel_width + spacer + 4.4, ",3.9;denypriv_noclip;;", tostring(context.selected_privs.noclip == false), "]", + "checkbox[", panel_width + spacer + 4.4, ",4.3;denypriv_give;;", tostring(context.selected_privs.give == false), "]", + "button[", panel_width + spacer, ",4.6;3.5,0.8;p_priv_update;Update privs]", + "button[", panel_width + spacer + 3.6, ",4.6;3.5,0.8;p_priv_reset;Reset privs]", + + "textarea[", panel_width + text_spacer, ",5.5;", panel_width - 2*text_spacer, ",1;;;Global actions]", + "button[", panel_width + spacer, ",5.9;2.3,0.8;p_teleport;Teleport]", + "button[", panel_width + spacer + 2.4, ",5.9;2.3,0.8;p_bring;Bring]", + "button[", panel_width + spacer + 4.8, ",5.9;2.3,0.8;p_audience;Audience]", + create_state_button(panel_width + spacer, 6.8, 2.3, 0.8, {[true] = "p_mute", [false] = "p_unmute"}, {[true] = "Mute", [false] = "Unmute", default = "Mute"}, priv_b_state.shout), - create_state_button(panel_width + spacer + 2.4, 6.6, 2.3, 0.8, {[true] = "p_deactivate", [false] = "p_reactivate"}, + create_state_button(panel_width + spacer + 2.4, 6.8, 2.3, 0.8, {[true] = "p_deactivate", [false] = "p_reactivate"}, {[true] = "Deactivate", [false] = "Reactivate", default = "Activate"}, priv_b_state.interact), - create_state_button(panel_width + spacer + 4.8, 6.6, 2.3, 0.8, {[true] = "p_freeze", [false] = "p_unfreeze"}, + create_state_button(panel_width + spacer + 4.8, 6.8, 2.3, 0.8, {[true] = "p_freeze", [false] = "p_unfreeze"}, {[true] = "Freeze", [false] = "Unfreeze", default = "Freeze"}, priv_b_state.frozen), - "button[", panel_width + spacer, ",7.5;2.3,0.8;p_timeout;Timeout]", + create_state_button(panel_width + spacer, 7.7, 2.3, 0.8, {[true] = "p_timeout", [false] = "p_endtimeout"}, + {[true] = "Timeout", [false] = "End timeout", default = "Timeout"}, priv_b_state.timeout), + "button[", panel_width + spacer + 2.4, ",7.7;2.3,0.8;p_kick;Kick]", + "button[", panel_width + spacer + 4.8, ",7.7;2.3,0.8;p_ban;Ban]", - "textarea[", panel_width + text_spacer, ",8.4;", panel_width - 2*text_spacer, ",1;;;Server Role]", + "textarea[", panel_width + text_spacer, ",8.6;", panel_width - 2*text_spacer, ",1;;;Server role]", + "style[blocked_role_student,blocked_role_teacher,blocked_role_admin;bgimg=mc_pixel.png^[multiply:"..mc_core.col.b.blocked.."]", })) - if not has_server_privs then - table.insert(fs, "style[blocked_role_teacher,blocked_role_admin;bgimg=mc_pixel.png^[multiply:"..mc_core.col.b.blocked.."]") - end if selected_player then table.insert(fs, "style["..role_to_fs_elem(mc_teacher.get_server_role(selected_player), has_server_privs)..";bgimg=mc_pixel.png^[multiply:"..mc_core.col.b.selected.."]") end table.insert(fs, table.concat({ - "image[", panel_width + spacer, ",8.8;3.5,1;mc_pixel.png^[multiply:", mc_core.col.t.blue, "]", - "image[", panel_width + spacer + 3.6, ",8.8;3.5,1;mc_pixel.png^[multiply:", mc_core.col.t.orange, "]", - "button[", panel_width + spacer + 0.1, ",8.9;1.6,0.8;p_role_none;None]", - "button[", panel_width + spacer + 1.8, ",8.9;1.6,0.8;p_role_student;Student]", - "button[", panel_width + spacer + 3.7, ",8.9;1.6,0.8;", has_server_privs and "p_role_teacher" or "blocked_role_teacher", ";Teacher]", - "button[", panel_width + spacer + 5.4, ",8.9;1.6,0.8;", has_server_privs and "p_role_admin" or "blocked_role_admin", ";Admin]", + "button[", panel_width + spacer, ",9;2.3,0.8;", has_server_privs and "p_role_student" or "blocked_role_student", ";Student]", + "button[", panel_width + spacer + 2.4, ",9;2.3,0.8;", has_server_privs and "p_role_teacher" or "blocked_role_teacher", ";Teacher]", + "button[", panel_width + spacer + 4.8, ",9;2.3,0.8;", has_server_privs and "p_role_admin" or "blocked_role_admin", ";Admin]", "tooltip[p_mode_selected;The selected player;#404040;#ffffff]", "tooltip[p_mode_tab;All players in the selected tab;#404040;#ffffff]", "tooltip[p_mode_all;All online players;#404040;#ffffff]", - "tooltip[p_role_none;No privileges\nListed as a student\nCan not use classroom tools;#404040;#ffffff]", - "tooltip[p_role_student;Privileges: student\nListed as a student\nCan use student tools;#404040;#ffffff]", - "tooltip[", has_server_privs and "p_role_teacher" or "blocked_role_teacher", ";Privileges: student, teacher\nListed as a teacher\nCan use student and teacher tools;#404040;#ffffff]", - "tooltip[", has_server_privs and "p_role_admin" or "blocked_role_admin", ";Privileges: student, teacher, server\nListed as a teacher\nCan use student, teacher, and administrator tools;#404040;#ffffff]", + "tooltip[", has_server_privs and "p_role_student" or "blocked_role_student", ";No universal privileges\nCan use student tools", has_server_privs and "" or "\n(Only administrators can change roles)", ";#404040;#ffffff]", + "tooltip[", has_server_privs and "p_role_teacher" or "blocked_role_teacher", ";Universal privileges: teacher\nCan use student and teacher tools", has_server_privs and "" or "\n(Only administrators can change roles)", ";#404040;#ffffff]", + "tooltip[", has_server_privs and "p_role_admin" or "blocked_role_admin", ";Universal privileges: teacher, server\nCan use student, teacher, and administrator tools", has_server_privs and "" or "\n(Only administrators can change roles)", ";#404040;#ffffff]", - "tooltip[", panel_width + text_spacer, ",2.7;0.4,0.4;ALLOW: Privilege will be granted\n(overrides universal privileges);#404040;#ffffff]", - "tooltip[", panel_width + text_spacer + 0.4, ",2.7;0.4,0.4;IGNORE: Privilege will be unaffected;#404040;#ffffff]", - "tooltip[", panel_width + text_spacer + 0.8, ",2.7;0.4,0.4;DENY: Privilege will not be granted\n(overrides universal privileges);#404040;#ffffff]", - "tooltip[", panel_width + text_spacer + 3.6, ",2.7;0.4,0.4;ALLOW: Privilege will be granted\n(overrides universal privileges);#404040;#ffffff]", - "tooltip[", panel_width + text_spacer + 4.0, ",2.7;0.4,0.4;IGNORE: Privilege will be unaffected;#404040;#ffffff]", - "tooltip[", panel_width + text_spacer + 4.4, ",2.7;0.4,0.4;DENY: Privilege will not be granted\n(overrides universal privileges);#404040;#ffffff]", - "tooltip[p_mute;Revokes the shout privilege globally;#404040;#ffffff]", - "tooltip[p_unmute;Re-grants the shout privilege globally;#404040;#ffffff]", - "tooltip[p_deactivate;Revokes the interact privilege globally;#404040;#ffffff]", - "tooltip[p_reactivate;Re-grants the interact privilege globally;#404040;#ffffff]", + "tooltip[", panel_width + text_spacer, ",2.8;0.4,0.4;ALLOW: Privilege will be granted\n(overrides universal privileges);#404040;#ffffff]", + "tooltip[", panel_width + text_spacer + 0.4, ",2.8;0.4,0.4;IGNORE: Privilege will be unaffected;#404040;#ffffff]", + "tooltip[", panel_width + text_spacer + 0.8, ",2.8;0.4,0.4;DENY: Privilege will not be granted\n(overrides universal privileges);#404040;#ffffff]", + "tooltip[", panel_width + text_spacer + 3.6, ",2.8;0.4,0.4;ALLOW: Privilege will be granted\n(overrides universal privileges);#404040;#ffffff]", + "tooltip[", panel_width + text_spacer + 4.0, ",2.8;0.4,0.4;IGNORE: Privilege will be unaffected;#404040;#ffffff]", + "tooltip[", panel_width + text_spacer + 4.4, ",2.8;0.4,0.4;DENY: Privilege will not be granted\n(overrides universal privileges);#404040;#ffffff]", + "tooltip[p_mute;Revokes the shout privilege universally;#404040;#ffffff]", + "tooltip[p_unmute;Re-grants the shout privilege universally;#404040;#ffffff]", + "tooltip[p_deactivate;Revokes the interact privilege universally;#404040;#ffffff]", + "tooltip[p_reactivate;Re-grants the interact privilege universally;#404040;#ffffff]", "tooltip[p_freeze;Disables player movement;#404040;#ffffff]", "tooltip[p_unfreeze;Re-enables player movement;#404040;#ffffff]", "tooltip[p_teleport;Teleports you to the selected player;#404040;#ffffff]", "tooltip[p_bring;Teleports players to your position;#404040;#ffffff]", - "tooltip[p_audience;This action has not been implemented yet!;#404040;#ffffff]" --[[Teleports players to you, standing in a semicircle facing you;#404040;#ffffff]"]], - "tooltip[p_timeout;This action has not been implemented yet!;#404040;#ffffff]" --[[Teleports players to spawn and\nprevents them from joining classrooms;#404040;#ffffff]"]], - "tooltip[p_untimeout;Allows players to join classrooms again;#404040;#ffffff]", + "tooltip[p_audience;This action has not been implemented yet!;#404040;#ffffff]", --[[Teleports players to you, standing in a semicircle facing you;#404040;#ffffff]"]] + "tooltip[p_timeout;Teleports players to spawn and\nprevents them from joining classrooms;#404040;#ffffff]", + "tooltip[p_endtimeout;Allows players to join classrooms again;#404040;#ffffff]", + "tooltip[p_group_new;Add a new group;#404040;#ffffff]", })) + if tonumber(context.selected_p_tab) <= mc_teacher.PTAB.N then + table.insert(fs, table.concat({ + "tooltip[p_group_edit;This is a predefined group that can not be edited;#404040;#ffffff]", + "tooltip[p_group_delete;This is a predefined group that can not be deleted;#404040;#ffffff]", + })) + end + return fs end, [mc_teacher.TABS.MODERATION] = function() @@ -1120,7 +1279,7 @@ function mc_teacher.show_controller_fs(player, tab) if add_server then table.insert(context.indexed_chat_players, mc_core.SERVER_USER) local server_messages = {} - for _, msg_list in pairs(server_msg or {}) do + for _,msg_list in pairs(server_msg or {}) do for _,msg_table in pairs(msg_list) do if msg_table.anonymous and msg_table.recipient ~= mc_teacher.M.RECIP.ADMIN then table.insert(server_messages, msg_table) @@ -1200,7 +1359,7 @@ function mc_teacher.show_controller_fs(player, tab) end table.insert(fs, table.concat({ - "textarea[", text_spacer, ",1;", panel_width - 2*text_spacer, ",1;;;Message Logs]", + "textarea[", text_spacer, ",1;", panel_width - 2*text_spacer, ",1;;;Message logs]", "textlist[", spacer, ",1.4;", panel_width - 2*spacer, ",5.5;mod_log_players;", table.concat(context.indexed_chat_players, ","), ";", context.player_chat_index, ";false]", })) @@ -1237,7 +1396,7 @@ function mc_teacher.show_controller_fs(player, tab) end table.insert(fs, table.concat({ - "textarea[", panel_width + text_spacer, ",1;", panel_width - 2*text_spacer, ",1;;;Sent Messages]", + "textarea[", panel_width + text_spacer, ",1;", panel_width - 2*text_spacer, ",1;;;Logged messages]", "textlist[", panel_width + spacer, ",1.4;", panel_width - 2*spacer, ",6.4;mod_log_messages;", table.concat(player_log, ","), ";", context.message_chat_index, ";false]", "textarea[", panel_width + text_spacer, ",8;", panel_width - 2*text_spacer, ",1;;;", display_message and display_message.header or "Unknown", "]", "style_type[textarea;font=mono]", @@ -1289,7 +1448,7 @@ function mc_teacher.show_controller_fs(player, tab) local selected = report_log[context.report_i_to_idx[context.selected_report]] table.insert(fs, table.concat({ - "textarea[", text_spacer, ",1;", panel_width - 2*text_spacer, ",1;;;Report Log]", + "textarea[", text_spacer, ",1;", panel_width - 2*text_spacer, ",1;;;Report log]", "textlist[", spacer, ",1.4;", panel_width - 2*spacer, ",7.5;report_log;", table.concat(report_strings, ","), ";", context.selected_report, ";false]", "button[", spacer, ",9;3.5,0.8;", selected and "report_delete" or "blocked", ";Delete report]", "button[", spacer + 3.6, ",9;3.5,0.8;", selected and "report_clearlog" or "blocked", ";Clear report log]", @@ -1366,28 +1525,28 @@ function mc_teacher.show_controller_fs(player, tab) "style_type[textarea;font=mono,bold;textcolor=#000000]", "style_type[button;border=false;font=mono,bold;bgimg=mc_pixel.png^[multiply:", mc_core.col.b.default, "]", - "textarea[", text_spacer, ",1;", panel_width - 2*text_spacer, ",1;;;Global Messenger]", + "textarea[", text_spacer, ",1;", panel_width - 2*text_spacer, ",1;;;Global messenger]", "style_type[textarea,field;font=mono]", "textarea[", spacer, ",1.4;", panel_width - 2*spacer, ",2.3;server_message;;", context.server_message or "", "]", "style_type[textarea;font=mono,bold]", "textarea[", text_spacer, ",3.7;", panel_width - 2*text_spacer, ",1;;;Send as:]", "dropdown[", spacer, ",4.1;", panel_width - 2*spacer, ",0.8;server_message_type;General server message,Server message from yourself,Chat message from yourself;", context.server_message_type or mc_teacher.M.MODE.SERVER_ANON, ";true]", "textarea[", text_spacer, ",4.9;", panel_width - 2*text_spacer, ",1;;;Send to:]", - "button[", spacer, ",5.3;1.7,0.8;server_send_teachers;Teachers]", - "button[", spacer + 1.8, ",5.3;1.7,0.8;server_send_students;Students]", + "button[", spacer, ",5.3;1.7,0.8;server_send_students;Students]", + "button[", spacer + 1.8, ",5.3;1.7,0.8;server_send_teachers;Teachers]", "button[", spacer + 3.6, ",5.3;1.7,0.8;server_send_admins;Admins]", "button[", spacer + 5.4, ",5.3;1.7,0.8;server_send_all;Everyone]", - "textarea[", text_spacer, ",6.3;", panel_width - 2*text_spacer, ",1;;;Schedule Server Shutdown]", + "textarea[", text_spacer, ",6.3;", panel_width - 2*text_spacer, ",1;;;Schedule server shutdown]", "dropdown[", spacer, ",6.7;", panel_width - 2*spacer, ",0.8;server_shutdown_timer;", table.concat(time_options, ","), ";", context.time_index or 1, ";false]", "button[", spacer, ",7.6;3.5,0.8;server_shutdown_", mc_teacher.restart_scheduled.timer and "cancel" or "schedule", ";", mc_teacher.restart_scheduled.timer and "Cancel shutdown" or "Schedule", "]", "button[", spacer + 3.6, ",7.6;3.5,0.8;server_shutdown_now;Shutdown now]", - "textarea[", text_spacer, ",8.6;", panel_width - 2*text_spacer, ",1;;;Server Actions]", + "textarea[", text_spacer, ",8.6;", panel_width - 2*text_spacer, ",1;;;Server actions]", "button[", spacer, ",9;3.5,0.8;server_edit_rules;Server rules]", "button[", spacer + 3.6, ",9;3.5,0.8;server_whitelist;Whitelist]", - "textarea[", text_spacer + panel_width, ",1;", panel_width - 2*text_spacer, ",1;;;Game Information]", + "textarea[", text_spacer + panel_width, ",1;", panel_width - 2*text_spacer, ",1;;;Game information]", "style_type[textarea;font=mono]", "textarea[", text_spacer + panel_width, ",1.4;", panel_width - 2*text_spacer, ",1.2;;;", version.project or "Minetest", " version", version.string and ": "..version.string or " unknown", "\nServer uptime: ", mc_core.expand_time(uptime or 0), "]", @@ -1398,7 +1557,7 @@ function mc_teacher.show_controller_fs(player, tab) if context.selected_s_tab == mc_teacher.STAB.ONLINE then context.server_dyn_list = {} - for _, p in pairs(minetest.get_connected_players()) do + for _,p in pairs(minetest.get_connected_players()) do if p and p:is_player() then table.insert(context.server_dyn_list, p:get_player_name()) end @@ -1417,7 +1576,8 @@ function mc_teacher.show_controller_fs(player, tab) "textlist[", spacer + panel_width, ",3.2;", panel_width - 2*spacer, ",6.8;server_dyn;", table.concat(context.server_dyn_list, ","), ";", context.selected_s_dyn or 1, ";false]", })) else - context.server_dyn_list = minetest.get_ban_list() or "" + local ban_string = minetest.get_ban_list() or "" + context.server_dyn_list = string.gsub(ban_string, ", *", ",") table.insert(fs, table.concat({ "textlist[", spacer + panel_width, ",3.2;", panel_width - 2*spacer, ",5.7;server_dyn;", context.server_dyn_list, ";", context.selected_s_dyn or 1, ";false]", "button[", spacer + panel_width, ",9;", panel_width - 2*spacer, ",0.8;server_unban;Unban]", @@ -1500,7 +1660,7 @@ textarea[0.55,0.1;7.2,1;;;Overview] textarea[8.85,0.1;7.2,1;;;Dashboard] textarea[0.55,1;7.2,1;;;Welcome to Minetest Classroom!] textarea[0.55,1.4;7.2,3;;;This is the teacher controller!] -textarea[0.55,4.6;7.2,1;;;Server Rules] +textarea[0.55,4.6;7.2,1;;;Server rules] textarea[0.55,5;7.2,3.9;;;These are the server rules!] button[0.6,9;7.1,0.8;modifyrules;Edit Server Rules] textarea[10.7,0.9;5.4,1.8;;;Classrooms Find classrooms or players] @@ -1536,7 +1696,7 @@ textarea[12.45,2.3;3.6,1;;;Generation] dropdown[12.5,2.7;3.5,0.8;mode;Empty World,Schematic,Digital Twin;1;true] textarea[8.85,3.6;7.2,1;;;OPTIONS] box[8.9,4;7.1,0.8;#808080] -textarea[8.85,4.9;7.2,1;;;Default Privileges] +textarea[8.85,4.9;7.2,1;;;Default privileges] textarea[10.15,5.7;2.3,1;;;interact] textarea[10.15,6.1;2.3,1;;;shout] textarea[10.15,6.5;2.3,1;;;fast] @@ -1567,7 +1727,7 @@ checkbox[12.9,6.7;ignorepriv_give;;true] checkbox[13.3,5.9;denypriv_fly;;false] checkbox[13.3,6.3;denypriv_noclip;;false] checkbox[13.3,6.7;denypriv_give;;false] -textarea[8.85,7;7.2,1;;;Background Music] +textarea[8.85,7;7.2,1;;;Background music] dropdown[8.9,7.4;7.1,0.8;bgmusic;;1;true] textarea[8.85,8.3;7.2,1;;;Skybox] dropdown[8.9,8.7;7.1,0.8;;;1;true] @@ -1581,16 +1741,16 @@ box[8.275,0;0.05,10.4;#000000] image_button_exit[0.2,0.05;0.4,0.4;mc_x.png;exit;;false;false] textarea[0.55,0.1;7.1,1;;;Map] textarea[8.85,0.1;7.1,1;;;Coordinates] -textarea[0.55,1;7.1,1;;;Surrounding Area] +textarea[0.55,1;7.1,1;;;Surrounding area] box[0.6,1.4;7.1,7.1;#000000] box[0.625,1.425;7.05,7.05;#808080] image[4,4.8;0.3,0.3;] -textarea[0.55,8.6;7.1,1;;;Coordinate and Elevation Display] +textarea[0.55,8.6;7.1,1;;;Coordinate display] button[0.6,9;1.7,0.8;utmcoords;UTM] button[2.4,9;1.7,0.8;latloncoords;Lat/Long] button[4.2,9;1.7,0.8;classroomcoords;Local] button[6,9;1.7,0.8;coordsoff;Off] -textarea[8.85,1;7.1,1;;;Saved Coordinates] +textarea[8.85,1;7.1,1;;;Saved coordinates] textlist[8.9,1.4;7.1,4.4;coordlist;;8;false] textarea[8.85,8.5;7.1,1;;;Save current coordinates] image_button[15.1,8.9;0.9,0.9;blank.png;;Save;false;true] @@ -1616,67 +1776,64 @@ textlist[0.6,1.4;7.1,7.5;student_list;;1;false] button[7.25,0.95;0.45,0.45;p_group_new;+] button[0.6,9;3.5,0.8;p_group_edit;Edit group] button[4.2,9;3.5,0.8;p_group_delete;Delete group] -image[4.25,1.45;0.4,0.4;] -image[4.75,1.45;0.4,0.4;] -image[5.25,1.45;0.4,0.4;] -image[5.75,1.45;0.4,0.4;] -image[6.25,1.45;0.4,0.4;] -image[6.75,1.45;0.4,0.4;] -image[7.25,1.45;0.4,0.4;] -textarea[8.85,1;7.2,1;;;Action Mode] -button[8.9,1.4;2.3,0.8;;Selected] -button[11.3,1.4;2.3,0.8;;Group] -button[13.7,1.4;2.3,0.8;;All] -textarea[8.85,2.3;7.2,1;;;Privileges in this Classroom] -textarea[10.15,3.1;2.3,1;;;interact] -textarea[10.15,3.5;1.8,1;;;shout] -textarea[10.15,3.9;1.8,1;;;fast] -textarea[13.75,3.1;1.8,1;;;fly] -textarea[13.75,3.5;1.8,1;;;noclip] -textarea[13.75,3.9;1.8,1;;;give] -image[8.9,2.7;0.4,0.4;mc_teacher_check.png] -image[9.3,2.7;0.4,0.4;mc_teacher_ignore.png] -image[9.7,2.7;0.4,0.4;mc_teacher_delete.png] -image[12.5,2.7;0.4,0.4;mc_teacher_check.png] -image[12.9,2.7;0.4,0.4;mc_teacher_ignore.png] -image[13.3,2.7;0.4,0.4;mc_teacher_delete.png] -checkbox[8.9,3.3;allowpriv_interact;;false] -checkbox[8.9,3.7;allowpriv_shout;;false] -checkbox[8.9,4.1;allowpriv_fast;;false] -checkbox[9.3,3.3;ignorepriv_interact;;true] -checkbox[9.3,3.7;ignorepriv_shout;;true] -checkbox[9.3,4.1;ignorepriv_fast;;true] -checkbox[9.7,3.3;denypriv_interact;;false] -checkbox[9.7,3.7;denypriv_shout;;false] -checkbox[9.7,4.1;denypriv_fast;;false] -checkbox[12.5,3.3;allowpriv_fly;;false] -checkbox[12.5,3.7;allowpriv_noclip;;false] -checkbox[12.5,4.1;allowpriv_give;;false] -checkbox[12.9,3.3;ignorepriv_fly;;true] -checkbox[12.9,3.7;ignorepriv_noclip;;true] -checkbox[12.9,4.1;ignorepriv_give;;true] -checkbox[13.3,3.3;denypriv_fly;;false] -checkbox[13.3,3.7;denypriv_noclip;;false] -checkbox[13.3,4.1;denypriv_give;;false] -button[8.9,4.4;3.5,0.8;p_priv_update;Update privs] -button[12.5,4.4;3.5,0.8;p_priv_reset;Reset privs] -textarea[8.85,5.3;7.2,1;;;Actions] -button[8.9,5.7;2.3,0.8;p_teleport;Teleport] -button[11.3,5.7;2.3,0.8;p_bring;Bring] -button[13.7,5.7;2.3,0.8;p_audience;Audience] -button[8.9,6.6;2.3,0.8;p_mute;Mute] -button[11.3,6.6;2.3,0.8;p_deactivate;Deactivate] -button[13.7,6.6;2.3,0.8;p_freeze;Freeze] -button[8.9,7.5;2.3,0.8;p_timeout;Timeout] -button[11.3,7.5;2.3,0.8;p_kick;Kick] -button[13.7,7.5;2.3,0.8;p_ban;Ban] -textarea[8.9,8.4;7.2,1;;;Server Role] -box[12.5,8.8;3.5,1;#FFCC00] -box[8.9,8.8;3.5,1;#00FF00] -button[9,8.9;1.6,0.8;p_role_none;None] -button[10.7,8.9;1.6,0.8;p_role_student;Student] -button[12.6,8.9;1.6,0.8;p_role_teacher;Teacher] -button[14.3,8.9;1.6,0.8;p_role_admin;Admin] +image[0.65,1.45;0.4,0.4;] +image[1.15,1.45;0.4,0.4;] +image[1.65,1.45;0.4,0.4;] +image[2.15,1.45;0.4,0.4;] +image[2.65,1.45;0.4,0.4;] +image[3.15,1.45;0.4,0.4;] +image[3.75,1.45;0.4,0.4;] +textarea[8.85,1;7.2,1;;;Action mode] +button[8.9,1.4;2.3,0.8;p_mode_selected;Selected] +button[11.3,1.4;2.3,0.8;p_mode_tab;Tab] +button[13.7,1.4;2.3,0.8;p_mode_all;All] +textarea[8.85,2.4;7.2,1;;;Privileges in this classroom] +textarea[10.15,3.3;2.3,1;;;interact] +textarea[10.15,3.7;1.8,1;;;shout] +textarea[10.15,4.1;1.8,1;;;fast] +textarea[13.75,3.3;1.8,1;;;fly] +textarea[13.75,3.7;1.8,1;;;noclip] +textarea[13.75,4.1;1.8,1;;;give] +image[8.9,2.8;0.4,0.4;mc_teacher_check.png] +image[9.3,2.8;0.4,0.4;mc_teacher_ignore.png] +image[9.7,2.8;0.4,0.4;mc_teacher_delete.png] +image[12.5,2.8;0.4,0.4;mc_teacher_check.png] +image[12.9,2.8;0.4,0.4;mc_teacher_ignore.png] +image[13.3,2.8;0.4,0.4;mc_teacher_delete.png] +checkbox[8.9,3.5;allowpriv_interact;;false] +checkbox[8.9,3.9;allowpriv_shout;;false] +checkbox[8.9,4.3;allowpriv_fast;;false] +checkbox[9.3,3.5;ignorepriv_interact;;true] +checkbox[9.3,3.9;ignorepriv_shout;;true] +checkbox[9.3,4.3;ignorepriv_fast;;true] +checkbox[9.7,3.5;denypriv_interact;;false] +checkbox[9.7,3.9;denypriv_shout;;false] +checkbox[9.7,4.3;denypriv_fast;;false] +checkbox[12.5,3.5;allowpriv_fly;;false] +checkbox[12.5,3.9;allowpriv_noclip;;false] +checkbox[12.5,4.3;allowpriv_give;;false] +checkbox[12.9,3.5;ignorepriv_fly;;true] +checkbox[12.9,3.9;ignorepriv_noclip;;true] +checkbox[12.9,4.3;ignorepriv_give;;true] +checkbox[13.3,3.5;denypriv_fly;;false] +checkbox[13.3,3.9;denypriv_noclip;;false] +checkbox[13.3,4.3;denypriv_give;;false] +button[8.9,4.6;3.5,0.8;p_priv_update;Update privs] +button[12.5,4.6;3.5,0.8;p_priv_reset;Reset privs] +textarea[8.85,5.5;7.2,1;;;Global actions] +button[8.9,5.9;2.3,0.8;p_teleport;Teleport] +button[11.3,5.9;2.3,0.8;p_bring;Bring] +button[13.7,5.9;2.3,0.8;p_audience;Audience] +button[8.9,6.8;2.3,0.8;p_mute;Mute] +button[11.3,6.8;2.3,0.8;p_deactivate;Deactivate] +button[13.7,6.8;2.3,0.8;p_freeze;Freeze] +button[8.9,7.7;2.3,0.8;p_timeout;Timeout] +button[11.3,7.7;2.3,0.8;p_kick;Kick] +button[13.7,7.7;2.3,0.8;p_ban;Ban] +textarea[8.9,8.6;7.2,1;;;Server role] +button[8.9,9;2.3,0.8;p_role_student;Student] +button[11.3,9;2.3,0.8;p_role_teacher;Teacher] +button[13.7,9;2.3,0.8;p_role_admin;Admin] MODERATION: formspec_version[6] @@ -1686,14 +1843,14 @@ box[8.275,0;0.05,10.4;#000000] image_button_exit[0.2,0.05;0.4,0.4;mc_x.png;exit;;false;false] textarea[0.55,0.1;7.1,1;;;Moderation] textarea[8.85,0.1;7.1,1;;;Message Log] -textarea[0.55,1;7.2,1;;;Message Logs] +textarea[0.55,1;7.2,1;;;Message logs] textlist[0.6,1.4;7.1,5.5;mod_log_players;;1;false] button[0.6,7;3.5,0.8;mod_mute;Mute player] button[4.2,7;3.5,0.8;mod_clearlog;Clear player's log] textarea[0.55,8;7.2,1;;;Message player] textarea[0.6,8.4;6.3,1.4;mod_message;;] button[6.9,8.4;0.8,1.4;mod_send_message;Send] -textarea[8.85,1;7.2,1;;;Sent Messages] +textarea[8.85,1;7.2,1;;;Logged messages] textlist[8.9,1.4;7.1,6.4;mod_log_messages;;1;false] textarea[8.85,8;7.2,1;;;(message type)] textarea[8.85,8.4;7.2,1.4;;;add message text here!] @@ -1706,7 +1863,7 @@ box[8.275,0;0.05,10.4;#000000] image_button_exit[0.2,0.05;0.4,0.4;mc_x.png;exit;;false;false] textarea[0.55,0.1;7.1,1;;;Reports] textarea[8.85,0.1;7.1,1;;;Report Info] -textarea[0.55,1;7.2,1;;;Report Log] +textarea[0.55,1;7.2,1;;;Report log] textlist[0.6,1.4;7.1,7.5;report_log;;1;false] button[0.6,9;3.5,0.8;report_delete;Delete report] button[4.2,9;3.5,0.8;report_clearlog;Clear report log] @@ -1742,23 +1899,23 @@ box[8.275,0;0.05,10.4;#000000] image_button_exit[0.2,0.05;0.4,0.4;mc_x.png;exit;;false;false] textarea[0.55,0.1;7.1,1;;;Server Management] textarea[8.85,0.1;7.1,1;;;Server Information] -textarea[0.55,1;7.2,1;;;Global Messenger] +textarea[0.55,1;7.2,1;;;Global messenger] textarea[0.6,1.4;7.1,2.3;server_message;;] textarea[0.6,3.7;7.2,1;;;Send as:] dropdown[0.6,4.1;7.1,0.8;server_message_type;Anonymous server message,Server message from yourself,Chat message from yourself;1;true] textarea[0.6,4.9;7.2,1;;;Send to:] -button[2.4,5.3;1.7,0.8;server_send_teachers;Teachers] button[0.6,5.3;1.7,0.8;server_send_students;Students] +button[2.4,5.3;1.7,0.8;server_send_teachers;Teachers] button[4.2,5.3;1.7,0.8;server_send_admins;Admins] button[6,5.3;1.7,0.8;server_send_all;Everyone] -textarea[0.6,6.3;7.2,1;;;Schedule Server Shutdown] +textarea[0.6,6.3;7.2,1;;;Schedule server shutdown] dropdown[0.6,6.7;7.1,0.8;server_shutdown_timer;;1;false] button[0.6,7.6;3.5,0.8;server_shutdown_schedule;Schedule] button[4.2,7.6;3.5,0.8;server_shutdown_now;Shutdown now] -textarea[0.55,8.6;7.2,1;;;Server Actions] +textarea[0.55,8.6;7.2,1;;;Server actions] button[0.6,9;3.5,0.8;server_edit_rules;Server rules] button[4.2,9;3.5,0.8;server_whitelist;Whitelist] -textarea[8.85,1;7.2,1;;;Game Information] +textarea[8.85,1;7.2,1;;;Game information] textarea[8.85,1.4;7.2,1.4;;;Minetest version: x.x.x -- Server uptime: time] textarea[8.85,2.5;7.2,1;;;Online/Banned/Mods] textlist[8.9,2.9;7.1,6;server_dynamic;;1;false] diff --git a/mods/mc_teacher/init.lua b/mods/mc_teacher/init.lua index 7c3dc9af..5b0ac6e3 100644 --- a/mods/mc_teacher/init.lua +++ b/mods/mc_teacher/init.lua @@ -20,13 +20,12 @@ mc_teacher = { SERVER = "8", -- TODO: make dynamic for easier modification of notebook }, CTAB = {PUBLIC = "1", PRIVATE = "2", HIDDEN = "3"}, - PTAB = {STUDENTS = "1", TEACHERS = "2", CLASSROOM = "3"}, + PTAB = {STUDENTS = "1", TEACHERS = "2", CLASSROOM = "3", N = 3}, STAB = {BANNED = "1", ONLINE = "2", MODS = "3"}, ROLES = { - NONE = "roleless", - STUDENT = "student", - TEACHER = "teacher", - ADMIN = "administrator", + STUDENT = "1", + TEACHER = "2", + ADMIN = "3", }, MODES = {EMPTY = "1", SCHEMATIC = "2", TWIN = "3"}, PMODE = {SELECTED = "1", TAB = "2", ALL = "3"}, diff --git a/mods/mc_teacher/mod.conf b/mods/mc_teacher/mod.conf index 01193d5c..bc767e6a 100644 --- a/mods/mc_teacher/mod.conf +++ b/mods/mc_teacher/mod.conf @@ -1,4 +1,4 @@ name = mc_teacher description = Provides formspec access to the teacher functions and abilities. -depends = mc_core, mc_worldmanager, mc_rules, mc_mapper, networking, biomegen +depends = mc_core, mc_worldmanager, mc_rules, mc_mapper, networking, biomegen, skybox optional_depends = mc_toolhandler \ No newline at end of file diff --git a/mods/mc_tutorial/textures/mc_tutorial_reward_add.png b/mods/mc_teacher/textures/mc_teacher_swap_arrow_add.png similarity index 100% rename from mods/mc_tutorial/textures/mc_tutorial_reward_add.png rename to mods/mc_teacher/textures/mc_teacher_swap_arrow_add.png diff --git a/mods/mc_tutorial/textures/mc_tutorial_reward_delete.png b/mods/mc_teacher/textures/mc_teacher_swap_arrow_delete.png similarity index 100% rename from mods/mc_tutorial/textures/mc_tutorial_reward_delete.png rename to mods/mc_teacher/textures/mc_teacher_swap_arrow_delete.png diff --git a/mods/mc_tutorial/init.lua b/mods/mc_tutorial/init.lua index dcf6cdec..941e035b 100644 --- a/mods/mc_tutorial/init.lua +++ b/mods/mc_tutorial/init.lua @@ -68,7 +68,7 @@ local SAFE, CAUTION, UNSAFE = 0, 1, 2 -- basic privilege safety: may be worth moving? local PRIV_SAFETY = { -- basic privileges by overall safety -- SAFE: generally safe to give to players - ["fast"] = SAFE, ["fly"] = SAFE, ["interact"] = SAFE, ["shout"] = SAFE, ["student"] = SAFE, + ["fast"] = SAFE, ["fly"] = SAFE, ["interact"] = SAFE, ["shout"] = SAFE, -- CAUTION: privileges which may be appropriate to grant in some circumstances, but have the potential to be abused ["noclip"] = CAUTION, ["give"] = CAUTION, ["teleport"] = CAUTION, ["bring"] = CAUTION, ["creative"] = CAUTION, ["settime"] = CAUTION, ["debug"] = CAUTION, @@ -570,8 +570,8 @@ function mc_tutorial.show_record_fs(player) "textlist[", panel_width + spacer, ",1.4;", panel_width - 2*spacer, ",5.9;reward_selection;", concat_col_field_list(context.selected_rewards, ","), ";", context.reward_selected and context.reward_selected[2] or 1, ";false]", "style_type[button,image_button;border=false;font=mono,bold;bgimg=mc_pixel.png^[multiply:", mc_core.col.b.default, "]", "style_type[field;border=false;font=mono]", - "image_button[7.9,1.4;0.8,2.85;mc_tutorial_reward_add.png;reward_add;;false;true]", - "image_button[7.9,4.45;0.8,2.85;mc_tutorial_reward_delete.png;reward_delete;;false;true]", + "image_button[7.9,1.4;0.8,2.85;mc_teacher_swap_arrow_add.png;reward_add;;false;true]", + "image_button[7.9,4.45;0.8,2.85;mc_teacher_swap_arrow_delete.png;reward_delete;;false;true]", "textarea[", panel_width + text_spacer, ",7.4;", panel_width - 2*text_spacer, ",1;;;Quantity (WIP)]", "textarea[", panel_width + text_spacer, ",8.6;", panel_width - 2*text_spacer, ",1;;;Search for rewards (WIP)]", "image[", panel_width + spacer, ",7.8;", panel_width - 2*spacer, ",0.8;mc_pixel.png^[multiply:", mc_core.col.b.default, "]", @@ -733,7 +733,7 @@ function mc_tutorial.show_record_options_fs(player) "label[3.5,0.6;What would you like to do?]", "box[0.4,1.1;10.6,4.7;#0090a0]", "box[0.4,6.2;10.6,1.7;#9040a0]", - "label[3.9,1.5;Add an Action or Event]", + "label[3.9,1.5;Add an action or event]", "button_exit[0.6,1.8;5,0.8;wieldeditem;Wield an item]", "button_exit[5.8,1.8;5,0.8;playercontrol;Press keys]", "button_exit[0.6,2.8;5,0.8;getpos;Go to current position]", @@ -741,7 +741,7 @@ function mc_tutorial.show_record_options_fs(player) "button_exit[0.6,3.8;5,0.8;lookvertical;Look in current vertical dir.]", "button_exit[5.8,3.8;5,0.8;lookhorizontal;Look in current horizontal dir.]", "button[3.2,4.8;5,0.8;editor;Add action/event using editor]", - "label[4.6,6.6;Other Options]", + "label[4.6,6.6;Other options]", "button_exit[0.6,6.9;5,0.8;exit;Cancel]", "button_exit[5.8,6.9;5,0.8;stop;End recording]" } @@ -796,7 +796,7 @@ function mc_tutorial.show_event_popup_fs(player, is_edit, is_iso) "label[0.4,2.7;Registered items]", "textlist[0.4,2.9;5.8,4.8;epop_list;", table.concat(context.epop.list.list, ","), ";", context.epop.list.selected or 1, ";false]", "image[6.4,6.5;1.2,1.2;mc_tutorial_tutorialbook.png]", - "textarea[7.7,6.4;4.6,1.3;;;Item + desc]", + "textarea[7.7,6.4;4.6,1.3;;;Item + description]", "field[6.4,2.9;5.8,0.8;node;Punch (node);", context.epop.fields.node or "", "]", "field[6.4,4.2;5.8,0.8;tool;With (item);", context.epop.fields.tool or "", "]", "image_button[11.1,2.9;1.1,0.8;mc_tutorial_paste.png;node_import;;false;false]", @@ -817,7 +817,7 @@ function mc_tutorial.show_event_popup_fs(player, is_edit, is_iso) "label[0.4,2.7;Registered items]", "textlist[0.4,2.9;5.8,4.8;epop_list;", table.concat(context.epop.list.list, ","), ";", context.epop.list.selected or 1, ";false]", "image[6.4,6.5;1.2,1.2;mc_tutorial_tutorialbook.png]", - "textarea[7.7,6.4;4.6,1.3;;;Item + desc]", + "textarea[7.7,6.4;4.6,1.3;;;Item + description]", "field[6.4,2.9;5.8,0.8;node;Dig (node);", context.epop.fields.node or "", "]", "field[6.4,4.2;5.8,0.8;tool;With (item);", context.epop.fields.tool or "", "]", "image_button[11.1,2.9;1.1,0.8;mc_tutorial_paste.png;node_import;;false;false]", @@ -838,7 +838,7 @@ function mc_tutorial.show_event_popup_fs(player, is_edit, is_iso) "label[0.4,2.7;Registered nodes]", "textlist[0.4,2.9;5.8,4.8;epop_list;", table.concat(context.epop.list.list, ","), ";", context.epop.list.selected or 1, ";false]", "image[6.4,6.5;1.2,1.2;mc_tutorial_tutorialbook.png]", - "textarea[7.7,6.4;4.6,1.3;;;Node + desc]", + "textarea[7.7,6.4;4.6,1.3;;;Node + description]", "field[6.4,2.9;5.8,0.8;node;Place (node);", context.epop.fields.node or "", "]", "image_button[11.1,2.9;1.1,0.8;mc_tutorial_paste.png;node_import;;false;false]", "field_close_on_enter[node;false]", @@ -855,7 +855,7 @@ function mc_tutorial.show_event_popup_fs(player, is_edit, is_iso) "label[0.4,2.7;Registered items]", "textlist[0.4,2.9;5.8,4.8;epop_list;", table.concat(context.epop.list.list, ","), ";", context.epop.list.selected or 1, ";false]", "image[6.4,6.5;1.2,1.2;mc_tutorial_tutorialbook.png]", - "textarea[7.7,6.4;4.6,1.3;;;Item + desc]", + "textarea[7.7,6.4;4.6,1.3;;;Item + description]", "field[6.4,2.9;5.8,0.8;tool;Wield (item);", context.epop.fields.tool or "", "]", "image_button[11.1,2.9;1.1,0.8;mc_tutorial_paste.png;tool_import;;false;false]", "field_close_on_enter[tool;false]", @@ -874,8 +874,8 @@ function mc_tutorial.show_event_popup_fs(player, is_edit, is_iso) "textlist[0.4,2.9;5.58,4.8;epop_list;", table.concat(context.epop.list.list, ","), ";", context.epop.list.selected or 1, ";false]", "label[6.7,2.7;Keys to press]", "textlist[6.62,2.9;5.58,4.8;key_list;", next(keys) and table.concat(keys, ",") or minetest.formspec_escape("[none]"), ";", context.epop.fields.key_selected or 1, ";false]", - "image_button[5.98,2.9;0.64,2.4;mc_tutorial_reward_add.png;key_add;;false;true]", - "image_button[5.98,5.3;0.64,2.4;mc_tutorial_reward_delete.png;key_delete;;false;true]", + "image_button[5.98,2.9;0.64,2.4;mc_teacher_swap_arrow_add.png;key_add;;false;true]", + "image_button[5.98,5.3;0.64,2.4;mc_teacher_swap_arrow_delete.png;key_delete;;false;true]", "tooltip[key_add;Add key]", "tooltip[key_delete;Remove key]" } @@ -974,7 +974,7 @@ function mc_tutorial.show_event_popup_fs(player, is_edit, is_iso) return { "textarea[0.4,2.9;5.9,4.8;;Position info;", "\nEach position is an absolute world position with 3 coordinates (X, Y, Z). You can view your position by pressing F5.\n", - "NOTE: In order for a player to complete a tutorials with a position action, they must go to the exact world position specified. This means that players completing these tutorials must have access to the realms the tutorials were recorded in.", + "NOTE: In order for a player to complete a tutorials with a position action, they must go to the exact world position specified. This means that players completing these tutorials must have access to the classrooms the tutorials were recorded in.", "]", "label[6.4,2.9;X =]", "label[6.4,3.8;Y =]", @@ -1161,7 +1161,7 @@ function mc_tutorial.show_tutorials(player) fs = { "style_type[textarea;font=mono,bold;textcolor=black]", - "textarea[", text_spacer, ",1;", panel_width - 2*text_spacer, ",1.1;;;Available Tutorials]", + "textarea[", text_spacer, ",1;", panel_width - 2*text_spacer, ",1.1;;;Available tutorials]", "style[tutoriallist;font=mono]", "textlist[", spacer, ",1.4;", panel_width - 2*spacer, ",", has_recorder_privs and "7.5" or "8.4", ";tutoriallist;", table.concat(titles, ","), ";", context.tutorial_selected, ";false]", "style_type[textarea;font=mono,bold;textcolor=black;font_size=*1.5]", @@ -1247,7 +1247,7 @@ function mc_tutorial.show_delete_confirm_popup(player) local fs = { "formspec_version[6]", "size[8,3.2]", - "textarea[0.4,0.4;7.2,1.4;;;Are you sure you want to delete \"", selected.title or "Untitled tutorial", "\"?\nThis action is irreversible.]", + "textarea[0.4,0.4;7.2,1.4;;;Are you sure you want to delete \"", selected.title or "this tutorial", "\"?\nThis action is irreversible.]", "button[0.4,2;3.5,0.8;confirm;Delete]", "button[4.1,2;3.5,0.8;cancel;Cancel]", } diff --git a/mods/mc_tutorial/tutorial_settings.conf b/mods/mc_tutorial/tutorial_settings.conf index c489bbbd..bdc4fe9f 100644 --- a/mods/mc_tutorial/tutorial_settings.conf +++ b/mods/mc_tutorial/tutorial_settings.conf @@ -1,5 +1,5 @@ -# (comma-separated list) Default starting value is "student", which corresponds to {student = true} -mc_tutorial.player_priv_table = student +# (comma-separated list) Default starting value is "interact", which corresponds to {interact = true} +mc_tutorial.player_priv_table = interact # (comma-separated list) Default starting value is "teacher, interact", which corresponds to {teacher = true, interact = true} mc_tutorial.recorder_priv_table = teacher, interact diff --git a/mods/mc_worldmanager/commands.lua b/mods/mc_worldmanager/commands.lua index 6a287c17..6d2e8259 100644 --- a/mods/mc_worldmanager/commands.lua +++ b/mods/mc_worldmanager/commands.lua @@ -43,7 +43,7 @@ commands["new"] = { local newRealm = Realm:New(realmName, { x = sizeX, y = sizeY, z = sizeZ }) newRealm:CreateGround() newRealm:CreateBarriersFast() - newRealm:set_data("owner", name) + newRealm:AddOwner(name) return true, "created new realm with ID: " .. newRealm.ID end, diff --git a/mods/mc_worldmanager/hooks.lua b/mods/mc_worldmanager/hooks.lua index 89470948..2a375f85 100644 --- a/mods/mc_worldmanager/hooks.lua +++ b/mods/mc_worldmanager/hooks.lua @@ -54,7 +54,7 @@ minetest.register_on_joinplayer(function(player, last_login) local realm = Realm.GetRealmFromPlayer(player) -- don't allow players to enter realms they no longer have access to when joining - if (not realm or not realm:getCategory().joinable(realm, player) or realm:isDeleted() or (realm:isHidden() and not mc_core.checkPrivs(player, {teacher = true}))) then + if not realm:Joinable(player) then realm = mc_worldManager.GetSpawnRealm() mc_core.temp_unfreeze_and_run(player, realm.TeleportPlayer, realm, player) pmeta:set_int("realm", realm.ID) @@ -66,6 +66,7 @@ minetest.register_on_joinplayer(function(player, last_login) realm:RegisterPlayer(player) realm:RunTeleportInFunctions(player) realm:ApplyPrivileges(player) + realm:ApplySkybox(player) mc_core.update_marker_visibility(player:get_player_name(), realm.ID) end end) @@ -190,13 +191,19 @@ minetest.register_on_shutdown(function() end) minetest.register_on_mods_loaded(function() - -- Ensure that there is only one spawn realm for id,realm in pairs(Realm.realmDict) do + -- Ensure that there is only one spawn realm local cat = realm:getCategory().key if cat == "spawn" and tonumber(id) ~= tonumber(mc_worldManager.spawnRealmID) then realm:setCategoryKey("default") elseif cat ~= "spawn" and tonumber(id) == tonumber(mc_worldManager.spawnRealmID) then realm:setCategoryKey("spawn") end + + -- Apply old default skybox to realms with undefined skyboxes + local sky = realm:GetSkybox() + if not sky then + realm:UpdateSkybox(skybox.get_default_sky()) + end end end) diff --git a/mods/mc_worldmanager/init.lua b/mods/mc_worldmanager/init.lua index 7e325900..4c1b268c 100644 --- a/mods/mc_worldmanager/init.lua +++ b/mods/mc_worldmanager/init.lua @@ -104,16 +104,15 @@ function mc_worldManager.GetCreateInstancedRealm(realmName, player, schematic, t if (realm == nil) then if (schematic == nil or schematic == "" or schematic == "nil") then - realm = Realm:New("["..player:get_player_name().."] "..realmName, { x = realmSize.x or 80, y = realmSize.y or 80, z = realmSize.z or 80 }) + realm = Realm:New(realmName, {x = realmSize.x or 80, y = realmSize.y or 80, z = realmSize.z or 80}) realm:CreateGround() realm:CreateBarriers() else - realm = Realm:NewFromSchematic("["..player:get_player_name().."] "..realmName, schematic) + realm = Realm:NewFromSchematic(realmName, schematic) end realmInstanceTable[realmKey] = realm.ID realm:setCategoryKey("instanced") - realm:AddOwner(player:get_player_name()) if (temporary == nil) then @@ -130,9 +129,9 @@ function mc_worldManager.GetCreateInstancedRealm(realmName, player, schematic, t end function mc_worldManager.InstancedDelete(realm, player) - local owners = realm:get_data("owner") + local owners = realm:GetOwners() - if (owners[player:get_player_name()] ~= nil) then + if (owners ~= nil and owners[player:get_player_name()] ~= nil) then realm:Delete() end end diff --git a/mods/mc_worldmanager/mod.conf b/mods/mc_worldmanager/mod.conf index 5bd149ac..dba0c995 100644 --- a/mods/mc_worldmanager/mod.conf +++ b/mods/mc_worldmanager/mod.conf @@ -1,5 +1,5 @@ name = mc_worldmanager description = Provides functions for managing the world. -depends = mc_core, unbreakable_map_barrier, exschem, worldedit, biomegen, default, flowers +depends = mc_core, unbreakable_map_barrier, exschem, worldedit, biomegen, default, flowers, skybox optional_depends = areas title = MC Realms diff --git a/mods/mc_worldmanager/realm/realm.lua b/mods/mc_worldmanager/realm/realm.lua index 4b59be32..eb63f7f7 100644 --- a/mods/mc_worldmanager/realm/realm.lua +++ b/mods/mc_worldmanager/realm/realm.lua @@ -589,5 +589,22 @@ function Realm:isDeleted() return self:get_tmpData("deleted") == true end +---@public +---Updates and saves the skybox of a realm. +---@param skybox string Name of skybox +---@return boolean Whether the operation succeeded. +function Realm:UpdateSkybox(sky) + self:set_data("skybox", sky) + Realm.SaveDataToStorage() + return true +end + +---@public +---Gets the skybox of a realm. +---@return string +function Realm:GetSkybox() + return self:get_data("skybox") +end + Realm.LoadDataFromStorage() -Realm.consolidateEmptySpace() \ No newline at end of file +Realm.consolidateEmptySpace() diff --git a/mods/mc_worldmanager/realm/realmCategory.lua b/mods/mc_worldmanager/realm/realmCategory.lua index 81e40e8b..65249b64 100644 --- a/mods/mc_worldmanager/realm/realmCategory.lua +++ b/mods/mc_worldmanager/realm/realmCategory.lua @@ -43,12 +43,25 @@ function Realm.getRegisteredCategories() return Realm.categories end +---@public +---GetOwners +---Gets owners of a realm. +---@return table +function Realm:GetOwners() + local owners = self:get_data("owner") + if type(owners) == "string" then + owners = {[owners] = true} + self:set_data("owner", owners) + end + return owners +end + ---@public ---AddOwner ---Adds a new owner to a realm. ---@param owner string @The owner to add. function Realm:AddOwner(ownerName) - local owners = self:get_data("owner") + local owners = self:GetOwners() if (owners == nil) then owners = {} end @@ -66,7 +79,7 @@ end ---Removes an owner from a realm. ---@param owner string @The owner to remove. function Realm:RemoveOwner(ownerName) - local owners = self:get_data("owner") + local owners = self:GetOwners() if (owners == nil) then owners = {} end @@ -107,13 +120,13 @@ Realm.RegisterCategory({ realm:set_data("students", {}) end - if (realm:get_data("owner") == nil) then + if (realm:GetOwners() == nil) then realm:set_data("owner", {}) end if (realm:get_data("students")[player:get_player_name()] ~= nil) then return true, "You are a student in this realm." - elseif (realm:get_data("owner")[player:get_player_name()] ~= nil) then + elseif (realm:GetOwners()[player:get_player_name()] ~= nil) then return true, "You are an owner of this realm." else return false, "You are not a student in this realm." @@ -125,13 +138,13 @@ Realm.RegisterCategory({ realm:set_data("students", {}) end - if (realm:get_data("owner") == nil) then + if (realm:GetOwners() == nil) then realm:set_data("owner", {}) end if (realm:get_data("students")[player:get_player_name()] ~= nil) then return true, "You are a student in this realm." - elseif (realm:get_data("owner")[player:get_player_name()] ~= nil) then + elseif (realm:GetOwners()[player:get_player_name()] ~= nil) then return true, "You are an owner of this realm." elseif (minetest.check_player_privs(player, { teacher = true })) then return true, "All realms are joinable by teachers." @@ -145,11 +158,11 @@ Realm.RegisterCategory({ key = "instanced", visible = function(realm, player) - if (realm:get_data("owner") == nil) then + if (realm:GetOwners() == nil) then realm:set_data("owner", {}) end - if (realm:get_data("owner")[player:get_player_name()] ~= nil) then + if (realm:GetOwners()[player:get_player_name()] ~= nil) then return true, "You are an owner of this realm." end @@ -157,11 +170,11 @@ Realm.RegisterCategory({ end, joinable = function(realm, player) - if (realm:get_data("owner") == nil) then + if (realm:GetOwners() == nil) then realm:set_data("owner", {}) end - if (realm:get_data("owner")[player:get_player_name()] ~= nil) then + if (realm:GetOwners()[player:get_player_name()] ~= nil) then return true, "You are an owner of this realm." elseif (minetest.check_player_privs(player, { teacher = true })) then return true, "All realms are joinable by teachers." diff --git a/mods/mc_worldmanager/realm/realmPlayerManagement.lua b/mods/mc_worldmanager/realm/realmPlayerManagement.lua index bea4df3d..83841412 100644 --- a/mods/mc_worldmanager/realm/realmPlayerManagement.lua +++ b/mods/mc_worldmanager/realm/realmPlayerManagement.lua @@ -31,6 +31,7 @@ function Realm:TeleportPlayer(player) -- We register the player with the new realm, and apply their realm-specific privileges. self:RegisterPlayer(player) self:ApplyPrivileges(player) + self:ApplySkybox(player) -- We run the teleport functions of the new realm. These are added by non-core features, other mods, and realms. self:RunTeleportInFunctions(player) @@ -150,4 +151,19 @@ function Realm:GetPlayerCount() else return 0 end -end \ No newline at end of file +end + +---@public +---Applies this realm's skybox to a player +---@param player +function Realm:ApplySkybox(player) + local sky = self:get_data("skybox") + skybox.set(player, skybox.get_sky_number(sky or skybox.get_default_sky())) +end + +---@public +---Checks if a player can join this realm. +---@return boolean whether player can join this realm. +function Realm:Joinable(player) + return player and player:is_player() and not self:isDeleted() and self:getCategory().joinable(self, player) and (not self:isHidden() or mc_core.checkPrivs(player, {teacher = true})) +end diff --git a/mods/mc_worldmanager/realm_extensions/realmExtensions.lua b/mods/mc_worldmanager/realm_extensions/realmExtensions.lua index 96d3684f..0cea04e7 100644 --- a/mods/mc_worldmanager/realm_extensions/realmExtensions.lua +++ b/mods/mc_worldmanager/realm_extensions/realmExtensions.lua @@ -2,7 +2,6 @@ realmExtensions = {} dofile(minetest.get_modpath("mc_worldmanager") .. "/realm_extensions/realmMusic.lua") - Realm.RegisterOnJoinCallback(function(realm, player) minetest.sound_play("teleport", {to_player = player:get_player_name(), gain = 1.0, pitch = 1.0,}, true) end) \ No newline at end of file diff --git a/mods/mc_worldmanager/realm_extensions/realmMusic.lua b/mods/mc_worldmanager/realm_extensions/realmMusic.lua index 003a8531..e3d29a86 100644 --- a/mods/mc_worldmanager/realm_extensions/realmMusic.lua +++ b/mods/mc_worldmanager/realm_extensions/realmMusic.lua @@ -1,22 +1,47 @@ realmExtensions.playerSounds = {} Realm.RegisterOnJoinCallback(function(realm, player) - local backgroundSound = realm:get_data("background_sound") - local backgroundVolume = tonumber(realm:get_data("background_volume")) or 1.0 - if (backgroundSound ~= nil) then - local reference = minetest.sound_play(backgroundSound, { - to_player = player:get_player_name(), - gain = backgroundVolume, - object = player, - loop = true }) + realm:ApplyMusic(player) +end) - realmExtensions.playerSounds[player:get_player_name()] = reference +-- TODO: programmatically make names look nicer in GUI without changing names internally +function Realm.GetRegisteredMusic() + local sounds = {} + for _,file in pairs(minetest.get_dir_list(mc_worldManager.path.."/sounds/music", false)) do + if string.sub(file, -4) == ".ogg" then + table.insert(sounds, string.sub(file, 1, -5)) + end end -end) + return sounds +end + +function Realm:UpdateMusic(sound, volume) + self:set_data("background_sound", sound or nil) + self:set_data("background_volume", volume and volume/100 or nil) + Realm.SaveDataToStorage() +end -Realm.RegisterOnLeaveCallback(function(realm, player) +function Realm:GetMusic() + return self:get_data("background_sound") or "none" +end + +function Realm:ApplyMusic(player) + -- clear previous sound if (realmExtensions.playerSounds[player:get_player_name()] ~= nil) then minetest.sound_stop(realmExtensions.playerSounds[player:get_player_name()]) realmExtensions.playerSounds[player:get_player_name()] = nil end -end) \ No newline at end of file + + -- apply new sound + local backgroundSound = self:GetMusic() + local backgroundVolume = tonumber(self:get_data("background_volume")) or 1.0 + if (backgroundSound ~= nil) then + local reference = minetest.sound_play(backgroundSound, { + to_player = player:get_player_name(), + gain = backgroundVolume, + object = player, + loop = true + }) + realmExtensions.playerSounds[player:get_player_name()] = reference + end +end diff --git a/mods/mc_worldmanager/sounds/arctic-wind.ogg b/mods/mc_worldmanager/sounds/music/arctic-wind.ogg similarity index 100% rename from mods/mc_worldmanager/sounds/arctic-wind.ogg rename to mods/mc_worldmanager/sounds/music/arctic-wind.ogg diff --git a/mods/mc_worldmanager/sounds/forest-birds.ogg b/mods/mc_worldmanager/sounds/music/forest-birds.ogg similarity index 100% rename from mods/mc_worldmanager/sounds/forest-birds.ogg rename to mods/mc_worldmanager/sounds/music/forest-birds.ogg diff --git a/mods/mc_worldmanager/sounds/grass-wind.ogg b/mods/mc_worldmanager/sounds/music/grass-wind.ogg similarity index 100% rename from mods/mc_worldmanager/sounds/grass-wind.ogg rename to mods/mc_worldmanager/sounds/music/grass-wind.ogg diff --git a/mods/mc_worldmanager/sounds/jungle.ogg b/mods/mc_worldmanager/sounds/music/jungle.ogg similarity index 100% rename from mods/mc_worldmanager/sounds/jungle.ogg rename to mods/mc_worldmanager/sounds/music/jungle.ogg diff --git a/mods/mc_worldmanager/sounds/ocean-seagulls.ogg b/mods/mc_worldmanager/sounds/music/ocean-seagulls.ogg similarity index 100% rename from mods/mc_worldmanager/sounds/ocean-seagulls.ogg rename to mods/mc_worldmanager/sounds/music/ocean-seagulls.ogg diff --git a/mods/mc_worldmanager/sounds/tide-pools.ogg b/mods/mc_worldmanager/sounds/music/tide-pools.ogg similarity index 100% rename from mods/mc_worldmanager/sounds/tide-pools.ogg rename to mods/mc_worldmanager/sounds/music/tide-pools.ogg diff --git a/mods/skybox/init.lua b/mods/skybox/init.lua index d3d47461..c8fb086b 100644 --- a/mods/skybox/init.lua +++ b/mods/skybox/init.lua @@ -15,9 +15,23 @@ of the license, or (at your option) any later version. -- local skies = { + {"DarkStormy", "#1f2226", 0.5, { density = 0.5, color = "#aaaaaae0", ambient = "#000000", + height = 64, thickness = 32, speed = {x = 6, y = -6},}}, + {"CloudyLightRays", "#5f5f5e", 0.9, { density = 0.4, color = "#efe3d5d0", ambient = "#000000", + height = 96, thickness = 24, speed = {x = 4, y = 0},}}, + {"FullMoon", "#24292c", 0.2, { density = 0.25, color = "#ffffff80", ambient = "#404040", + height = 140, thickness = 8, speed = {x = -2, y = 2},}}, + {"SunSet", "#72624d", 0.4, { density = 0.2, color = "#f8d8e8e0", ambient = "#000000", + height = 120, thickness = 16, speed = {x = 0, y = -2},}}, + {"ThickCloudsWater", "#a57850", 0.8, { density = 0.35, color = "#ebe4ddfb", ambient = "#000000", + height = 80, thickness = 32, speed = {x = 4, y = 3},}}, {"TropicalSunnyDay", "#f1f4ee", 1.0, { density = 0.25, color = "#fffffffb", ambient = "#000000", height = 120, thickness = 8, speed = {x = -2, y = 0},}}, } +local skymap = {["None"] = 0} +for i, def in pairs(skies) do + skymap[def[1]] = i +end -- -- API @@ -53,7 +67,7 @@ skybox.set = function(player, number) player:set_sky(sky[2], "skybox", textures, true) end player:set_clouds(sky[4]) - player:set_attribute("skybox:skybox", sky[1]) + player:get_meta():set_string("skybox:skybox", sky[1]) end end @@ -72,20 +86,26 @@ skybox.clear = function(player) thickness = 16, speed = {x = 0, y = -2}, }) - player:set_sun({visible = true, sunrise_visible = true}) - player:set_moon({visible = true}) + player:set_sun({visible = true, sunrise_visible = true, texture = ""}) + player:set_moon({visible = true, texture = ""}) player:set_stars({visible = true}) - player:set_attribute("skybox:skybox", "off") + player:get_meta():set_string("skybox:skybox", "off") end --- --- registrations and load/save code --- +skybox.add = function(def) + table.insert(skies, def) + skymap[def[1]] = #skies +end -minetest.register_on_joinplayer(function(player) - local sky = player:get_attribute("skybox:skybox") - -- Default to TropicalSunnyDay onjoin - skybox.clear(player) - skybox.set(player, 1) -end) +skybox.get_skies = function() + return table.copy(skies) +end + +skybox.get_sky_number = function(name) + return skymap[name] +end + +skybox.get_default_sky = function() + return skies[6][1] +end