Depends on what you mean by possible, and how picky you are about implementation. You can do this in vanilla NWN by simply adding a feat to the feats 2da, and then putting code ingame that checks for the feat and applies a bonus if it's found. You'll have to reapply bonuses like that every time a player is raised/rezzed, though, so you'll need an 'on resurrection' script that you fire any time a player is raised, a sort of pseudoevent - I'll post our subraces include and 'death process' script for you.
If, however, you want a bonus that stacks outside of the 50 skills cap, you would need engine hacks.
As for making skills only count against reptilians, you would need either script edits or some fairly extraordinary engine hacks to pull it off. If checked in a scriptable event, it's fairly easy - you would just NOT apply the feat to the character's base skill, and add it in where appropriate. With spot, though, there is no player perception event, so you would need hacks.
Here's some excerpts from our subrace include, to show you how some effects are applied on entry/death. You'll see that we use it for class bonuses as well, not just subraces, when appropriate.
int GetSubraceSR (object oPC=OBJECT_SELF) {
int nSR = GetLocalInt(oPC, "SubraceSR");
if (!nSR) {
struct SubraceInfo sub = GetSubraceInfo(oPC);
nSR = sub.sr;
SetLocalInt(oPC, "SubraceSR", (nSR == 0 ? -1 : nSR));
}
if (nSR < -1)
nSR = GetHitDiceIncludingLLs(oPC) - ((nSR + 2) * 5);
if (nSR < 0)
nSR = 0;
int nLevel = GetLevelIncludingLLs(class_TYPE_DRAGONDISCIPLE, oPC);
if (nLevel > 19 && GetLocalInt(oPC, "RDDLevel") >= 20) {
nLevel = 20 + nLevel;
if (nLevel > nSR)
nSR = nLevel;
}
if (GetLLControlclass(oPC) == class_TYPE_MONK) {
int nLevel = 10 + ((GetLevelIncludingLLs(class_TYPE_MONK, oPC) * 7) / 6);
if (nLevel > nSR)
nSR = nLevel;
}
nSR += GetKnowsFeatChain(FEAT_EPIC_IMPROVED_SPELL_RESISTANCE_1, oPC) * 2;
return nSR;
}
/* Apply vulnerabilities which can be reduced by dragon's blood */
void ApplySubraceDamageVulnerabilities (object oPC, int nSubID) {
int bBlood = GetLocalInt(oPC, "DragonBlood");
int nDamType, nDamVuln = 0;
switch (nSubID) {
case SUBRACE_DRIDER:
nDamType = DAMAGE_TYPE_FIRE;
nDamVuln = (bBlood ? 10 : 50);
break;
case SUBRACE_DWARF_AZER:
nDamType = DAMAGE_TYPE_COLD;
nDamVuln = (bBlood ? 10 : 50);
break;
case SUBRACE_HALF_DRAGON_RED:
nDamType = DAMAGE_TYPE_COLD;
nDamVuln = (bBlood ? 25 : 150);
break;
case SUBRACE_HALF_DRAGON_BLUE:
nDamType = DAMAGE_TYPE_ACID;
nDamVuln = (bBlood ? 25 : 150);
break;
case SUBRACE_HALF_DRAGON_GREEN:
nDamType = DAMAGE_TYPE_ELECTRICAL;
nDamVuln = (bBlood ? 25 : 150);
break;
case SUBRACE_HALF_DRAGON_BLACK:
nDamType = DAMAGE_TYPE_COLD;
nDamVuln = (bBlood ? 25 : 150);
break;
case SUBRACE_HALF_DRAGON_WHITE:
nDamType = DAMAGE_TYPE_FIRE;
nDamVuln = (bBlood ? 25 : 150);
break;
case SUBRACE_TROLL:
nDamType = DAMAGE_TYPE_ACID | DAMAGE_TYPE_FIRE;
nDamVuln = (bBlood ? 20 : 75);
break;
}
if (nDamVuln < 1)
return;
int i;
effect eVuln;
for (i = 0; i < 12; i++) {
if (nDamType & (1 << i)) {
eVuln = EffectDamageImmunityDecrease(1 << i, nDamVuln);
eVuln = SupernaturalEffect(eVuln);
ApplyEffectToObject(DURATION_TYPE_PERMANENT, eVuln, oPC);
}
}
}
void ApplySubraceSize (object oPC) {
int nRDDType, nCurrentWings;
int nSub = GetLocalInt(oPC, "SubraceID");
if (GetLocalInt(oPC, "RDDLevel") >= 30 ||
nSub == SUBRACE_TREANT ||
nSub == SUBRACE_HALF_CYCLOPS ||
nSub == SUBRACE_HALF_GIANT_CLOUD) {
SetCreatureSize(oPC, CREATURE_SIZE_LARGE);
}
if ((nRDDType = GetLocalInt(oPC, "RDDType")) > 0 &&
(nCurrentWings = GetCreatureWingType(oPC)) != 0) {
int nWings = -1;
switch (nRDDType) {
case SUBRACE_HALF_DRAGON_BLACK: nWings = WING_MODEL_DRAGON_BLACK; break;
case SUBRACE_HALF_DRAGON_BLUE: nWings = WING_MODEL_DRAGON_BLUE; break;
case SUBRACE_HALF_DRAGON_GREEN: nWings = WING_MODEL_DRAGON_GREEN; break;
case SUBRACE_HALF_DRAGON_RED: nWings = WING_MODEL_DRAGON_RED; break;
case SUBRACE_HALF_DRAGON_WHITE: nWings = WING_MODEL_DRAGON_WHITE; break;
}
if (nCurrentWings != nWings &&
(nCurrentWings == WING_MODEL_DRAGON_BLACK ||
nCurrentWings == WING_MODEL_DRAGON_BLUE ||
nCurrentWings == WING_MODEL_DRAGON_GREEN ||
nCurrentWings == WING_MODEL_DRAGON_RED ||
nCurrentWings == WING_MODEL_DRAGON_WHITE))
DelayCommand(18.0, SetCreatureWingType(nWings, oPC));
}
}
void ApplySubraceEffects (object oPC) {
string sEffects = GetLocalString(oPC, "SubraceEffects");
SetLocalInt(oPC, "sne_applied", 1);
if (sEffects == "") {
struct SubraceInfo sub = GetSubraceInfo(oPC, 2);
if ((sub.id == SUBRACE_HALF_DRAGON_WHITE && GetQuasiclass(oPC) == QUASIclass_BLOODFIRE_MAGE) ||
(sub.id == SUBRACE_HALF_DRAGON_GREEN && GetQuasiclass(oPC) == QUASIclass_DRAGONSTORM_MAGE))
sEffects = "";
else
sEffects = sub.effects;
SetLocalInt(oPC, "SubraceSR", (sub.sr == 0 ? -1 : sub.sr));
SetLocalString(oPC, "SubraceEffects", (sEffects == "" ? "NONE" : sEffects));
if (sub.moverate > 0)
SetLocalInt(oPC, "SubraceMovementRate", sub.moverate);
else
SetLocalInt(oPC, "SubraceMovementRate", MOVEMENT_RATE_NORMAL);
}
int nRate = GetBaseMovementRate(oPC);
if (nRate == MOVEMENT_RATE_NORMAL)
SetMovementRate(oPC, MOVEMENT_RATE_PC);
else
SetMovementRate(oPC, nRate);
if (sEffects == "NONE")
sEffects = "";
/* 21 Fighter needs at least 1% concealment to trigger their innate Parry conceal */
if (GetLevelByclass(class_TYPE_FIGHTER, oPC) >= 21)
sEffects += " 72,1";
/* 15 Shadowdancer grants Imm: Blindness */
if (GetLevelByclass(class_TYPE_SHADOWDANCER, oPC) >= 15)
sEffects += " 15,7";
/* 20 RDD and ET5 grant TS and Imm: Sneak Attack */
if (GetLevelIncludingLLs(class_TYPE_DRAGONDISCIPLE, oPC) >= 20 &&
GetLocalInt(oPC, "RDDLevel") >= 20)
sEffects += " 64 15,30";
/* 25 CoT grants True Seeing */
int nLevel = GetLevelIncludingLLs(class_TYPE_DIVINECHAMPION, oPC);
if (nLevel >= 25)
sEffects += " 64";
/* 45 CoT grants Imm: Stun/Daze/Charm/Confuse */
if (nLevel >= 45)
sEffects += " 15,12 15,14 15,16 15,18";
/* Dwarven Defender grants (StrMod) natural AC in LLs, with RDD AC
* subtracted (minimum cap of +16)
*/
int nControl = GetLLControlclass(oPC);
int i, nPhysImm = 0;
if (nControl == class_TYPE_DWARVENDEFENDER) {
int nAC = GetAbilityModifier(ABILITY_STRENGTH, oPC);
int nMax = 20 - (GetLevelByclass(class_TYPE_DRAGONDISCIPLE, oPC) / 3);
if (nMax < 16)
nMax = 16;
sEffects += " 46," + IntToString(nAC > nMax ? nMax : nAC) + ",1";
} else if (nControl == class_TYPE_MONK) {
nPhysImm += (GetAbilityModifier(ABILITY_STRENGTH, oPC)/2);
}
/* Legendary Battle Hardening */
for (i = 2514; i > 2510; i--) {
if (GetHasFeat(i, oPC)) {
nPhysImm += ((i-2510) * 5);
break;
}
}
if (nPhysImm > 0) {
string sPhysImm = IntToString(nPhysImm);
sEffects += " 44,1," + sPhysImm + " 44,2," + sPhysImm + " 44,4," + sPhysImm;
}
/* Legendary Skill Focus grants +20 to the selected skill */
for (i = 0; i < 27; i++) {
if (GetHasFeat(GetSkillLegendaryFocusFeat(i), oPC))
sEffects += " 54," + IntToString(i) + ",20";
}
/* Legendary Battle Awareness grants Imm: Sneak Attack */
if (GetHasFeat(HGFEAT_LEG_BATTLE_AWARENESS, oPC))
sEffects += " 15,30";
int nSR = GetSubraceSR(oPC);
if (nSR > 0)
sEffects += " 52," + IntToString(nSR);
int nArtificial = GetLocalInt(oPC, "ArtificialLevel");
if (nArtificial > 0) {
int nACPenalty = 1 + ((GetLootable(oPC) - nArtificial) / 3);
sEffects += " 47," + IntToString(nACPenalty) + ",0";
}
if (GetHasFeat(HGFEAT_PAR_INHERENT_BREATHING, oPC))
sEffects += " 73," + IntToString(HGEFFECT_SURVIVAL_BREATHE_WATER) + " 73,437 44,32,20";//drown imm, cold 20%
if (GetHasFeat(HGFEAT_PAR_INHERENT_LEVITATION, oPC))
sEffects += " 73," + IntToString(HGEFFECT_SURVIVAL_LEVITATION) + " 44,128,20";
if (GetHasFeat(HGFEAT_PAR_INHERENT_PASSWALL, oPC))
sEffects += " 73," + IntToString(HGEFFECT_SURVIVAL_PASSWALL) + " 44,16,20";
if (GetHasFeat(HGFEAT_PAR_INHERENT_FIREWALK, oPC))
sEffects += " 73," + IntToString(HGEFFECT_SURVIVAL_FIREWALK) + " 44,256,20";
/* chop off a leading space if there is one */
if (GetStringLeft(sEffects, 1) == " ")
sEffects = GetStringRight(sEffects, GetStringLength(sEffects) - 1);
ApplyDynamicEffects(sEffects, oPC, -2, -1.0, SUBTYPE_SUPERNATURAL);
ApplySubraceDamageVulnerabilities(oPC, GetLocalInt(oPC, "SubraceID"));
/* Paragon Skills */
string sSkills = GetLocalString(oPC, "ParagonSkills");//format "0_21 7_6 8_33"
string sEffects2;
if (sSkills != "") {
struct SubString ss;
ss.rest = sSkills;
int nSkill, nScore, nPos;
while (ss.rest != "") {
ss = GetFirstSubString(ss.rest);
nPos = FindSubString(ss.first, "_");
nScore = StringToInt(GetStringRight(ss.first, GetStringLength(ss.first) - (nPos + 1)));
nSkill = StringToInt(GetStringLeft(ss.first, nPos));
sEffects2 += " 54," + IntToString(nSkill) + "," + IntToString(nScore);
}
}
if (GetStringLeft(sEffects2, 1) == " ")
sEffects2 = GetStringRight(sEffects2, GetStringLength(sEffects2) - 1);
ApplyDynamicEffects(sEffects2, oPC, HGEFFECT_PARAGON_SKILL_BONUS, -1.0, SUBTYPE_SUPERNATURAL);
/* Accursed Pariahs receive 5% regeneration 2x/round */
if (GetIsQuasiclass(QUASIclass_ACCURSED_PARIAH, oPC))
ApplyEffectToObject(DURATION_TYPE_PERMANENT,
SupernaturalEffect(EffectRegenerate(GetMaxHitPoints(oPC) / 20, 3.0)), oPC);
int nArtifact = GetLocalInt(oPC, "StatArtifactUsed");
if (nArtifact)
ApplyArtifactEffects(nArtifact, oPC);
if (ApplyAccomplishmentBonuses(oPC) == HGSPELL_IMMUNITY_DAMAGE_BONUS_PRELL) {
int nAdj = GetXPAdjustment(oPC, TRUE);
if (nAdj != 0)
SetLocalInt(oPC, "XPAdjustment", nAdj);
else
DeleteLocalInt(oPC, "XPAdjustment");
}
effect eSurv;
int nMonkSurvival = GetLocalInt(oPC, "MonkSurvival");
if (nMonkSurvival) {
int nMS, nSurvImm;
for (nMS = 1; nMS < 16; nMS *= 2) {
if (nMS & nMonkSurvival) {
nSurvImm = GetMonkSurvivalImmunity(nMS);
if (!GetHasSpellImmunity(nSurvImm, oPC)) {
eSurv = SupernaturalEffect(EffectSpellImmunity(nSurvImm));
ApplyEffectToObject(DURATION_TYPE_PERMANENT, eSurv, oPC);
}
}
}
}
}
void CheckSubraceEffects (object oPC) {
if (GetLocalInt(oPC, "sne_applied") > 0)
return;
object oCreator = GetLocalObject(GetModule(), "subcreator");
AssignCommand(oCreator, ApplySubraceEffects(oPC));
}
void OnSubraceEnter (object oPC) {
string sPath = GetLocalString(oPC, "BicFilePath");
if (sPath == "") {
sPath = GetBicFilePath(oPC);
if (sPath != "") {
SetLocalString(oPC, "BicFilePath", sPath);
if (!GetCommandable(oPC))
SetCommandable(TRUE, oPC);
} else {
ExportSingleCharacter(oPC);
FloatingTextStringOnCreature("File System Error! Attempting to fix, please wait. " +
"If you get this message more than 3 times in a row, please contact a DM.",
oPC, FALSE);
if (GetCommandable(oPC))
SetCommandable(FALSE, oPC);
DelayCommand(5.0f,
FloatingTextStringOnCreature("Accents or other unusual symbols in character names " +
"prevent the server from locating your character for subrace and other edits.",
oPC, FALSE));
/* if the bic hasn't updated yet, try again at an extended delay */
DelayCommand(10.0f, OnSubraceEnter(oPC));
return;
}
}
DoHardCoreChange(oPC, sPath);
ApplySubraceEdits(oPC);
if (!GetLocalInt(oPC, "SubraceID"))
GetSubraceInfo(oPC);
ApplySubraceSize(oPC);
CheckSubraceEffects(oPC);
SetLocalInt(oPC, "XPAdjustment", GetXPAdjustment(oPC));
}
void OnSubraceLeave (object oPC) {
string sScript = "";
if (GetLootable(oPC) <= 1 && GetLocalInt(oPC, "SubraceID") < -1)
sScript += "/Subrace = q!INVALID - TAG NEEDED!; ";
if (sScript != "") {
string sBic = GetLocalString(oPC, "BicFilePath");
if (sBic != "") {
WriteTimestampedLogEntry("LETO : " + sBic + " : " + sScript);
LetoScript("%char= q!" + sBic + "!; " + sScript + "%char = '>'; close %char; ");
}
}
}
void OnSubraceLevelUp (object oPC) {
int nAdj = GetXPAdjustment(oPC, TRUE);
if (nAdj != 0)
SetLocalInt(oPC, "XPAdjustment", nAdj);
else
DeleteLocalInt(oPC, "XPAdjustment");
}
int GetBaseRaceFavoredclass (object oPC=OBJECT_SELF) {
switch (GetRacialType(oPC)) {
case RACIAL_TYPE_DWARF: return class_TYPE_FIGHTER;
case RACIAL_TYPE_ELF: return class_TYPE_WIZARD;
case RACIAL_TYPE_GNOME: return (GetLevelByclass(class_TYPE_BARD, oPC) > 0 ? class_TYPE_BARD : class_TYPE_WIZARD);
case RACIAL_TYPE_HALFLING: return class_TYPE_ROGUE;
case RACIAL_TYPE_HALFORC: return class_TYPE_BARBARIAN;
case RACIAL_TYPE_HALFELF:
case RACIAL_TYPE_HUMAN: return class_TYPE_INVALID;
case RACIAL_TYPE_ABERRATION: return class_TYPE_ABERRATION;
case RACIAL_TYPE_ANIMAL: return class_TYPE_ANIMAL;
case RACIAL_TYPE_BEAST: return class_TYPE_BEAST;
case RACIAL_TYPE_CONSTRUCT: return class_TYPE_CONSTRUCT;
case RACIAL_TYPE_DRAGON: return class_TYPE_DRAGON;
case RACIAL_TYPE_ELEMENTAL: return class_TYPE_ELEMENTAL;
case RACIAL_TYPE_FEY: return class_TYPE_FEY;
case RACIAL_TYPE_GIANT: return class_TYPE_GIANT;
case RACIAL_TYPE_MAGICAL_BEAST: return class_TYPE_MAGICAL_BEAST;
case RACIAL_TYPE_OOZE: return class_TYPE_OOZE;
case RACIAL_TYPE_OUTSIDER: return class_TYPE_OUTSIDER;
case RACIAL_TYPE_SHAPECHANGER: return class_TYPE_SHAPECHANGER;
case RACIAL_TYPE_UNDEAD: return class_TYPE_UNDEAD;
case RACIAL_TYPE_VERMIN: return class_TYPE_VERMIN;
}
return class_TYPE_COMMONER;
}
int GetIsBonusclass (int nclass, object oPC=OBJECT_SELF) {
int nBonus = 0, nFavored = GetLocalInt(oPC, "SubraceFavored");
if (nFavored & SUBRACE_FAVORED_BONUS) {
if ((nFavored & SUBRACE_FAVORED_BONUS_1) && nclass == (nFavored & 0xFF) - 1)
nBonus++;
if ((nFavored & SUBRACE_FAVORED_BONUS_2) && nclass == ((nFavored >> & 0xFF) - 1)
nBonus++;
if ((nFavored & SUBRACE_FAVORED_BONUS_3) && nclass == ((nFavored >> 16) & 0xFF) - 1)
nBonus++;
}
return nBonus;
}
int GetIsFavoredclass (int nclass, object oPC=OBJECT_SELF, int bHighest=FALSE) {
int nFavored = GetLocalInt(oPC, "SubraceFavored");
if (nFavored == 0) {
struct SubraceInfo sub = GetSubraceInfo(oPC);
if ((nFavored = sub.favored) == 0)
nFavored = -1;
SetLocalInt(oPC, "SubraceFavored", nFavored);
}
/* if no favored class is set or if -1 was specified, use the base race's */
if (nFavored < 0 || (nFavored & SUBRACE_FAVORED_BASE)) {
int nBaseFavored = GetBaseRaceFavoredclass(oPC);
/* if class_TYPE_INVALID was specified, any class can be favored */
if (nBaseFavored == class_TYPE_INVALID && bHighest)
return TRUE;
if (nFavored < 0 || nclass == nBaseFavored)
return (nclass == nBaseFavored);
}
/* 0xFFFFFF means all classes are always favored classes */
if (nFavored == 0xFFFFFF)
return TRUE;
/* if SUBRACE_FAVORED_HIGHEST is set, the highest-level class is always favored */
if (bHighest && (nFavored & SUBRACE_FAVORED_HIGHEST))
return TRUE;
/* The value of the subrace data is (bytewise) FABC, where F is the flags
* byte, and A B and C are favored classes (in the form class constant + 1).
*
* If SUBRACE_FAVORED_GENDER is set, C applies to both genders, A applies to
* female characters only, and B applies to male characters only.
*
* Otherwise, A B and C apply to all characters.
*/
if (nFavored & SUBRACE_FAVORED_GENDER) {
if (nclass == (nFavored & 0xFF) - 1)
return TRUE;
if (GetGender(oPC) == GENDER_FEMALE)
return (nclass == ((nFavored >> 16) & 0xFF) - 1);
else
return (nclass == ((nFavored >> & 0xFF) - 1);
}
return (nclass == (nFavored & 0xFF) - 1 ||
nclass == ((nFavored >> & 0xFF) - 1 ||
nclass == ((nFavored >> 16) & 0xFF) - 1);
}
int GetXPAdjustment (object oPC, int bTemporary=FALSE) {
int i, nclass, nLevel, nFav, nHighestclass, nHighestLevel = 0, nclasses = 0, nAdj = 0;
/* first find their highest-level class */
for (i = 1; i <= 3; i++) {
nclass = GetclassByPosition(i, oPC);
if (nclass < 0 || nclass > class_TYPE_WIZARD)
continue;
if ((nLevel = GetLevelByPosition(i, oPC)) > nHighestLevel) {
nHighestclass = nclass;
nHighestLevel = nLevel;
}
}
nHighestLevel = 0;
for (i = 1; i <= 3; i++) {
if ((nclass = GetclassByPosition(i, oPC)) < 0 || nclass == class_TYPE_INVALID)
continue;
if ((nFav = GetIsFavoredclass(nclass, oPC, (nclass == nHighestclass)))) {
if ((nFav = GetIsBonusclass(nclass, oPC)) > 0)
nAdj += nFav * 10;
} else if (nclass <= class_TYPE_WIZARD) {
nclasses |= (1 << i);
if ((nLevel = GetLevelByPosition(i, oPC)) > nHighestLevel)
nHighestLevel = nLevel;
}
}
for (i = 1; i <= 3; i++) {
if (!(nclasses & (1 << i)))
continue;
if (GetLevelByPosition(i, oPC) < nHighestLevel - 1)
nAdj -= 20;
}
if (bTemporary && GetHasSpellImmunity(HGSPELL_IMMUNITY_DAMAGE_BONUS_PRELL, oPC))
nAdj += 5;
if (nAdj < 0 && GetLocalInt(oPC, "NoXPPenalty"))
return 0;
return (nAdj > 30 ? 30 : (nAdj < -40 ? -40 : nAdj));
}
Funky
Modifié par FunkySwerve, 08 août 2011 - 04:25 .