Skip to content
This repository was archived by the owner on Jun 17, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
275 changes: 20 additions & 255 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,270 +1,35 @@
# PrimaUO Server
# 🚨 DEPRECATED – Prima

![Docker Image Version (tag)](https://img.shields.io/docker/v/tgiachi/prima-server/latest)
[![GitHub Actions](https://github.com/tgiachi/prima/actions/workflows/docker_image.yml/badge.svg)](https://github.com/tgiachi/prima/actions/workflows/docker_image.yml)
[![License](https://img.shields.io/badge/license-MIT-red.svg)](LICENSE)
**This repository is no longer maintained.**
The Prima project has been deprecated and completely rewritten as part of the [Moongate](https://github.com/Moongate-server/Moongate) initiative.

![](./Assets/prima_logo.png)
---

PrimaUO is a modern Ultima Online server implementation written in C# (.NET 9.0). The project aims to provide a modern, extensible, and maintainable server alternative with support for the latest client versions.
## ❗️ Important Notice

## Features
This project is archived and will not receive further updates or support.
For the latest development, please refer to the new project:

- ⚡ Modern C# (.NET 9.0) implementation
- 🔄 Event-driven architecture
- 🚀 High performance through optimized code and event loop system
- 🔌 Modular design with dependency injection
- 📦 Support for multiple client versions
- 🔒 Secure authentication using JWT
- 📈 Built-in metrics and diagnostics
- 🔧 Extensible through JavaScript scripting system
- 🌐 REST API for integration with external tools
- 🌍 Multi-language support with localization services
- 💾 Automatic world saving system
- 🧰 Modern UOP file format support
👉 [Moongate](https://github.com/Moongate-server/Moongate)

## Scripting System
---

PrimaUO includes a powerful JavaScript scripting engine that allows you to extend and customize server functionality without modifying the core code. Scripts are loaded from the `scripts` directory and can interact with server systems through registered modules.
## Why the Change?

### Example Script
Prima served as an initial prototype for my solution. However, to address architectural limitations and enhance scalability, I have restructured and rebuilt the project from the ground up under the Moongate initiative.

```javascript
// bootstrap.js
// This script is executed when the server starts
---

// Import script modules are automatically registered by the server
// Log a message to the server console
console.Log("Server startup script loaded!");
## What’s New in Moongate?

// Listen for server started event
events.OnStarted(() => {
logger.Info("Server has started successfully!");
- **Modernized architecture** with improved performance
- **Enhanced developer experience** and documentation
- **Active development and community support**

// Register admin commands
commands.RegisterConsoleCommand(
"announce",
"Broadcasts a message to all players",
(args) => {
const message = args.join(" ");
logger.Info(`Broadcasting message: ${message}`);
// Actual broadcast implementation would go here
return true;
},
["broadcast", "shout"],
CommandType.ALL,
CommandPermissionType.ADMIN
);
---

// Create a recurring task to run every 5 minutes
scheduler.ScheduleTask(
"cleanup",
300, // seconds
() => {
logger.Info("Running scheduled cleanup task...");
// Cleanup logic would go here
}
);
## Migration

// Register an event handler for character creation
events.OnCharacterCreated((args) => {
logger.Info(`New character created: ${args.Name}`);
We encourage all users and contributors to transition to [Moongate](https://github.com/Moongate-server/Moongate) for the latest updates and features.

// Start a delayed welcome timer
timers.OneShot("welcome_message", 10, () => {
logger.Info(`Sending welcome message to ${args.Name}`);
// Actual welcome message code would go here
});
});

// Register custom item
items.Register("enchanted_sword", {
ItemId: 3937,
Name: "Enchanted Longsword",
GraphicId: 0x0F60,
Weight: 4.0,
Layer: Layer.ONE_HANDED,
Amount: 1,
Damage: {
Min: 10,
Max: 25,
Type: "Slashing"
}
});
});
```

### Available Script Modules

The scripting API exposes the following modules:

- **logger**: Functions for logging at different levels (Info, Debug, Warn, Error, Critical)
- **events**: Subscribe to server events like OnStarted, OnUserLogin, OnCharacterCreated
- **scheduler**: Schedule tasks to run periodically
- **template**: Add variables for template rendering and text processing
- **files**: Include other script files or directories
- **timers**: Create one-time or repeating timers with delays
- **items**: Register custom items with properties and behaviors
- **console**: Log messages to the console or prompt for input
- **commands**: Register custom commands for console or in-game use

## Project Structure

- **Prima.Core.Server**: Core server infrastructure
- **Prima.Network**: Network layer and packet handling
- **Prima.Server**: Main server application
- **Prima.UOData**: Ultima Online data structures and utilities
- **Prima.JavaScript.Engine**: JavaScript scripting engine for server extensibility
- **Prima.Tcp.Test**: TCP testing utilities

## Getting Started

### Prerequisites

- .NET 9.0 SDK
- Ultima Online client files for data loading

### Configuration

Configuration is handled through YAML files located in the `configs` directory. Here's an example configuration:

```yaml
debug:
save_raw_packets: false
process:
pid_file: server.pid
process_queue:
max_parallel_task: 2
shard:
max_players: 1000
uo_directory: /path/to/uo/files
client_version:
name: Prima Shard
admin_email: admin@primauo.com
url: https://github.com/tgiachi/prima
time_zone: 0
language: en
autosave:
interval_in_minutes: 5
jwt_auth:
issuer: Prima
audience: Prima
secret: YourSecretKey
expiration_in_minutes: 44640
refresh_token_expiry_days: 1
accounts:
is_registration_enabled: true
smtp:
host: localhost
port: 25
username: ''
password: ''
use_ssl: false
use_start_tls: false
tcp_server:
host: ''
login_port: 2593
game_port: 2592
enable_web_server: true
web_server_port: 23000
log_packets: true
```

### Building from Source

```bash
git clone https://github.com/tgiachi/prima.git
cd prima
dotnet build
```

### Running the Server

```bash
cd src/Prima.Server
dotnet run
```

### Docker Support

A Dockerfile is provided to build and run the server in a container:

```bash
docker build -t primauo/server .
docker run -p 2593:2593 -p 2592:2592 -p 23000:23000 -v /path/to/uo/files:/app/data/client primauo/server
```

## API Documentation

The server includes a Swagger UI for API documentation available at:

```
http://localhost:23000/swagger
```

## Type Definitions for Scripts

The server automatically generates TypeScript definitions for the scripting API in `scripts/index.d.ts`, which provides autocomplete and type checking when using an editor like Visual Studio Code:

```typescript
/**
* prima v0.15.4.0 JavaScript API TypeScript Definitions
* Auto-generated documentation on 2025-05-16 10:46:15
**/

// Constants
declare const APP_NAME: string;
declare const VERSION: string;

// Modules
declare const logger: {
Info(Message: string, Args: any[]): void;
Warn(Message: string, Args: any[]): void;
Error(Message: string, Args: any[]): void;
Critical(Message: string, Args: any[]): void;
Debug(Message: string, Args: any[]): void;
};

declare const events: {
OnStarted(Action: () => void): void;
HookEvent(EventName: string, EventHandler: (arg: any) => void): void;
OnUserLogin(Action: (arg: IUserLoginContext) => void): void;
OnCharacterCreated(Action: (arg: ICharacterCreatedEventArgs) => void): void;
};

// ... and other module definitions
```

### Help Wanted!
I'm actively seeking developers to join the PrimaUO project!
This project aims to modernize Ultima Online and keep this legendary MMORPG alive for future generations. Whether you're skilled in C#, JavaScript, network programming, or game development, your contributions can make a difference.
Why join us:

Work on preserving gaming history
Modernize a classic MMORPG with cutting-edge technologies
Join a passionate community of UO enthusiasts
Gain experience with .NET 9.0, modern architecture patterns, and game server development

No previous Ultima Online development experience is required - just enthusiasm and a willingness to learn. If you're interested in contributing, please reach out via GitHub issues or discussions.
Together, we can ensure Ultima Online continues to thrive for years to come!


## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

## License

This project is licensed under the MIT License - see the LICENSE file for details.

## Acknowledgments

- The Ultima Online community
- [ModernUO](https://github.com/modernuo/modernuo) for inspiration and some utility code
- [UOX3](https://github.com/UOX3DevTeam/UOX3) for inspiration
Thank you for your support and contributions to Prima!
5 changes: 4 additions & 1 deletion src/Prima.Server/Handlers/LoginCompleteHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,15 @@ protected override void RegisterHandlers()

public async Task HandleAsync(LoginCompleteEvent @event, CancellationToken cancellationToken = default)
{

var session = SessionService.GetSession(@event.SessionId);
session.UseNetworkCompression = true;

var mobile = session.GetProperty<MobileEntity>();

// Login confirmation packet
await session.SendPacketAsync(new CharLocaleAndBody(mobile));
// GeneralInformation packet
//GeneralInformation packet
await session.SendPacketAsync(new SeasonalInformation(Season.Spring, true));
await session.SendPacketAsync(new DrawGamePlayer(mobile));
await session.SendPacketAsync(new CharacterDraw(mobile));
Expand Down
4 changes: 2 additions & 2 deletions src/Prima.Server/Services/NetworkService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,8 @@ private async Task SendPacketInternal(string sessionId, IUoNetworkPacket packet)
"-> {PacketType} {Session} ({Size} bytes) {Data}",
packet.GetType().Name,
sessionId.ToShortSessionId(),
data.Length,
data.ToArray().HumanizedContent(20)
packetContent.Length,
packetContent.HumanizedContent(20)
);

await _networkTransportManager.EnqueueMessageAsync(
Expand Down
54 changes: 54 additions & 0 deletions src/src.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.2.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Prima.Tcp.Test", "Prima.Tcp.Test\Prima.Tcp.Test.csproj", "{F80C9EA4-41E1-D42F-AEF0-B836341FE624}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Prima.UOData", "Prima.UOData\Prima.UOData.csproj", "{CB0AD729-B3AA-65AC-7F42-D703FAAB4949}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Prima.Server", "Prima.Server\Prima.Server.csproj", "{F6BFD976-8F6F-7B94-82F7-4D8C1829417E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Prima.Core.Server", "Prima.Core.Server\Prima.Core.Server.csproj", "{E6D09843-A464-8604-1143-2E6DA21DBE55}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Prima.Network", "Prima.Network\Prima.Network.csproj", "{D784E30C-1DE7-057A-DD71-1E2FE32F523D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Prima.JavaScript.Engine", "Prima.JavaScript.Engine\Prima.JavaScript.Engine.csproj", "{8D77D0F0-24E3-CCDA-F5D8-4BD39E23ED64}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F80C9EA4-41E1-D42F-AEF0-B836341FE624}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F80C9EA4-41E1-D42F-AEF0-B836341FE624}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F80C9EA4-41E1-D42F-AEF0-B836341FE624}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F80C9EA4-41E1-D42F-AEF0-B836341FE624}.Release|Any CPU.Build.0 = Release|Any CPU
{CB0AD729-B3AA-65AC-7F42-D703FAAB4949}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CB0AD729-B3AA-65AC-7F42-D703FAAB4949}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CB0AD729-B3AA-65AC-7F42-D703FAAB4949}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CB0AD729-B3AA-65AC-7F42-D703FAAB4949}.Release|Any CPU.Build.0 = Release|Any CPU
{F6BFD976-8F6F-7B94-82F7-4D8C1829417E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F6BFD976-8F6F-7B94-82F7-4D8C1829417E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F6BFD976-8F6F-7B94-82F7-4D8C1829417E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F6BFD976-8F6F-7B94-82F7-4D8C1829417E}.Release|Any CPU.Build.0 = Release|Any CPU
{E6D09843-A464-8604-1143-2E6DA21DBE55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E6D09843-A464-8604-1143-2E6DA21DBE55}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E6D09843-A464-8604-1143-2E6DA21DBE55}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E6D09843-A464-8604-1143-2E6DA21DBE55}.Release|Any CPU.Build.0 = Release|Any CPU
{D784E30C-1DE7-057A-DD71-1E2FE32F523D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D784E30C-1DE7-057A-DD71-1E2FE32F523D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D784E30C-1DE7-057A-DD71-1E2FE32F523D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D784E30C-1DE7-057A-DD71-1E2FE32F523D}.Release|Any CPU.Build.0 = Release|Any CPU
{8D77D0F0-24E3-CCDA-F5D8-4BD39E23ED64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8D77D0F0-24E3-CCDA-F5D8-4BD39E23ED64}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8D77D0F0-24E3-CCDA-F5D8-4BD39E23ED64}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8D77D0F0-24E3-CCDA-F5D8-4BD39E23ED64}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DBC843DA-943B-4F27-87DB-4BD5E473D031}
EndGlobalSection
EndGlobal
Loading