Skip to content

Commit

Permalink
feat(content): add Lua bindings for spell types (#5851)
Browse files Browse the repository at this point in the history
* Initial Spell-binding Lua work

* Comment adjustment.

* Further expansion of bindings; fake_spell tentatively added as SpellShell for naming convention purposes

* Minor changes to spell bindings

* Further (minor) spellbinding work.

* Reoordered catalua_bindings.h for consistency.

* Added code allowing Luna to document properties properly.

* Further binding work, touchups, and a convenience function for quicker SpellSimple invocation.

* Minor grammar fix.

* style(autofix.ci): automated formatting

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
  • Loading branch information
karxi and autofix-ci[bot] authored Dec 30, 2024
1 parent 6f2904a commit a4dde2f
Show file tree
Hide file tree
Showing 8 changed files with 275 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/catalua_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1012,6 +1012,7 @@ void cata::reg_all_bindings( sol::state &lua )
reg_enums( lua );
reg_game_ids( lua );
mod_mutation_branch( lua );
reg_magic( lua );
reg_coords_library( lua );
reg_constants( lua );
reg_hooks_examples( lua );
Expand Down
4 changes: 4 additions & 0 deletions src/catalua_bindings.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,14 @@ void reg_locale_api( sol::state &lua );
void reg_map( sol::state &lua );
void reg_monster( sol::state &lua );
void mod_mutation_branch( sol::state &lua );
void reg_magic( sol::state &lua );
void reg_npc( sol::state &lua );
void reg_player( sol::state &lua );
void reg_point_tripoint( sol::state &lua );
void reg_skill_level_map( sol::state &lua );
void reg_spell_type( sol::state &lua );
void reg_spell_fake( sol::state &lua );
void reg_spell( sol::state &lua );
void reg_testing_library( sol::state &lua );
void reg_time_types( sol::state &lua );
void reg_types( sol::state &lua );
Expand Down
2 changes: 2 additions & 0 deletions src/catalua_bindings_ids.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "flag_trait.h"
#include "itype.h"
#include "json.h"
#include "magic.h"
#include "mapdata.h"
#include "martialarts.h"
#include "monfaction.h"
Expand Down Expand Up @@ -123,6 +124,7 @@ void cata::detail::reg_game_ids( sol::state &lua )
reg_id<recipe, false>( lua );
reg_id<Skill, false>( lua );
reg_id<species_type, false>( lua );
reg_id<spell_type, false>( lua );
reg_id<ter_t, true>( lua );

}
Expand Down
229 changes: 229 additions & 0 deletions src/catalua_bindings_magic.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
#ifdef LUA
#include "catalua_bindings.h"

#include "catalua.h"
// Thx Almantuxas
#include "catalua_bindings_utils.h"
#include "catalua_impl.h"
#include "catalua_log.h"
#include "catalua_luna.h"
#include "catalua_luna_doc.h"

#include "avatar.h"
#include "creature.h"
#include "magic.h"

// IN WAITING: enchantment_id, enchantments in general
void cata::detail::reg_magic( sol::state &lua )
{
reg_spell_type( lua );
reg_spell_fake( lua );
reg_spell( lua );
}

