Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for musical instruments (scale off perception) #11468

Merged
merged 9 commits into from
Mar 22, 2015
44 changes: 44 additions & 0 deletions data/json/items/tool_armor.json
Original file line number Diff line number Diff line change
Expand Up @@ -2119,5 +2119,49 @@
"flags" : ["VARSIZE", "SKINTIGHT"],
"coverage" : 60,
"material_thickness" : 3
},
{
"type" : "TOOL_ARMOR",
"id" : "harmonica_holder",
"name" : "harmonica with a holder",
"name_plural": "harmonicas with holders",
"category" : "tools",
"weight" : 200,
"color" : "brown",
"covers" : ["HEAD"],
"to_hit" : 0,
"max_charges": 1,
"initial_charges": 1,
"charges_per_use": 0,
"turns_per_charge": 0,
"ammo": "NULL",
"use_action": {
"type": "musical_instrument",
"moves_cost": 10,
"volume": 10,
"fun": -5,
"fun_bonus": 2,
"description_frequency": 100,
"descriptions": [
"You produce a happy tune on your harmonica.",
"You whistle on your harmonica."
]
},
"revert_to": "null",
"storage" : 0,
"symbol" : "-",
"description" : "A harmonica with a holder, so that you can play it without a free hand.",
"price" : 1000,
"material" : ["steel", "null"],
"volume" : 1,
"cutting" : 0,
"warmth" : 0,
"phase" : "solid",
"environmental_protection" : 0,
"encumbrance" : 0,
"bashing" : -5,
"flags" : ["STRAPPED"],
"coverage" : 0,
"material_thickness" : 1
}
]
2 changes: 2 additions & 0 deletions src/item_factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1353,6 +1353,8 @@ use_function Item_factory::use_from_object(JsonObject obj)
return load_actor<firestarter_actor>( obj );
} else if( type == "extended_firestarter" ) {
return load_actor<extended_firestarter_actor>( obj );
} else if( type == "musical_instrument" ) {
return load_actor<musical_instrument_actor>( obj );
} else {
obj.throw_error( "unknown use_action", "type" );
return use_function(); // line above throws, but the compiler does not know \-:
Expand Down
106 changes: 106 additions & 0 deletions src/iuse_actor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -917,3 +917,109 @@ bool extended_firestarter_actor::can_use( const player* p, const item* it, bool

return true;
}

iuse_actor *musical_instrument_actor::clone() const
{
return new musical_instrument_actor(*this);
}

void musical_instrument_actor::load( JsonObject &obj )
{
moves_cost = obj.get_int( "moves_cost", 25 );
volume = obj.get_int( "volume", 99 ); // Make it huge to alert the modder
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make it huge to alert the modder

I don't get this. If it's mandatory, why not omit the default value and let the json parser throw an error when it's missing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't know parser drops errors on missing fields with no defaults.

fun = obj.get_int( "fun", -100 ); // Likewise
fun_bonus = obj.get_int( "fun_bonus", 0 );
description_frequency = obj.get_int( "description_frequency", 1 );

if( obj.has_array( "descriptions" ) ) {
JsonArray jarr = obj.get_array( "descriptions" );
while( jarr.has_more() ) {
const auto desc = jarr.next_string();
descriptions.push_back( desc );
}
} else {
descriptions.push_back( "You produce buggy chirping on your %s" );
}
}

long musical_instrument_actor::use( player *p, item *it, bool t, point ) const
{
if( p == nullptr ) {
// No haunted pianos here!
it->active = false;
return 0;
}

if( p->is_underwater() ) {
p->add_msg_if_player( m_bad, _("You can't play music underwater") );
it->active = false;
return 0;
}

if( !t ) {
// TODO: Make the player stop playing music when paralyzed/choking
if( it->active || p->has_effect("sleep") ) {
p->add_msg_if_player( _("You stop playing your %s"), it->display_name().c_str() );
it->active = false;
return 0;
}
}

// Check for worn or wielded - no "floating"/bionic instruments for now
// TODO: Distinguish instruments played with hands and with mouth, consider encumbrance
const int inv_pos = p->get_item_position( it );
if( inv_pos > 0 || inv_pos == INT_MIN ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inv_pos == 0 is inventory, too.

p->add_msg_if_player( m_bad, _("You need to hold or wear %s to play it"), it->display_name().c_str() );
it->active = false;
return 0;
}

// To prevent players from getting into a soft-lock and starving to death
// How often does the player get to act
const double actions_per_turn = 100.0 / p->get_speed();
// How much does it cost (per player action) to play this instrument continuously
const double moves_per_action = moves_cost * actions_per_turn;
if( p->get_speed() / 2.0 < moves_per_action ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can shorten this to p->get_speed() * p->get_speed() < moves_cost * 200

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But won't it be harder to understand?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very subjective thing, your view is as good as mine.

However, if I wanted to know whether a certain character (given speed) can play that instrument, I'd have to do track the variables, which are not used anywhere else, so they could be removed. But they help understanding the code, so it's fine.

Why actually this condition? It looks strange.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's there to make the player bail out of playing the instrument if half of the turn would be used up just on playing the instrument. Without such a safeguard (or with lower proportions), player could be locked up trying to play the instrument while dangerous things happen around.

I think I'll change it to speed penalty, because it's safer, more obvious and doesn't penalize slow characters.

p->add_msg_if_player( m_bad, _("You feel too weak to play your %s"), it->display_name().c_str() );
it->active = false;
return 0;
}

// We can play the music now
if( !it->active ) {
p->add_msg_if_player( m_good, _("You start playing your %s"), it->display_name().c_str() );
it->active = true;
}

p->moves -= moves_cost;
std::string desc = "";
const int morale_effect = fun + fun_bonus * p->per_cur;
if( morale_effect >= 0 && int(calendar::turn) % description_frequency == 0 ) {
const size_t desc_index = rng( 0, descriptions.size() - 1 );
desc = _(descriptions[ desc_index ].c_str());
} else if( morale_effect < 0 && int(calendar::turn) % 10 ) {
// No musical skills = possible morale penalty
desc = _("You produce an annoying sound");
sounds::ambient_sound( p->posx(), p->posy(), volume, desc );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That prints the sounds twice, it's printed below again.

}

sounds::ambient_sound( p->posx(), p->posy(), volume, desc );

if( !p->has_effect( "music" ) && !p->can_hear( p->pos(), volume ) ) {
p->add_effect( "music", 1 );
const int sign = morale_effect > 0 ? 1 : -1;
p->add_morale( MORALE_MUSIC, sign, morale_effect, 5, 2 );
}

return 0;
}

bool musical_instrument_actor::can_use( const player *p, const item*, bool, const point& ) const
{
// TODO (maybe): Mouth encumbrance? Smoke? Lack of arms? Hand encumbrance?
if( p->is_underwater() ) {
return false;
}

return true;
}
39 changes: 39 additions & 0 deletions src/iuse_actor.h
Original file line number Diff line number Diff line change
Expand Up @@ -390,4 +390,43 @@ class extended_firestarter_actor : public firestarter_actor
virtual iuse_actor *clone() const;
};

/**
* Plays music
*/
class musical_instrument_actor : public iuse_actor
{
public:
/**
* Move cost to play (per turn)
*/
int moves_cost;
/**
* Volume of the music played
*/
int volume;
/**
* Base morale bonus/penalty
*/
int fun;
/**
* Morale bonus scaling (off current perception)
*/
int fun_bonus;
/**
* List of sound descriptions
*/
std::vector< std::string > descriptions;
/**
* Display description once per this many turns
*/
int description_frequency;

musical_instrument_actor() : iuse_actor(), moves_cost( 25 ) { }
virtual ~musical_instrument_actor() = default;
virtual void load( JsonObject &jo );
virtual long use( player*, item*, bool, point ) const;
virtual bool can_use( const player*, const item*, bool, const point& ) const;
virtual iuse_actor *clone() const;
};

#endif