Code Monkey home page Code Monkey logo

framework's Introduction

MafiaHub
Discord server license

A suite of tools and libraries to accelerate multi-player modification development.
Brought to you by @Segfault, @zaklaus, @DavoSK, and other contributors!

Introduction

This codebase provides a suite of tools and libraries to simplify the development of multi-player modifications and ensure consistency across all of them. The primary goal is to provide a common foundation and interface with shared functionality and data. It covers many fields we found necessary during the development of multi-player mods, such as:

  • Networking: The core of the framework provides all the necessary tools to synchronize data between players.
  • ECS: Backed by a robust ECS framework that simplifies entity management and world streaming, it is also easily extensible.
  • Scripting: The Node.js scripting layer provides an easy way to create and manage game modes used on game servers.
  • Logging: It is always vital to log actions and errors, so the framework provides a simple way.
  • GUI: It provides a simple way to create and manage GUI elements using the Ultralight library.
  • Sentry: The framework provides a simple way to report errors and exceptions to the Sentry service.
  • Firebase: It easily stores and retrieves data from the Firebase service. Including stats, player data, and more.
  • Externals: Contains wrappers for various libraries and services used within the framework.
  • Integrations: Provides a simple server and client moddable setup combining various framework components, allowing you to focus on game-specific features.
  • Utils: It provides useful functions and classes throughout the framework.

MafiaHub Services are NOT part of this project, but our framework provides a simple way to integrate them. Feel free to ask us for more information about this service so we can provide the resources and a license to use it.

Contributing

We're always looking for new contributors, so if you have any ideas or suggestions, please let us know, and we'll see how we can improve it. You can reach us at our Discord server MafiaHub or raise an issue on our repository.

If you're interested in development, please read our Contribution Guidelines.

Building

We use CMake to build our projects so that you can use any of the supported build systems. We currently support Windows, Linux, and MacOS operating systems. You can follow this guide to get started:

First make sure your Git client supports LFS objects, visit Git LFS page for more info.

# Clone the repo
git clone https://github.com/MafiaHub/Framework.git
cd Framework

# Pull LFS objects
git lfs pull

Note: If you have issues cloning the repository (Git LFS-related errors), first ensure you have Git LFS support enabled. If you do, and this looks to be a server issue, please get in touch with @ZaKlaus on our Discord server to investigate it.

Build on macOS/Linux

# Configure CMake project
cmake -B build

# Build framework
cmake --build build

# Run framework tests
cmake --build build --target RunFrameworkTests

Build on Windows

Visual Studio 2022 support

Please ensure you have the cmake tools installed in your copy of Visual Studio first. Open your newly cloned Framework repository folder in Visual Studio, and it will automatically set up everything for you!

Things to note:

  • code/projects folder is hidden by default as it's ignored by Git, in Solution Explorer, enable Show All Files option to see your project files.
  • Changes in your project's cmake will not be auto-detected; cmake will only reload config on the build. Otherwise, you can do it yourself in the Projects section in the main menu.

CLion support

The guide on how to set up the project files for CLion is available here.

Add a multi-player project to the framework

Multi-player modifications are cloned into the code/projects directory and are automatically picked up by the framework. We use this approach to efficiently manage the projects and their dependencies and perform mass changes and general maintenance during development.

# Create and navigate to the folder
mkdir -p code/projects
cd code/projects

# Clone an MP repo that uses the framework
git clone [email protected]:<your-awesome-username>/<your-amazing-project>.git

# e.g.
git clone https://github.com/MafiaHub/MafiaMP.git

Now, you can access your targets and build them within the framework.

To exclude a project from compilation, create an empty file called IGNORE in the project's root.

License

The code is licensed under the MafiaHub OSS license.

The 5th clause ensures the work can focus primarily on this repository, as we provide access to the framework and its services. This is important to ensure that the framework is not used for other purposes, such as creating other projects, that would diverge from the framework. This approach guarantees that the changes are directly made to the framework, having a healthy ecosystem in mind.

framework's People

Contributors

arsenic-atg avatar davosk avatar deewarz avatar inlife avatar leonmrbonnie avatar mufty avatar segfaultd avatar sevenisko avatar zpl-zak avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

framework's Issues

[RFC] Scripting API

Summary

The objective of this RFC (Request for Comments) is to jointly define the vision for Scripting API.

I have already thought about it and I would like to share you an example.

It's possible that this implementation is complicated to implement or requires a JS wrapper on top of the NAPI layer.
That's why I'm starting this thread to see together what can be done.

For now, I focus on the server side.