void cata::detail::reg_spell_type( sol::state &lua )
{
#define UT_CLASS spell_type
{
/* NOTE: These changes are applied to the "SpellTypeRaw" Lua obj.
* Because spell_type is bound as an ID, the actual object is
* shoved into a 'Raw' binding.
*/
DOC( "The 'raw' type for storing the information defining every spell in the game. It's not possible to cast directly from this type; check SpellSimple and Spell." );
sol::usertype<UT_CLASS> ut =
luna::new_usertype<UT_CLASS>(
lua,
luna::no_bases,
luna::no_constructor
);

// The string conversion function references this object's str_id.
luna::set_fx( ut, sol::meta_function::to_string,
[]( const UT_CLASS & id ) -> std::string {
return string_format( "%s[%s]", luna::detail::luna_traits<UT_CLASS>::name, id.id.c_str() );
} );

SET_MEMB_RO( id );
DOC( "The name of the primary effect this spell will enact." );
SET_MEMB_RO( effect_name );
DOC( "Specifics about the effect this spell will enact." );
SET_MEMB_RO( effect_str );

// Currently unclear on how to implement 'field'; it's std::optional,
// while we want to return sol::optional.
SET_MEMB_RO( field_chance );
SET_MEMB_RO( min_field_intensity );
SET_MEMB_RO( field_intensity_increment );
SET_MEMB_RO( max_field_intensity );
SET_MEMB_RO( field_intensity_variance );

SET_MEMB_RO( min_damage );
SET_MEMB_RO( damage_increment );
SET_MEMB_RO( max_damage );

SET_MEMB_RO( min_range );
SET_MEMB_RO( range_increment );
SET_MEMB_RO( max_range );

SET_MEMB_RO( min_aoe );
SET_MEMB_RO( aoe_increment );
SET_MEMB_RO( max_aoe );

SET_MEMB_RO( min_dot );
SET_MEMB_RO( dot_increment );
SET_MEMB_RO( max_dot );

SET_MEMB_RO( min_duration );
SET_MEMB_RO( duration_increment );
SET_MEMB_RO( max_duration );

// Ignoring pierce damage for now, amongst other things.

SET_MEMB_RO( base_energy_cost );
SET_MEMB_RO( energy_increment );
SET_MEMB_RO( final_energy_cost );

SET_MEMB_RO( difficulty );
SET_MEMB_RO( max_level );
SET_MEMB_RO( base_casting_time );
SET_MEMB_RO( casting_time_increment );
SET_MEMB_RO( final_casting_time );

DOC( "Other spells cast by this spell." );
luna::set_fx( ut, "additional_spells",
[]( const UT_CLASS & spid ) -> std::vector<fake_spell> {
std::vector<fake_spell> rv = spid.additional_spells; return rv;
} );

DOC( "Returns a (long) list of every spell in the game." );
SET_FX_T( get_all, const std::vector<spell_type> &() );

}
#undef UT_CLASS // #define UT_CLASS spell_type
}

void cata::detail::reg_spell_fake( sol::state &lua )
{
#define UT_CLASS fake_spell
{
DOC( "The type for basic spells. If you don't need to track XP from casting (e.g., if a spell is intended to be cast by anything *other than* a player), this is likely the appropriate type. Otherwise, see the Spell type." );
sol::usertype<UT_CLASS> ut =
luna::new_usertype<UT_CLASS>(
lua,
luna::no_bases,
luna::constructors <
UT_CLASS( spell_id sp, bool hit_self ),
UT_CLASS( spell_id sp, bool hit_self, int max_level )
> ()
);

luna::set_fx( ut, sol::meta_function::to_string,
[]( const UT_CLASS & id ) -> std::string {
return string_format( "%s[%s]", luna::detail::luna_traits<UT_CLASS>::name, id.id.c_str() );
} );

SET_MEMB_RO( id );

DOC( "Returns the defined maximum level of this SpellSimple instance, if defined. Otherwise, returns 0." );
luna::set_fx( ut, "max_level", []( UT_CLASS & sp ) -> int {
return sp.max_level.has_value() ? *sp.max_level : 0;
} );

// Perhaps this should be writeable?
SET_MEMB_RO( level );
DOC( "Whether or not the target point is *locked* to the source's location." );
SET_MEMB_N_RO( self, "force_target_source" );
DOC( "Used for enchantments; the spell's *chance* to trigger every turn." );
SET_MEMB_RO( trigger_once_in );

// TODO: Support min_level_override
luna::set_fx( ut, "cast",
[]( UT_CLASS & sp,
Creature & source,
const tripoint & target,
sol::optional<int> min_lvl_override )
{
int mlo = min_lvl_override.has_value() ? *min_lvl_override : 0;
sp.get_spell( mlo ).cast_all_effects( source, target );
}
);

DOC( "Static function: Creates and immediately casts a SimpleSpell, then returns the new spell for potential reuse. If the given tripoint is the player's location, the spell will be locked to the player. (This does not necessarily cause friendly fire!) If an integer is specified, the spell will be cast at that level." );
luna::set_fx( ut, "prompt_cast",
[]( spell_id spid,
tripoint & target,
sol::optional<int> level ) -> fake_spell
{
// This will be our return value, as well as the spell we cast.
fake_spell sp;
/* Without a specified Creature, we assume the player is the
* source.
* I'd prefer to call gapi.get_avatar, but this will do for now.
*/
avatar &avvy = get_avatar();
// If target is avatar's location, assume we want to hit self
bool hit_self = avvy.pos() == target;
sp = fake_spell( spid, hit_self );

// If a level is given, forcefully clamp to that level.
if( level.has_value() )
{
sp.level = *level;
sp.max_level = *level;
}

// Now that the spell is configured, we cast it as usual...
sp.get_spell().cast_all_effects( avvy, target );
// ...and return the spell we made for reuse.
return sp;
}
);
}
#undef UT_CLASS // #define UT_CLASS fake_spell
}

