There is a difference in "modular scripting" and "object oriented scripting".
The word module is self explanatory. It means spliting your script, either by hooking way(which i recommend) or simple splitting. Also another way out is using filterscripts and (S)PVars fpr script integration. The only reason you do modular type of scripting is for: - Script integration (like you want to get player admin level in more than one module, like you have login dialog in login.pwn and want to access that variable in say houses.pwn) - Making a part more readable and editable, like you know in future you want to change textdraws, you can make a function "CreateSelectionTextDraws" in a seperate module so it will be easy for modifying the tds) - Plug'n'play features, they are same as filterscripts (like maps or objects; a custom vending machine script etc) I remade serversided.inc using modular scripting technique, if you want a refrence here it is: https://github.com/Agneese-Saini/SA-...de/serversided |
/*
-
*/
#if defined ji@core_MySQLConnection
#endinput
#endif
#define ji@core_MySQLConnection
#if !defined OnTestServer
#error "OnTestServer n'est pas dйfinie!"
#endif
#if OnTestServer
#define MYSQL_HOST "localhost"
#define MYSQL_USER "root"
#define MYSQL_DATABASE "myserver"
#define MYSQL_PASSWORD ""
#else
#if !defined MYSQL_HOST
#define MYSQL_HOST "127.0.0.1"
#define MYSQL_USER "-"
#define MYSQL_DATABASE "-"
#define MYSQL_PASSWORD "-"
#endif
#endif
new MySQL:MySQL;
stock ConnectToMYSQLDataBase()
{
new MySQLOpt:option_id = mysql_init_options();
mysql_set_option(option_id, AUTO_RECONNECT, true);
MySQL = mysql_connect(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE, option_id);
if(MySQL == MYSQL_INVALID_HANDLE || mysql_errno(MySQL) != 0)
{
print("[SERVER] Connexion MySQL ratйe. Fermeture du serveur...");
SendRconCommand("exit");
return 1;
}
print("[SERVER] Connexion MySQL rйussie.");
mysql_log(ALL);
return 1;
}
#include "/newGM/core/MySQLConnection.inc"
...
public OnGameModeInit()
{
print("\n\n[SERVER] Initialisation du gamemode...");
#if OnTestServer
print("[SERVER] Bienvenue sur le serveur de test!");
#endif
#if DebugMode
print("[SERVER] MODE DEBUG ACTIVЙ !");
#endif
ConnectToMYSQLDataBase();
...
Code:
/* - How it happens behind the console -------------------------------------- * OnGameModeInitEx - first * hooks of OnGameModeInit - second -------------------------------------- "OnGameModeInitEx" will be called first to avoid crashes and confusion within the hooks. Everything that you have to do regarding OnGameModeInit You have to place them in "OnGameModeInitEx" to make sure other hooks will not mess up on start up. */ |
Hello. I'm wondering if it's not better to make a gameode with only include.
Example: - MainFolder -- Players --- VariablesSaveLoading --- Other -- Vehicle ... And all of them included in one gamemode. |
- {Name of Project} (Folder) - Config ( Core ) - base.pwn ( Global Definitions, MySQL, Server Settings ) - macros.pwn ( Global Macros for shortcuts like FUNCTION: and PUBLIC: ) - plugins.pwn ( Global Plugin Definitions ) - Player ( Module ) // The Prefix will be PD for PLAYER_DATA the name of the ENUM ( E_PLAYER_DATA ) - init.pwn ( Includes, Initialize and Deinitialize functions ) - config.pwn ( Enums, Definitions, Variables ) - functions.pwn ( IsPlayerLoggedIn, etc ) - database.pwn ( DB Functions / Threaded Query ) - callbacks.pwn ( Prefixed Names, PD_OnPlayerConnect ) - dialogs.pwn ( Functions / Responses ) - textdraws.pwn ( Functions / Callbacks ) - commands.pwn ( Player / Admin Commands ) // You can just add a file for each section, pickups / objects, etc bootstrap.pwn // includes all modules and core files, so main gamemode only needs to include this file.
#define ENVIORNMENT ( 0 ) // 0 - Production, 1 - Local Development, 2 - Remote Development #define MYSQL_REMOTE_HOST "xxx.xxx.xxx.xxxx" #define MYSQL_REMOTE_USER "server_name" #define MYSQL_REMOTE_PASS "server_password" #define MYSQL_REMOTE_TABLE "server_table" #define MYSQL_LOCAL_HOST "xxx.xxx.xxx.xxxx" #define MYSQL_LOCAL_USER "server_name" #define MYSQL_LOCAL_PASS "server_password" #define MYSQL_LOCAL_TABLE "server_table" #define MYSQL_PRODUCTION_HOST "xxx.xxx.xxx.xxxx" #define MYSQL_PRODUCTION_USER "server_name" #define MYSQL_PRODUCTION_PASS "server_password" #define MYSQL_PRODUCTION_TABLE "server_table" // Now depending on how much you care, not that it makes much difference that you should, you can either use #if and #endif // or you can a switch statment, I prefer swtich as it looks cleaner, and I love clean code. new database_connection ; stock mysql_connect_ex() { switch( ENVIORNMENT ) { case 0: database_connection = mysql_connect( MYSQL_PRODUCTION_HOST, MYSQL_PRODUCTION_USER, MYSQL_PRODUCTION_PASS, MYSQL_PRODUCTION_TABLE ); case 1: database_connection = mysql_connect( MYSQL_LOCAL_HOST, MYSQL_LOCAL_USER, MYSQL_LOCAL_PASS, MYSQL_LOCAL_TABLE ); case 2: database_connection = mysql_connect( MYSQL_REMOTE_HOST, MYSQL_REMOTE_USER, MYSQL_REMOTE_PASS, MYSQL_REMOTE_TABLE ); } return database_connection; }
Here is an example of how I lay things out
Code:
- {Name of Project} (Folder) - Config ( Core ) - base.pwn ( Global Definitions, MySQL, Server Settings ) - macros.pwn ( Global Macros for shortcuts like FUNCTION: and PUBLIC: ) - plugins.pwn ( Global Plugin Definitions ) - Player ( Module ) // The Prefix will be PD for PLAYER_DATA the name of the ENUM ( E_PLAYER_DATA ) - init.pwn ( Includes, Initialize and Deinitialize functions ) - config.pwn ( Enums, Definitions, Variables ) - functions.pwn ( IsPlayerLoggedIn, etc ) - database.pwn ( DB Functions / Threaded Query ) - callbacks.pwn ( Prefixed Names, PD_OnPlayerConnect ) - dialogs.pwn ( Functions / Responses ) - textdraws.pwn ( Functions / Callbacks ) - commands.pwn ( Player / Admin Commands ) // You can just add a file for each section, pickups / objects, etc bootstrap.pwn // includes all modules and core files, so main gamemode only needs to include this file. As for your mysql idea I find this to be a better way. Code:
#define ENVIORNMENT ( 0 ) // 0 - Production, 1 - Local Development, 2 - Remote Development #define MYSQL_REMOTE_HOST "xxx.xxx.xxx.xxxx" #define MYSQL_REMOTE_USER "server_name" #define MYSQL_REMOTE_PASS "server_password" #define MYSQL_REMOTE_TABLE "server_table" #define MYSQL_LOCAL_HOST "xxx.xxx.xxx.xxxx" #define MYSQL_LOCAL_USER "server_name" #define MYSQL_LOCAL_PASS "server_password" #define MYSQL_LOCAL_TABLE "server_table" #define MYSQL_PRODUCTION_HOST "xxx.xxx.xxx.xxxx" #define MYSQL_PRODUCTION_USER "server_name" #define MYSQL_PRODUCTION_PASS "server_password" #define MYSQL_PRODUCTION_TABLE "server_table" // Now depending on how much you care, not that it makes much difference that you should, you can either use #if and #endif // or you can a switch statment, I prefer swtich as it looks cleaner, and I love clean code. new database_connection ; stock mysql_connect_ex() { switch( ENVIORNMENT ) { case 0: database_connection = mysql_connect( MYSQL_PRODUCTION_HOST, MYSQL_PRODUCTION_USER, MYSQL_PRODUCTION_PASS, MYSQL_PRODUCTION_TABLE ); case 1: database_connection = mysql_connect( MYSQL_LOCAL_HOST, MYSQL_LOCAL_USER, MYSQL_LOCAL_PASS, MYSQL_LOCAL_TABLE ); case 2: database_connection = mysql_connect( MYSQL_REMOTE_HOST, MYSQL_REMOTE_USER, MYSQL_REMOTE_PASS, MYSQL_REMOTE_TABLE ); } return database_connection; } |
- Config ( Core ) - base.pwn ( Global Definitions, MySQL, Server Settings ) - macros.pwn ( Global Macros for shortcuts like FUNCTION: and PUBLIC: ) - plugins.pwn ( Global Plugin Definitions )
new
Log_Level:Log_Server,
...;
enum Log_Level:LogType
{
NOTHING,
COMMANDS,
TCHAT,
FULL_LOG = NOTHING | COMMANDS | TCHAT
};
stock CreateServerLog(Log_Level:Log_Serv = Log_Level:FULL_LOG)
{
Log_Server = Log_Serv;
if(_:Log_Serv == NOTHING)
return print("[SERVER] Aucun log crйй."), 1;
static
string[120],
hour,
minu,
sec,
day,
month,
year;
Get@SetWorldTime(hour, minu, sec);
getdate(year, month, day);
format(string, sizeof(string), "-------- Dйbut -------- [%02i/%02i/%02i] - %02ih %02imin %02is.", day, month, year, hour, minu, sec);
WriteLogs("AllCommands.txt", string);
print("[SERVER] Logs configurйs.")
return 1;
}
stock Get@SetWorldTime(&hour = -1, &minutes = -1, &seconds = -1)
{
gettime(hour, minu, sec);
SetWorldTime(hour);
}
WriteLogs(file[70], const reason[], playerid = INVALID_PLAYER_ID)
{
if(!_:Log_Server)
return 1;
static day,
month,
hour,
minute,
seconde,
year;
static stringW[190];
if(strfind(file, ".txt", false, 0) == -1) format(file, sizeof(file), "%s.txt", file);
new File:pos=fopen(file, io_append);
if(!pos)
{
printf("[Server] Write Logs Error | Reason : pos=fopen Error | File : '%s' | Reason: '%s'", file, reason);
SendMessageToAdmins(RED, "[Server] "SAUMON_U" Une erreur est survenue dans les logs. Merci de vйrifier ceux-ci!");
SendMessageToAdmins(RED, "[Server] "SAUMON_U"Fichier : '%s' | Contenus : '%s'", file, reason);
return 1;
}
gettime(hour, minute, seconde);
getdate(year, month, day);
if(!strcmp(file, "AllCommands.txt") && _:Log_Server == L_COMMANDS)
format(stringW,sizeof(stringW),"\r\n[%i] [%02i/%02i/%02i] %02i:%02i:%02i %s | '%s'", CmdID++, day, month, year, hour, minute, seconde, playerid != INVALID_PLAYER_ID ? GetName(playerid, true) : "ALL", reason);
else if(!strcmp(file, "AllTChat.txt") && _:Log_Server == L_TCHAT)
format(stringW,sizeof(stringW),"\r\n[%02i/%02i/%02i] %02i:%02i:%02i %s (Player #%i) » '%s'", day, month, year, hour, minute, seconde, playerid != INVALID_PLAYER_ID ? GetName(playerid, true) : "ALL", p_NameOff{playerid}, reason);
else if(_:Log_Server == L_TCHAT)
format(stringW,sizeof(stringW),"\r\n[%02i/%02i/%02i] %02i:%02i:%02i %s | '%s'", day, month, year, hour, minute, seconde, playerid != INVALID_PLAYER_ID ? GetName(playerid, true) : "ALL", reason);
fwrite(pos,stringW);
fclose(pos);
return 1;
}
I've 2 connection's type : when i'm on local (OnTestServer) and when I'm not. I'm using OnTestServer in differents commands like /ban, etc..
For this: Code:
- Config ( Core ) - base.pwn ( Global Definitions, MySQL, Server Settings ) - macros.pwn ( Global Macros for shortcuts like FUNCTION: and PUBLIC: ) - plugins.pwn ( Global Plugin Definitions ) "../core/configuration.inc" (GM's inititialization) "../core/MySQLConnections.inc" (Creating table, connection to mysql, etc...) "../core/base.inc" (Macros and constants) That's good? Annnd.. I'm trying a new thing. It's for creating logs (from configuration.inc): PHP Code:
EDIT: Write logs function PHP Code:
|
I've 2 connection's type : when i'm on local (OnTestServer) and when I'm not. I'm using OnTestServer in differents commands like /ban, etc..
For this: Code:
- Config ( Core ) - base.pwn ( Global Definitions, MySQL, Server Settings ) - macros.pwn ( Global Macros for shortcuts like FUNCTION: and PUBLIC: ) - plugins.pwn ( Global Plugin Definitions ) "../core/configuration.inc" (GM's inititialization) "../core/MySQLConnections.inc" (Creating table, connection to mysql, etc...) "../core/base.inc" (Macros and constants) That's good? Annnd.. I'm trying a new thing. It's for creating logs (from configuration.inc): PHP Code:
EDIT: Write logs function PHP Code:
|
Don't do logs in that way, just... don't...
Maddinat0r made an excepcional plugin for logging: https://sampforum.blast.hk/showthread.php?tid=603175 |
stock CreateServerLog(LogType:Log_Serv = FULL_LOG)
{
Log_Server = _:Log_Serv;
if(Log_Serv == NOTHING)
return print("[SERVER] Aucune logs crййes."), 1;
static
string[120],
hour,
minu,
sec,
day,
month,
year;
Get@SetWorldTime(hour, minu, sec);
getdate(year, month, day);
format(string, sizeof(string), "-------- Dйbut -------- [%02i/%02i/%02i] - %02ih %02imin %02is.", day, month, year, hour, minu, sec);
WriteLogs("AllCommands.txt", string);
print("[SERVER] Logs configurйs.");
return 1;
}
Name looked horrible (like you said), so I changed most of them. Is better now?
![]() And what I should comment ? When I include something? |
Because PAWN is not object oriented, PAWN is simple, PAWN is outdated (the version SAMP uses is probably 10 years old), and more ..
I didn't "fail", I've been testing PAWN limits for months now, and I'm saying there is no way to process with a REAL modular design, yeah you could shit yourself and make some files called "callbacks, commands, header" separate then include to main file, but this isn't modular. I'm not just a beginner scripter, it's just the language that lacks the required features to make it modular. Adding some hooks and functions in different files, isn't what Modular design is. Anyways, that's my last reply here, I've said what I wanted to. |
new gVehicleOwner;
new gLastKillerID;
static
gVehicleOwner[MAX_VEHICLES] = { INVALID_PLAYER_ID, ... };
bool:VO_SetOwner(vehicleid, playerid)
{
// Already owned.
if (gVehicleOwner[vehicleid] != INVALID_PLAYER_ID)
return false;
gVehicleOwner[vehicleid] = playerid;
return true;
}
VO_GetOwner(vehicleid)
{
return gVehicleOwner[vehicleid];
}
static
Iterator:gOwnedVehicles<MAX_PLAYERS, MAX_VEHICLES>;
bool:VO_SetOwner(vehicleid, playerid)
{
// Already owned.
if (Iter_Contains(gOwnedVehicles<>, vehicleid))
return false;
Iter_Add(gOwnedVehicles<playerid>, vehicleid);
return true;
}
VO_GetOwner(vehicleid)
{
new
ret = Iter_GetMulti(gOwnedVehicles<>, vehicleid);
return (ret == -1) ? INVALID_PLAYER_ID : ret;
}
No, having hooks and functions in separate files is not modular design at all, if you have a file called "callbacks" anywhere in your hierarchy you are probably doing it wrong, no wonder you gave up - that is not "real" at all. However, if you do it properly it is very easy to get good modular design, even in PAWN (and I've been testing its limits for years now).
Hooks, functions, macros, variables etc. should be kept together in one file when they are for the same logical part of code (semantics - meaning, instead of syntax - appearance). If you have a variable storing the owner of a vehicle, and a function to sell a vehicle - put them in the same file, named something like "VehicleOwnership.pwn". If you have another variable storing the last person to kill someone, that has probably got nothing at all to do with vehicle ownership and should go in a totally separate file. Yes, they are both variables. Yes, they might look very similar, so you can do: PHP Code:
The best way I have found to keep things modular is static variables. Make all the globals in one file "static" and only provide access through a function API: PHP Code:
PHP Code:
Some good examples of modular modes and includes: YSI (obviously). Look through that and take a guess at what "y_inline.inc", "y_foreach.inc", and "y_commands.inc" do. They do often have a second file of (say) "y_commands/impl.inc", but it is always just "impl.inc" (short for "implementation") and never split up further in to variables and functions in separate files, because then you can't use statics, and then you can't KNOW that no-one else is directly accessing your private internal state. Even the tests don't (usually) have access to internal state, because they test the external API ("VO_SetOwner" and "VO_GetOwner" above), not the implementation (again, usually). https://github.com/Zeex/amx_assembly Designed very purely, so actually has very little global state (variables) at all anywhere. https://github.com/Southclaws/ScavengeSurvive Already mentioned, but worth repeating. Again, see how all the files are named for the gamemode system they implement, not what the code in them looks like. A file for macros, another file for functions, that's just totally useless - like classifying vehicles by their colour because a red van and a red car have a superficially similar appearance (as functions do to other functions), instead of by use. |