Skip to content

Plugin Examples

Real-world plugin examples to learn from.

FPS Counter

A simple in-game FPS display.

plugin.js:

javascript
module.exports = {
  name: 'FPS Counter',
  version: '1.0.0',
  inject: ['style.css', 'template.html', 'script.js'],
  settings: [
    { id: 'enabled', type: 'checkbox', label: 'Show FPS', default: true },
    { id: 'position', type: 'select', options: ['top-left', 'top-right', 'bottom-left', 'bottom-right'], default: 'top-left' },
    { id: 'color', type: 'color', label: 'Text Color', default: '#4ade80' }
  ]
};

template.html:

html
{{#if enabled}}
<div class="fps-counter {{position}}" id="fps" style="color: {{color}}">-- FPS</div>
{{/if}}

style.css:

css
.fps-counter { position: fixed; padding: 4px 8px; background: rgba(0,0,0,0.7); font: 14px monospace; z-index: 99999; }
.fps-counter.top-left { top: 10px; left: 10px; }
.fps-counter.top-right { top: 10px; right: 10px; }
.fps-counter.bottom-left { bottom: 10px; left: 10px; }
.fps-counter.bottom-right { bottom: 10px; right: 10px; }

script.js:

javascript
const { enabled } = GEMSHELL_SETTINGS;
if (!enabled) return;

const el = document.getElementById('fps');
let frames = 0, last = performance.now();

(function loop() {
  frames++;
  const now = performance.now();
  if (now - last >= 1000) {
    el.textContent = frames + ' FPS';
    frames = 0;
    last = now;
  }
  requestAnimationFrame(loop);
})();

Discord Webhook

Send build notifications to Discord.

plugin.js:

javascript
module.exports = {
  name: 'Discord Notifier',
  version: '1.0.0',
  settings: [
    { id: 'webhookUrl', type: 'text', label: 'Webhook URL', placeholder: 'https://discord.com/api/webhooks/...' },
    { id: 'onSuccess', type: 'checkbox', label: 'Notify on success', default: true },
    { id: 'onError', type: 'checkbox', label: 'Notify on error', default: true }
  ],
  
  async onPostBuild(context, settings) {
    if (!settings.onSuccess || !settings.webhookUrl) return;
    
    await gemshell.http.post(settings.webhookUrl, {
      embeds: [{
        title: 'Build Complete',
        description: `**${context.config.title}** v${context.config.version}`,
        color: 0x4ade80,
        fields: [
          { name: 'Platform', value: `${context.platform}-${context.arch}`, inline: true }
        ],
        timestamp: new Date().toISOString()
      }]
    });
  },
  
  async onBuildError(context, settings) {
    if (!settings.onError || !settings.webhookUrl) return;
    
    await gemshell.http.post(settings.webhookUrl, {
      embeds: [{
        title: 'Build Failed',
        description: context.error,
        color: 0xef4444,
        timestamp: new Date().toISOString()
      }]
    });
  }
};

Watermark

Add a watermark to demo builds.

plugin.js:

javascript
module.exports = {
  name: 'Watermark',
  version: '1.0.0',
  inject: ['style.css', 'template.html'],
  settings: [
    { id: 'enabled', type: 'checkbox', label: 'Show Watermark', default: true },
    { id: 'text', type: 'text', label: 'Text', default: 'DEMO' },
    { id: 'opacity', type: 'slider', label: 'Opacity', min: 10, max: 100, default: 50 }
  ]
};

template.html:

html
{{#if enabled}}
<div class="watermark" style="opacity: {{opacity}}%">{{text}}</div>
{{/if}}

style.css:

css
.watermark {
  position: fixed;
  bottom: 20px;
  right: 20px;
  font: bold 24px sans-serif;
  color: white;
  text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
  pointer-events: none;
  z-index: 99999;
}

Build Counter

Track build numbers.

plugin.js:

javascript
module.exports = {
  name: 'Build Counter',
  version: '1.0.0',
  
  async onPreBuild(context, settings) {
    const count = gemshell.storage.get('buildCount', 0) + 1;
    gemshell.storage.set('buildCount', count);
    gemshell.log(`Build #${count}`);
  },
  
  async onModifyAssets(context, settings) {
    const count = gemshell.storage.get('buildCount', 1);
    
    // Add build number to game
    gemshell.transform.injectIntoHtml(
      `<script>window.BUILD_NUMBER = ${count};</script>`,
      'head'
    );
  }
};

JSON Minifier

Reduce JSON file sizes.

plugin.js:

javascript
module.exports = {
  name: 'JSON Minifier',
  version: '1.0.0',
  
  async onModifyAssets(context, settings) {
    const files = gemshell.glob('**/*.json');
    let saved = 0;
    
    for (const file of files) {
      try {
        const original = gemshell.fs.read(file);
        const data = JSON.parse(original);
        const minified = JSON.stringify(data);
        
        if (minified.length < original.length) {
          gemshell.fs.write(file, minified);
          saved += original.length - minified.length;
        }
      } catch (e) {
        gemshell.warn(`Could not minify ${file}`);
      }
    }
    
    gemshell.log(`Saved ${(saved / 1024).toFixed(1)} KB`);
  }
};

Save Encryption

Encrypt save files.

plugin.js:

javascript
module.exports = {
  name: 'Save Encryptor',
  version: '1.0.0',
  settings: [
    { id: 'key', type: 'text', label: 'Encryption Key', placeholder: 'Enter a secret key' },
    { id: 'pattern', type: 'text', label: 'File Pattern', default: 'saves/*.json' }
  ],
  
  async onModifyAssets(context, settings) {
    if (!settings.key) {
      gemshell.warn('No encryption key set');
      return;
    }
    
    const files = gemshell.glob(settings.pattern);
    
    for (const file of files) {
      const content = gemshell.fs.read(file);
      const encrypted = gemshell.native.encrypt(settings.key, content);
      
      if (encrypted) {
        gemshell.fs.write(file + '.enc', encrypted);
        gemshell.fs.remove(file);
        gemshell.log(`Encrypted: ${file}`);
      }
    }
  }
};

Version Banner

Show version info in-game.

plugin.js:

javascript
module.exports = {
  name: 'Version Banner',
  version: '1.0.0',
  inject: ['style.css', 'template.html'],
  settings: [
    { id: 'enabled', type: 'checkbox', label: 'Show Version', default: true }
  ],
  
  async onModifyAssets(context, settings) {
    if (!settings.enabled) return;
    
    // Store version for template
    gemshell.transform.injectIntoHtml(
      `<script>window.GAME_VERSION = "${context.config.version}";</script>`,
      'head'
    );
  }
};

template.html:

html
{{#if enabled}}
<div class="version-banner" id="version-banner"></div>
{{/if}}

style.css:

css
.version-banner {
  position: fixed;
  bottom: 5px;
  left: 5px;
  font: 10px monospace;
  color: rgba(255,255,255,0.5);
  pointer-events: none;
  z-index: 99999;
}

script.js:

javascript
const { enabled } = GEMSHELL_SETTINGS;
if (!enabled) return;

const el = document.getElementById('version-banner');
if (el && window.GAME_VERSION) {
  el.textContent = 'v' + window.GAME_VERSION;
}