void cata::detail::reg_spell( sol::state &lua )
{
#define UT_CLASS spell
{
/* NOTE: This is the actual 'spell type', which is fully-featured and
* intended for use with players. As such, it tracks things like xp
* and level, which you may not care about.
* This functionality isn't presently important, particularly since
* there are currently no methods to access the spells in a player's
* spellbook (known_magic).
*/
DOC( "The class used for spells that *a player* knows, casts, and gains experience for using. If a given spell is not supposed to be directly cast by a player, consider using SpellSimple instead." );
sol::usertype<UT_CLASS> ut =
luna::new_usertype<UT_CLASS>(
lua,
luna::no_bases,
luna::constructors <
UT_CLASS( spell_id, int )
> ()
);

// Lets us grab the ID from the object.
SET_MEMB_N_RO( type, "id" );

SET_FX_T( xp, int() const );
SET_FX_T( gain_exp, void( int ) );
SET_FX_T( set_exp, void( int ) );
SET_FX_T( gain_levels, void( int ) );
SET_FX_T( set_level, void( int ) );
SET_FX_T( get_level, int() const );

SET_FX_T( name, std::string() const );
SET_FX_N_T( description, "desc", std::string() const );

// Present priority is basic functionality.

DOC( "Cast this spell, as well as any sub-spells." );
SET_FX_N_T( cast_all_effects, "cast", void( Creature & source, const tripoint & target ) const );
DOC( "Cast *only* this spell's main effects. Generally, cast() should be used instead." );
SET_FX_N_T( cast_spell_effect, "cast_single_effect", void( Creature & source, const tripoint & target ) const );
}
#undef UT_CLASS // #define UT_CLASS spell
}

#endif // #ifdef LUA
18 changes: 18 additions & 0 deletions src/catalua_luna.h
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,24 @@ void doc_member( sol::table &dt, sol::types<Value Class::*> && )
}

// Olanti! Curse thee for what I must do!
// NOTE: This only works with read-only properties (for now).
// It also has some pretty significant issues with intuiting the type of the
// property it's working with.
// TODO: Resolve these issues.
template<typename GetClass, typename GetVal>
void doc_member( sol::table &dt,
sol::types<sol::property_wrapper<GetVal GetClass::*, sol::detail::no_prop>> && )
{
dt[KEY_MEMBER_TYPE] = MEMBER_IS_VAR;
add_comment( dt, KEY_MEMBER_COMMENT );
/* TODO: Why does this work HERE but not when it's run in doc_value_impl?!
* This may prove problematic in the future. Implementing luna_traits might
* help avert it for certain types, but I would much prefer the root problem
* solved.
*/
dt[KEY_MEMBER_VARIABLE_TYPE] = doc_value( sol::types<std::remove_const<GetVal>>() );
}

