/*****************************************************************************/ /* Template for version 2.0 plugins. */ /* Contact: yann.chachkoff@myrealbox.com */ /*****************************************************************************/ /* That code is placed under the GNU General Public Licence (GPL) */ /* (C)2001-2005 by Chachkoff Yann (Feel free to deliver your complaints) */ /*****************************************************************************/ /* CrossFire, A Multiplayer game for X-windows */ /* */ /* Copyright (C) 2000 Mark Wedel */ /* Copyright (C) 1992 Frank Tore Johansen */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ /* the Free Software Foundation; either version 2 of the License, or */ /* (at your option) any later version. */ /* */ /* This program is distributed in the hope that it will be useful, */ /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ /* GNU General Public License for more details. */ /* */ /* You should have received a copy of the GNU General Public License */ /* along with this program; if not, write to the Free Software */ /* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* */ /*****************************************************************************/ /** * @defgroup plugin_citylife City life plugin * This plugin adds random NPCs to town, and makes them enter houses, spawns new * ones. * * When a map is loaded, NPCs are randomly added so they appear already. During the * course of the server, some will enter houses (disappear), others will exit from * houses (appear on a house). * * For each map to be processed, two things are defined: * - spawn zones, where NPCs should be added when the map is loaded * - spawn points, from which to add new NPCs while the map is in memory. * Should probably be houses and such. * * NPCs use a key/value to prevent them from immediately entering the building * they exited. * * @todo * - define spawn points/zones for other towns * - vary NPCs based on time of day * - define "objectives" to go to * - make NPCs pause when player talks to them * * @{ */ /** * @file * This file is part of the @ref plugin_citylife "city life plugin". * See this page for more information. */ #include #include #ifndef __CEXTRACT__ #include #endif CF_PLUGIN int initPlugin(const char *iversion, f_plug_api gethooksptr) { cf_init_plugin(gethooksptr); cf_log(llevDebug, PLUGIN_VERSION " init\n"); return 0; } CF_PLUGIN void *getPluginProperty(int *type, ...) { va_list args; const char *propname; int size; char *buf; va_start(args, type); propname = va_arg(args, const char *); if (!strcmp(propname, "Identification")) { buf = va_arg(args, char *); size = va_arg(args, int); va_end(args); snprintf(buf, size, PLUGIN_NAME); return NULL; } else if (!strcmp(propname, "FullName")) { buf = va_arg(args, char *); size = va_arg(args, int); va_end(args); snprintf(buf, size, PLUGIN_VERSION); return NULL; } va_end(args); return NULL; } CF_PLUGIN int citylife_runPluginCommand(object *op, char *params) { return -1; } /** Key to contain whether it's the first move of the NPC or not. */ #define FIRST_MOVE_KEY "citylife_first_move" /** * Point from which a NPC can come when the map is loaded. */ typedef struct { int x; int y; } spawn_point; /** * Zone in which to add NPCs when the map was just loaded. * NPC will be added in [sx, ex[ and [sy, ey[. */ typedef struct { int sx, sy, ex, ey; } spawn_zone; /** * Options for a map. */ typedef struct { const spawn_point *points; /**< Points to spawn from when there is a player on the map. */ int count_points; /**< How many items in points. */ const spawn_zone *zones; /**< Zones where to spawn at load time. */ int count_zones; /**< How many items in zones. */ int population; /**< Maximum of NPCs to add at load time. */ const char *mapname; /**< Map path. */ const char *const *available_archetypes; /**< What archetypes can we chose from for an NPC? */ int archetypes_count; /**< Number of items in available_archetypes. */ } mapzone; /*@}*/ /** * @defgroup citylife_scorn Scorn parameters * Parameters for the @ref plugin_citylife "City life" plugin for Scorn. * * The city is pretty rectangular, so quite easy to define large zones to add to. * @ingroup plugin_citylife */ /*@{*/ /** Zones for map 104_115. */ static const spawn_zone scorn_nw_zones[] = { { 40, 26, 50, 50 } }; /** Points for map 104_115. */ static const spawn_point scorn_nw_points[] = { { 41, 37 }, { 48, 35 }, { 49, 40 }, { 47, 22 }, { 49, 37 } }; /** Zones for map 105_115. */ static const spawn_zone scorn_ne_zones[] = { { 0, 26, 22, 50 } }; /** Points for map 105_115. */ static const spawn_point scorn_ne_points[] = { { 15, 42 }, { 9, 35 }, { 15, 29 }, { 1, 25 }, { 1, 29 } }; /** Zones for map 104_116. */ static const spawn_zone scorn_sw_zones[] = { { 41, 0, 50, 10 } }; /** Points for map 104_116. */ static const spawn_point scorn_sw_points[] = { { 41, 2 }, { 46, 8 }, { 42, 8 } }; /** Zones for map 105_116. */ static const spawn_zone scorn_se_zones[] = { { 0, 0, 13, 10 } }; /** Points for map 105_116. */ static const spawn_point scorn_se_points[] = { { 2, 8 }, { 11, 8 }, { 8, 1 }, { 5, 8 } }; /** Archetypes to spawn in Scorn. */ static const char *const scorn_archs[] = { "c_man", "c_woman", "child", "farmer", "fatman", "fatwoman", "guard", "knight", "man", "nun", "sage", "woman" }; /*@}*/ /** @ingroup plugin_citylife @{*/ /** * All maps we work on. */ static const mapzone available_zones[] = { { scorn_nw_points, 5, scorn_nw_zones, 1, 2, "/world/world_104_115", scorn_archs, 12 }, { scorn_ne_points, 1, scorn_ne_zones, 1, 5, "/world/world_105_115", scorn_archs, 12 }, { scorn_sw_points, 3, scorn_sw_zones, 1, 5, "/world/world_104_116", scorn_archs, 12 }, { scorn_se_points, 1, scorn_se_zones, 1, 5, "/world/world_105_116", scorn_archs, 12 }, { NULL, -1, NULL, -1, 1, "", NULL, 0 }, }; /** * Finds if a map has a zone defined. * * @param map * candidat map. * @return * map zone, NULL if not defined. */ static const mapzone *get_zone_for_map(mapstruct *map) { int test; for (test = 0; available_zones[test].count_points != -1; test++) { if (strcmp(available_zones[test].mapname, map->path) == 0) return &available_zones[test]; } return NULL; } /** * Creates a NPC for the specified zone, and do needed initialization. * @param zone * what NPCs to create. * @return * new NPC, with event handled for time. NULL if invalid archetype in the zone. */ static object *get_npc(const mapzone *zone) { int arch = RANDOM()%zone->archetypes_count; object *npc = cf_create_object_by_name(zone->available_archetypes[arch]); object *evt; if (!npc) { cf_log(llevError, PLUGIN_NAME ": get_npc() got NULL object for %s!\n", zone->available_archetypes[arch]); return NULL; } cf_object_set_flag(npc, FLAG_RANDOM_MOVE, 1); /* Prevent disease spreading in Scorn, mostly rabies. */ cf_object_set_flag(npc, FLAG_UNDEAD, 1); /* add a key so NPC will not disappear in the house it came from */ cf_object_set_key(npc, FIRST_MOVE_KEY, "1", 1); evt = cf_create_object_by_name("event_time"); evt->slaying = cf_add_string(PLUGIN_NAME); evt->title = cf_add_string(PLUGIN_NAME); cf_object_insert_object(evt, npc); return npc; } /** * Add an NPC somewhere in a spawn zone. * @param zone * map zone definition from which to get a spawn zone. * @param map * map to insert into. */ static void add_npc_to_zone(const mapzone *zone, mapstruct *map) { int which; object *npc = get_npc(zone); if (!npc) return; which = RANDOM()%zone->count_zones; if (cf_object_teleport(npc, map, zone->zones[which].sx+RANDOM()%(zone->zones[which].ex-zone->zones[which].sx), zone->zones[which].sy+RANDOM()%(zone->zones[which].ey-zone->zones[which].sy))) { cf_object_free(npc); } } /** * Add an NPC somewhere at a spawn point. * @param zone * map zone definition from which to get a spawn point. * @param map * map to insert into. */ static void add_npc_to_point(const mapzone *zone, mapstruct *map) { int which; object *npc = get_npc(zone); which = RANDOM()%zone->count_points; if (cf_object_teleport(npc, map, zone->points[which].x, zone->points[which].y)) { cf_object_free(npc); } } /** * Add some NPCs to the map, based on the zone definition. * @param map * map to add to. */ static void add_npcs_to_map(mapstruct *map) { int add; const mapzone *zone = get_zone_for_map(map); if (!zone) return; add = 1+RANDOM()%zone->population; cf_log(llevDebug, PLUGIN_NAME ": adding %d NPC to map %s.\n", add, map->path); while (add-- >= 0) { add_npc_to_zone(zone, map); } } /** * Find a suitable map loaded and add an NPC to it. */ static void add_npc_to_random_map(void) { int count, test; mapstruct *list[50]; int zones[50]; count = 0; cf_log(llevDebug, PLUGIN_NAME ": adding NPC to random map.\n"); for (test = 0; available_zones[test].count_points != -1 && count < 50; test++) { if ((list[count] = cf_map_has_been_loaded(available_zones[test].mapname)) && (list[count]->in_memory == MAP_IN_MEMORY)) { zones[count] = test; count++; } } if (!count) return; test = RANDOM()%count; add_npc_to_point(&available_zones[zones[test]], list[test]); } CF_PLUGIN void *citylife_globalEventListener(int *type, ...) { va_list args; static int rv = 0; mapstruct *map; int code; va_start(args, type); code = va_arg(args, int); rv = 0; switch (code) { case EVENT_MAPLOAD: map = va_arg(args, mapstruct *); add_npcs_to_map(map); break; case EVENT_CLOCK: if (RANDOM()%40 == 0) add_npc_to_random_map(); } va_end(args); return &rv; } CF_PLUGIN int postInitPlugin(void) { cf_log(llevDebug, PLUGIN_VERSION " post init\n"); /* Pick the global events you want to monitor from this plugin */ /* cf_system_register_global_event(EVENT_BORN, PLUGIN_NAME, citylife_globalEventListener); cf_system_register_global_event(EVENT_CRASH, PLUGIN_NAME, citylife_globalEventListener); cf_system_register_global_event(EVENT_PLAYER_DEATH, PLUGIN_NAME, citylife_globalEventListener); cf_system_register_global_event(EVENT_GKILL, PLUGIN_NAME, citylife_globalEventListener); cf_system_register_global_event(EVENT_LOGIN, PLUGIN_NAME, citylife_globalEventListener); cf_system_register_global_event(EVENT_LOGOUT, PLUGIN_NAME, citylife_globalEventListener); cf_system_register_global_event(EVENT_MAPENTER, PLUGIN_NAME, citylife_globalEventListener); cf_system_register_global_event(EVENT_MAPLEAVE, PLUGIN_NAME, citylife_globalEventListener); cf_system_register_global_event(EVENT_MAPRESET, PLUGIN_NAME, citylife_globalEventListener); cf_system_register_global_event(EVENT_REMOVE, PLUGIN_NAME, citylife_globalEventListener); cf_system_register_global_event(EVENT_SHOUT, PLUGIN_NAME, citylife_globalEventListener); cf_system_register_global_event(EVENT_TELL, PLUGIN_NAME, citylife_globalEventListener); cf_system_register_global_event(EVENT_MUZZLE, PLUGIN_NAME, citylife_globalEventListener); cf_system_register_global_event(EVENT_KICK, PLUGIN_NAME, citylife_globalEventListener); */ cf_system_register_global_event(EVENT_CLOCK, PLUGIN_NAME, citylife_globalEventListener); cf_system_register_global_event(EVENT_MAPLOAD, PLUGIN_NAME, citylife_globalEventListener); /* cf_system_register_global_event(EVENT_MAPRESET, PLUGIN_NAME, citylife_globalEventListener); */ return 0; } CF_PLUGIN void *eventListener(int *type, ...) { static int rv = 1; va_list args; char *buf; object *ground, *who, *activator, *third, *event; int fix; const char *value; va_start(args, type); who = va_arg(args, object *); activator = va_arg(args, object *); third = va_arg(args, object *); buf = va_arg(args, char *); fix = va_arg(args, int); event = va_arg(args, object *); va_end(args); /* should our npc disappear? */ if (RANDOM()%100 < 30) { for (ground = cf_map_get_object_at(who->map, who->x, who->y); ground; ground = cf_object_get_object_property(ground, CFAPI_OBJECT_PROP_OB_ABOVE)) { if (ground->type == EXIT) { object *inv; value = cf_object_get_key(who, FIRST_MOVE_KEY); if (strcmp(value, "1") == 0) { cf_object_set_key(who, FIRST_MOVE_KEY, "0", 1); break; } /* must set inventory as no drop, else it'll just drop on the ground */ for (inv = cf_object_get_object_property(who, CFAPI_OBJECT_PROP_INVENTORY); inv; inv = cf_object_get_object_property(inv, CFAPI_OBJECT_PROP_OB_BELOW)) cf_object_set_flag(inv, FLAG_NO_DROP, 1); cf_log(llevDebug, PLUGIN_NAME ": NPC entering building.\n"); cf_object_remove(who); cf_object_free(who); return &rv; } } } /* we have to move manually, because during the night NPCs don't move. */ cf_object_move(who, 1+RANDOM()%8, NULL); return &rv; } CF_PLUGIN int closePlugin(void) { cf_log(llevDebug, PLUGIN_VERSION " closing\n"); return 0; } /*@}*/