Goals

  • OOP first API
  • Obvioulsy, inheritance (eg. Player class extends of Human class extends of Entity)
  • Typed API to benefit from auto-completion and error detection
  • Never instantiate but always use a factory (do World.createVehicle(...) instead of new Vehicle(...))
  • All "scripting modules" can listen for event (and some of them can emit) (also to net)
  • You can attach data to entities (and you can synchronize them with the client)
  • All methods are, or start with, a verb. (getXX, setXX, spawn, ...) (don't player.position but player.getPosition)
  • We use properties for immutable values (eg. player.id)

Example

import {
    Resources, // Resources scripting module
    Players, // Players scripting module
    Commands, // Commands scripting module
    Vehicles, // Vehicles scripting module
    Assets, // Assets definitions (data)
    Enums, // Enums definitions
    Vector3, // Vector3 factory
    World, // World scripting module
    Chat, // Chat scripting module
} from '@mafiahub/mafiamp/server' // could be shorter?

/**
 * You can also import ES module directly
 **/
// import Resources from '@mafiahub/mafiamp/server/resources'
// import WeaponsAssets from '@mafiahub/mafiamp/server/assets/weapons'



/*******************************************
 *
 * Resources scripting module example
 *
 *******************************************/
Resources.on('loaded', ({ resource }) => { // Always an object as argument to keep it scalable and for the end user, choose what he wants and in the order he wants
    console.log(`${resource.name} is loaded`)
})

Resources.on('unloading', ({ resource }) => {
    console.log(`${resource.name} is unloading.`)
})


/**
 * This function is an helper reused in
 * player connected and player dead events
 **/
function initPlayer (player) {
    player.setPosition(Vector3(0, 0, 0))
    player.setDirection(Vector3(0, 0, 0))
    player.setModel(/* TBD */)

    const { modelName, maxAmmo } = Assets.Weapons.getById(1)
    player.giveWeapon(modelName, maxAmmo) // can be discussed due to the way MDE works

    player.spawn()
}


/*******************************************
 *
 * Players scripting module example
 *
 *******************************************/
Players.on('connected', ({ player }) => { // We receive the player instance directly
    const message = `${player.name} (${player.id}) is connected.`
    console.log(message)
    player.sendChatToAll(message, 0, 0, 0, 1)

    // Here you can get data from database

    player.setData('isAdmin', true) // you can attach data to entity

    initPlayer(player)
})

Players.on('disconnecting', ({ player }) => {
    console.log(`${player.name} (${player.id}) is disconnecting.`)

    // Here you can save data in database

    player.sendChatToAll(`${player.name} (${player.id}) is disconnected.`, 0, 0, 0, 1) // for other players we tell them the player is already disconnected.
})

Players.on('spawned', ({ player }) => {
    console.log(`${player.name} (${player.id}) is spawned.`)

    player.emit('myCustomEvent', { foo: 'bar' }) // player emit a custom event with custom properties (see the handler below)
    player.emitNet('myEventFromServerToClient', { foo: 'bar' }) // this event will be emitted to the client

Players.on('myCustomEvent', ({ player, foo }) => { // merge base properties (player) with the emitted (foo)
    console.log(`${player.name} (${player.id}) send a custom event with ${foo} `)
})

Players.once('myCustomEvent', ({ player, foo }) => { // this uses "once", it means that the callback will be executed once
    console.log('This will be executed once.', player.name, foo)
})

Players.on('died', ({ player, killer }) => { // killer (if any) is an entity instance
    const killerType = killer?.type // returns the type of the entity or null

    switch (killerType) { // not exhaustive
        case Enums.ENTITY_TYPES.PLAYER:
            console.log(`${player.name} (${player.id}) was killed by ${killer.name}.`)

        case Enums.ENTITY_TYPES.CAR:
            const killerPlayer = killer.getInSeat(0)
            console.log(`${player.name} (${player.id}) was killed by a car driven by ${killerPlayer.name}.`)

        default:
            console.log(`${player.name} (${player.id}) is dead.`)
    }

    player.resetInventory()
    initPlayer(player)
})


/*******************************************
 *
 * Commands scripting module example
 *
 *******************************************/
Commands.on('mycommand', async ({
    player, // always pass the player instance
    command // always pass the command instance
}) => {
    console.log(command.name) // print "mycommand"
    console.log(command.args) // args is always defined as an Array[] even if there is no arguments, so you can use destructuring safely. (eg. `const [id, test, bar, foo] = command.args`)
    // command.listener() (it's this callback)

}, "I'm the help message.") // as 3rd parameters, you can provide an help message


Commands.on('car', async ({ player, command }) => {
    const id = parseInt(command.args[0], 10)

    if (!Number.isInteger(id)) {
        return player.sendChat(`You must provide a vehicle id.`, 255, 0, 0, 1)
    }

    const vehicleData = Assets.Vehicles.getById(id)

    if (!vehicleData) {
        return player.sendChat(`Unable to find vehicle with id: ${id}.`, 255, 0, 0, 1)
    }

    const { modelName } = vehicleData
    const vehicle = await World.createVehicle(modelName, /* position */ Vector3(0, 0, 0), /* direction */ Vector3(0, 0, 0), /* isVisible */ false) // This should be a promise?

    vehicle.setPrimaryColor(255, 255, 255, 1) // RGBA
    vehicle.setVisible(true)

    player.putInVehicle(vehicle)
}, "I'm the help message.") // as 3rd parameters, you can provide an help message

Commands.on('weapon', ({ player, command }) => {
    const id = parseInt(command.args[0], 10)

    if (!Number.isInteger(id)) {
        return player.sendChat(`You must provide a weapon id.`, 255, 0, 0, 1)
    }

    const weaponData = Assets.Weapons.getById(id)

    if (!weaponData) {
        return player.sendChat(`Unable to find weapon with id: ${id}.`, 255, 0, 0, 1)
    }

    const { modelName, maxAmmo } = weaponData
    player.giveWeapon(modelName, maxAmmo)
})

Commands.on('alert', ({ player, command }) => {
    if (!player.getData('isAdmin')) {
        return player.sendChat(`You are not allowed to send a server alert.`, 255, 0, 0, 1)
    }

    const [message] = command.args

    if (!message) {
        return player.sendChat(`You must provide a message.`, 255, 0, 0, 1)
    }

    Chat.sendToAll(`[SERVER] ${message}`, 0, 0, 0, 1) // message as the server
})

Commands.on('delallveh', ({ player }) => {
    if (!player.getData('isAdmin')) {
        return player.sendChat(`You are not allowed to use this command.`, 255, 0, 0, 1)
    }

    const vehicleList = Vehicles.getList()
    vehicleList.forEach((veh) => veh.delete())

    Chat.sendToAll(`[SERVER] All vehicles are deleted.`, 0, 0, 0, 1)
})

Commands.on('players', ({ player }) => {
    const players = Players.getList()

    const message = players.map(player => {
        return `- ${player.name}(${player.id})\n`
    })

    player.sendChat(message, 0, 0, 0, 1)
})

Commands.on('goto', ({ player, command }) => {
    const targetId = parseInt(command.args[0], 10)

    if (!Number.isInteger(id)) {
        return player.sendChat(`You must provide a player id.`, 255, 0, 0, 1)
    }

    const targetPlayer = Players.getById(targetId) // returns the instance of this player

    if (!targetPlayer) {
        return player.sendChat(`Unable to find player with id: ${targetId}.`, 255, 0, 0, 1)
    }

    player.setPosition(targetPlayer.getPosition()) // targetPlayer.getOffsetPosition()
})

Commands.on('repair', ({ player }) => {
    const vehicle = player.getCurrentVehicle()

    if (!vehicle) {
        return player.sendChat(`You must be in a vehicle.`, 255, 0, 0, 1)
    }

    vehicle.repair()
})

Commands.on('color', ({ player }) => {
    const vehicle = player.getCurrentVehicle()

    if (!vehicle) {
        return player.sendChat(`You must be in a vehicle.`, 255, 0, 0, 1)
    }

    vehicle.setColor(0, 0, 0, 1, 0, 0, 0, 1) // primary (setPrimaryColor) + secondary (setSecondaryColor)
})

Commands.on('help', ({ player }) => {
    const commandList = Commands.getList()

    if (commandList.length === 0) {
        return player.sendChat(`There is no command.`, 255, 0, 0, 1)
    }

    const message = []

    commandList.forEach((cmd) => {
        message.push(`- /${cmd.name} ${cmd.helpMessage && ('-- ' + cmd.helpMessage)}`)
    })

    player.sendChat(message.join('\n'), 255, 0, 0, 1)
})


/*******************************************
 *
 * Chat scripting module example
 *
 *******************************************/
Chat.on('message', ({ player, message }) => { // catch all chat messages
    if (player) {
        return console.log(`New message from player ${player.name} (${player.id}):`, message)
    }

    console.log(`New message from server:`, message)
})

Write tests for Framework modules

The following modules need to have tests written:

  • Interpolator
  • World ECS system
  • Scripting engine
  • Networking engine (basic connection init/shutdown + packet flow)
  • HTTP server (validate endpoints)
  • scripting bindings

Design a process for writing the bindings documentation

Discuss and design a process that would optimize the effort to write documentation for the native bindings.

Things to check:

  • Docs generation
  • Hosting
  • Tools that would accelerate the docs deployment and minimise maintenance costs.

Finish file watcher implementation

Finish the file watcher implementation for automated resource reload.

First implementation is done, however it's not working on macos backends and has to be finished on windows / linux.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.