template<typename Class, typename Value>
void doc_member( sol::table &dt, sol::types<sol::readonly_wrapper<Value Class::*>> && )
{
Expand Down
6 changes: 6 additions & 0 deletions src/catalua_luna_doc.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ class recipe;
class Skill;
class SkillLevel;
class SkillLevelMap;
class spell_type;
class spell;
class time_duration;
class time_point;
class tinymap;
Expand All @@ -52,6 +54,7 @@ struct body_part_type;
struct damage_instance;
struct damage_unit;
struct dealt_damage_instance;
struct fake_spell;
struct field_type;
struct mutation_branch;
struct npc_opinion;
Expand Down Expand Up @@ -123,6 +126,8 @@ LUNA_VAL( point, "Point" );
LUNA_VAL( query_popup, "QueryPopup" );
LUNA_VAL( SkillLevelMap, "SkillLevelMap" );
LUNA_VAL( SkillLevel, "SkillLevel" );
LUNA_VAL( fake_spell, "SpellSimple" )
LUNA_VAL( spell, "Spell" )
LUNA_VAL( time_duration, "TimeDuration" );
LUNA_VAL( time_point, "TimePoint" );
LUNA_VAL( tinymap, "Tinymap" );
Expand Down Expand Up @@ -154,6 +159,7 @@ LUNA_ID( mutation_category_trait, "MutationCategoryTrait" )
LUNA_ID( recipe, "Recipe" )
LUNA_ID( Skill, "Skill" )
LUNA_ID( species_type, "SpeciesType" )
LUNA_ID( spell_type, "SpellType" )
LUNA_ID( ter_t, "Ter" )

// Enums
Expand Down
15 changes: 14 additions & 1 deletion src/magic.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <vector>

#include "bodypart.h"
#include "catalua_type_operators.h"
#include "damage.h"
#include "enum_bitset.h"
#include "event_bus.h"
Expand Down Expand Up @@ -118,6 +119,11 @@ struct fake_spell {

bool operator==( const fake_spell &rhs )const;

// Borrowed from LUA_TYPE_OPS, catalua_type_operators.h
inline bool operator<( const fake_spell &rhs ) const {
return ( id ) < rhs.id;
}

void load( const JsonObject &jo );
void serialize( JsonOut &json ) const;
void deserialize( JsonIn &jsin );
Expand Down Expand Up @@ -284,14 +290,19 @@ class spell_type
static void check_consistency();
static void reset_all();
bool is_valid() const;

LUA_TYPE_OPS( spell_type, id );
};

class spell
{
public:
// Here for Lua reasons.
spell_id type;

private:
friend class spell_events;
// basic spell data
spell_id type;

// once you accumulate enough exp you level the spell
int experience = 0;
Expand Down Expand Up @@ -448,6 +459,8 @@ class spell
// picks a random valid tripoint from @area
std::optional<tripoint> random_valid_target( const Creature &caster,
const tripoint &caster_pos ) const;

LUA_TYPE_OPS( spell, type );
};

class known_magic
Expand Down
1 change: 1 addition & 0 deletions src/string_id_null_ids.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ MAKE_CLASS_NULL_ID( overmap_special, "" )
MAKE_CLASS_NULL_ID( recipe, "null" )
MAKE_CLASS_NULL_ID( SkillDisplayType, "none" )
MAKE_CLASS_NULL_ID( Skill, "none" )
MAKE_CLASS_NULL_ID( spell_type, "spell_none" )
MAKE_CLASS_NULL_ID( ter_furn_transform, "null" )
MAKE_CLASS_NULL_ID( translation, "null" )
MAKE_CLASS_NULL_ID( VehicleGroup, "null" )
Expand Down

0 comments on commit a4dde2f

Please sign in to comment.