Skip to content

Commit

Permalink
Add settings to configure features on level editor
Browse files Browse the repository at this point in the history
There are features that level admins may want to enable or disable on their
level, so here we're giving them the chance to do just that with:

* the global chat
* the voice amplifier
* kicks
  • Loading branch information
alimtunc authored and ramnes committed Sep 29, 2022
1 parent 22b7bbf commit 2e4c9d4
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 16 deletions.
26 changes: 26 additions & 0 deletions core/client/ui/toolboxes/level-toolbox.hbs.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,32 @@
<input id="level-toolbox-hidden" type="checkbox" class="js-hidden" checked={{hidden}}>
<label for="level-toolbox-hidden">Hide from the list of levels</label>
</div>
<div>
<div class="feature-dropdown">
<label for="voice-amplifier">Shout :</label>
<select name="voice-amplifier" id="voice-amplifier" class="js-voice-amplifier-select select" value="{{ shout }}">
{{#each dropdownValues}}
<option value="{{value}}" selected="{{#if eq shout value}}selected{{/if}}">{{label}}</option>
{{/each}}
</select>
</div>
<div class="feature-dropdown">
<label for="global-chat">Global chat :</label>
<select name="global-chat" id="global-chat" class="js-global-chat-select select">
{{#each dropdownValues}}
<option value="{{value}}" selected="{{#if eq globalChat value}}selected{{/if}}">{{label}}</option>
{{/each}}
</select>
</div>
<div class="feature-dropdown">
<label for="punch">Punch :</label>
<select name="punch" id="punch" class="js-punch-select select" value="adminOnly">
{{#each dropdownValues}}
<option value="{{value}}" selected="{{#if eq punch value}}selected{{/if}}">{{label}}</option>
{{/each}}
</select>
</div>
</div>
<div>
<button id="level-toolbox-position" type="button" class="js-spawn-position button">Set spawn position (current {{ spawnPosition }})</button>
</div>
Expand Down
36 changes: 30 additions & 6 deletions core/client/ui/toolboxes/level-toolbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,31 @@ const checkLevelName = value => {
if (value.length < 3) throw new Error('Level\'s name must be at least 2 characters');
};

const updateLevel = (name, spawnPosition, hide = false) => {
const updateLevel = (name, spawnPosition, hide = false, featuresPermissions) => {
try {
checkLevelName(name);
} catch (e) {
lp.notif.error(e.name);
return;
}

Meteor.call('updateLevel', name, spawnPosition, hide, err => {
Meteor.call('updateLevel', name, spawnPosition, hide, featuresPermissions, err => {
if (err) { lp.notif.error(err.reason); return; }
lp.notif.success('Level updated!');
});
};

const getFeaturesPermissions = () => currentLevel(Meteor.user()).featuresPermissions || {};

Template.levelToolbox.events({
'focus input'() { toggleUIInputs(true); },
'blur input'() { toggleUIInputs(false); },
'blur .js-name'(event) {
const user = Meteor.user();
const level = currentLevel(user);
const level = currentLevel(Meteor.user());
updateLevel(event.target.value, level.spawn, level.hide);
},
'change .js-hidden'(event) {
const user = Meteor.user();
const level = currentLevel(user);
const level = currentLevel(Meteor.user());
updateLevel(level.name, level.spawn, event.target.checked);
},
'click .js-spawn-position'() {
Expand All @@ -39,6 +39,20 @@ Template.levelToolbox.events({
const { x, y } = user.profile;
updateLevel(level.name, { x, y }, level.hide);
},
'change .js-voice-amplifier-select'(event) {
const level = currentLevel(Meteor.user());

updateLevel(level.name, level.spawn, level.hide, { shout: event.target.value });
},
'change .js-global-chat-select'(event) {
const level = currentLevel(Meteor.user());
updateLevel(level.name, level.spawn, level.hide, { globalChat: event.target.value });
},
'change .js-punch-select'(event) {
const level = currentLevel(Meteor.user());

updateLevel(level.name, level.spawn, level.hide, { punch: event.target.value });
},
});

Template.levelToolbox.helpers({
Expand All @@ -48,4 +62,14 @@ Template.levelToolbox.helpers({
const { spawn } = currentLevel(Meteor.user());
return `${Math.round(spawn.x)} - ${Math.round(spawn.y)}`;
},
dropdownValues() {
return [
{ value: 'enabled', label: 'Enabled' },
{ value: 'adminOnly', label: 'Admin only' },
{ value: 'disabled', label: 'Disabled' },
];
},
shout() { return getFeaturesPermissions().shout || 'enabled'; },
globalChat() { return getFeaturesPermissions().globalChat || 'enabled'; },
punch() { return getFeaturesPermissions().punch || 'enabled'; },
});
11 changes: 11 additions & 0 deletions core/client/ui/toolboxes/level-toolbox.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,15 @@
font-size: 0.85rem;
margin: 15px 0;
}

.feature-dropdown {
display: flex;
justify-content: space-between;
align-items: center;
}

.select {
width: fit-content;
min-width: 110px;
}
}
18 changes: 17 additions & 1 deletion core/lib/misc.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,14 +235,30 @@ const teleportUserInLevel = (user, level, source = 'teleporter') => {
return level.name;
};

const canUseLevelFeature = (user, featureName) => {
check(user._id, Match.Id);
check(featureName, String);

const level = currentLevel(user);
const featurePermission = level?.featuresPermissions?.[featureName];

if (featurePermission === 'disabled') {
if (user.roles?.admin) lp.notif.error(`This feature is disabled: ${featureName}`);
return false;
} else if (!user.roles?.admin && featurePermission === 'adminOnly') {
return false;
} else return true;
};

export {
canAccessZone,
canEditGuild,
canEditActiveLevel,
canEditGuild,
canEditLevel,
canEditUserPermissions,
canModerateLevel,
canModerateUser,
canUseLevelFeature,
completeUserProfile,
currentLevel,
fileOnBeforeUpload,
Expand Down
10 changes: 6 additions & 4 deletions core/modules/console/client/console.hbs.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
<template name="console">
<div class="console">
<form class="js-console-form">
<input type="file" tabindex="1" name="file" id="console-file" class="console-file" accept="image/png, image/jpeg, image/gif" />
<label for="console-file">📎</label>
<textarea class="js-command-input" name="js-command-input" placeholder="Talk here" autocomplete="off"></textarea>
<button type="button" class="js-button-submit">Send</button>
{{#if not chatDisabled}}
<input disabled="{{chatDisabled}}" type="file" tabindex="1" name="file" id="console-file" class="console-file" accept="image/png, image/jpeg, image/gif" />
<label for="console-file">📎</label>
{{/if}}
<textarea disabled="{{chatDisabled}}" class="js-command-input {{#if chatDisabled}}disabled{{/if}}" name="js-command-input" placeholder="{{#if chatDisabled}}Chat Disabled{{else}}Talk here{{/if}}" autocomplete="off"></textarea>
{{#if not chatDisabled}}<button type="button" class="js-button-submit">Send</button>{{/if}}
</form>
</div>
</template>
15 changes: 15 additions & 0 deletions core/modules/console/client/console.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { toggleUIInputs } from '../../../client/helpers';
import { canUseLevelFeature, currentLevel } from '../../../lib/misc';

const inputSelector = '.console .js-command-input';
const inputFileSelector = '.console .console-file';
Expand Down Expand Up @@ -140,3 +141,17 @@ Template.console.events({
onSubmit();
},
});

Template.console.helpers({
chatDisabled: () => {
const channel = Session.get('messagesChannel');
const user = Meteor.user({ fields: { _id: 1, 'profile.levelId': 1, roles: 1 } });

if (!user) return;

const level = currentLevel(user);

if (channel === level?._id) return !canUseLevelFeature(user, 'globalChat');
return false;
},
});
6 changes: 6 additions & 0 deletions core/modules/console/client/console.scss
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ $button-text-color: white;
height: 50px;
max-height: 200px;
min-height: 50px;

&.disabled {
background-color: $new-dark-secondary;
padding-left: 20px;
font-style: italic;
}
}
}

Expand Down
4 changes: 4 additions & 0 deletions core/modules/punch-ability/client/punch-ability.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import audioManager from '../../../client/audio-manager';
import { canUseLevelFeature } from '../../../lib/misc';

const playPunchAnimation = () => {
userManager.scene.cameras.main.shake(250, 0.015, 0.02);
Expand Down Expand Up @@ -26,6 +27,9 @@ window.addEventListener('load', () => {
});

hotkeys('x', { scope: scopes.player }, e => {
const user = Meteor.user({ fields: { _id: 1, 'profile.levelId': 1, roles: 1 } });
if (!user || !canUseLevelFeature(user, 'punch')) return;

e.preventDefault();
e.stopPropagation();
if (e.repeat) return;
Expand Down
29 changes: 26 additions & 3 deletions core/modules/shout-ability/client/shout-ability.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,45 @@
import { canUseLevelFeature } from '../../../lib/misc';


window.addEventListener('load', () => {
registerRadialMenuModules([
{ id: 'shout', icon: '📢', label: 'Shout', order: 40, shortcut: 55, scope: 'me' },
]);
Tracker.nonreactive(() => {
const user = Meteor.user();

if (!user) return;

const isAdmin = user.roles?.admin;
const isShoutFeatureEnabled = canUseLevelFeature(Meteor.user(), 'shout');

if (isAdmin || isShoutFeatureEnabled) {
registerRadialMenuModules([
{ id: 'shout', icon: '📢', label: 'Shout', order: 40, shortcut: 55, scope: 'me' },
]);
}
});

hotkeys('r', { keyup: true, scope: scopes.player }, event => {
if (event.repeat) return;

const user = Meteor.user({ fields: { _id: 1, 'profile.levelId': 1, roles: 1 } });

if (!user || !canUseLevelFeature(user, 'shout')) return;

userVoiceRecorderAbility.recordVoice(event.type === 'keydown', sendAudioChunksToUsersInZone);
});

const onMenuOptionSelected = e => {
const { option } = e.detail;
const user = Meteor.user({ fields: { _id: 1, 'profile.levelId': 1, roles: 1 } });

if (option.id !== 'shout') return;

userVoiceRecorderAbility.recordVoice(true, sendAudioChunksToUsersInZone);
};

const onMenuOptionUnselected = e => {
const { option } = e.detail;
const user = Meteor.user({ fields: { _id: 1, 'profile.levelId': 1, roles: 1 } });

if (option.id !== 'shout') return;

userVoiceRecorderAbility.recordVoice(false, sendAudioChunksToUsersInZone);
Expand Down
7 changes: 5 additions & 2 deletions core/server/levels.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ Meteor.publish('currentLevel', function () {

return Levels.find(
{ _id: levelId },
{ fields: { name: 1, spawn: 1, hide: 1, height: 1, width: 1, editorUserIds: 1, createdBy: 1, sandbox: 1, guildId: 1 } },
{ fields: { name: 1, spawn: 1, hide: 1, height: 1, width: 1, editorUserIds: 1, createdBy: 1, sandbox: 1, guildId: 1, featuresPermissions: 1 } },
);
});

Expand All @@ -211,11 +211,12 @@ Meteor.methods({

return createLevel({ templateId });
},
updateLevel(name, position, hide) {
updateLevel(name, position, hide, featurePermission = null) {
if (!this.userId) throw new Meteor.Error('missing-user', 'A valid user is required');
check(name, String);
check(position, { x: Number, y: Number });
check(hide, Boolean);
check(featurePermission, Match.OneOf(null, { shout: String }, { globalChat: String }, { punch: String }));

const user = Meteor.user();
const level = currentLevel(Meteor.user());
Expand All @@ -225,9 +226,11 @@ Meteor.methods({
const query = { $set: { name, spawn: { x: position.x, y: position.y } } };
if (hide) query.$set.hide = true;
else query.$unset = { hide: 1 };
if (featurePermission) query.$set.featuresPermissions = { ...level.featuresPermissions || {}, ...featurePermission };

Levels.update(level._id, query);
},

increaseLevelVisits(levelId) {
if (!this.userId) return;
check(levelId, Match.Id);
Expand Down

0 comments on commit 2e4c9d4

Please sign in to comment.