forked from mirror/openmw-tes3mp
Merge branch 'master' into savedgame
Conflicts: apps/openmw/mwmechanics/actors.cpp apps/openmw/mwworld/worldimp.cpp files/settings-default.cfgactorid
commit
030c733e2d
@ -0,0 +1,449 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/ucontext.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/ptrace.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <SDL_messagebox.h>
|
||||
|
||||
#ifdef __linux__
|
||||
#include <sys/prctl.h>
|
||||
#ifndef PR_SET_PTRACER
|
||||
#define PR_SET_PTRACER 0x59616d61
|
||||
#endif
|
||||
#elif defined (__APPLE__)
|
||||
#include <signal.h>
|
||||
#endif
|
||||
|
||||
|
||||
static const char crash_switch[] = "--cc-handle-crash";
|
||||
|
||||
static const char fatal_err[] = "\n\n*** Fatal Error ***\n";
|
||||
static const char pipe_err[] = "!!! Failed to create pipe\n";
|
||||
static const char fork_err[] = "!!! Failed to fork debug process\n";
|
||||
static const char exec_err[] = "!!! Failed to exec debug process\n";
|
||||
|
||||
static char argv0[PATH_MAX];
|
||||
|
||||
static char altstack[SIGSTKSZ];
|
||||
|
||||
|
||||
static struct {
|
||||
int signum;
|
||||
pid_t pid;
|
||||
int has_siginfo;
|
||||
siginfo_t siginfo;
|
||||
char buf[1024];
|
||||
} crash_info;
|
||||
|
||||
|
||||
static const struct {
|
||||
const char *name;
|
||||
int signum;
|
||||
} signals[] = {
|
||||
{ "Segmentation fault", SIGSEGV },
|
||||
{ "Illegal instruction", SIGILL },
|
||||
{ "FPU exception", SIGFPE },
|
||||
{ "System BUS error", SIGBUS },
|
||||
{ NULL, 0 }
|
||||
};
|
||||
|
||||
static const struct {
|
||||
int code;
|
||||
const char *name;
|
||||
} sigill_codes[] = {
|
||||
#ifndef __FreeBSD__
|
||||
{ ILL_ILLOPC, "Illegal opcode" },
|
||||
{ ILL_ILLOPN, "Illegal operand" },
|
||||
{ ILL_ILLADR, "Illegal addressing mode" },
|
||||
{ ILL_ILLTRP, "Illegal trap" },
|
||||
{ ILL_PRVOPC, "Privileged opcode" },
|
||||
{ ILL_PRVREG, "Privileged register" },
|
||||
{ ILL_COPROC, "Coprocessor error" },
|
||||
{ ILL_BADSTK, "Internal stack error" },
|
||||
#endif
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
static const struct {
|
||||
int code;
|
||||
const char *name;
|
||||
} sigfpe_codes[] = {
|
||||
{ FPE_INTDIV, "Integer divide by zero" },
|
||||
{ FPE_INTOVF, "Integer overflow" },
|
||||
{ FPE_FLTDIV, "Floating point divide by zero" },
|
||||
{ FPE_FLTOVF, "Floating point overflow" },
|
||||
{ FPE_FLTUND, "Floating point underflow" },
|
||||
{ FPE_FLTRES, "Floating point inexact result" },
|
||||
{ FPE_FLTINV, "Floating point invalid operation" },
|
||||
{ FPE_FLTSUB, "Subscript out of range" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
static const struct {
|
||||
int code;
|
||||
const char *name;
|
||||
} sigsegv_codes[] = {
|
||||
#ifndef __FreeBSD__
|
||||
{ SEGV_MAPERR, "Address not mapped to object" },
|
||||
{ SEGV_ACCERR, "Invalid permissions for mapped object" },
|
||||
#endif
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
static const struct {
|
||||
int code;
|
||||
const char *name;
|
||||
} sigbus_codes[] = {
|
||||
#ifndef __FreeBSD__
|
||||
{ BUS_ADRALN, "Invalid address alignment" },
|
||||
{ BUS_ADRERR, "Non-existent physical address" },
|
||||
{ BUS_OBJERR, "Object specific hardware error" },
|
||||
#endif
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
static int (*cc_user_info)(char*, char*);
|
||||
|
||||
|
||||
static void gdb_info(pid_t pid)
|
||||
{
|
||||
char respfile[64];
|
||||
char cmd_buf[128];
|
||||
FILE *f;
|
||||
int fd;
|
||||
|
||||
/* Create a temp file to put gdb commands into */
|
||||
strcpy(respfile, "gdb-respfile-XXXXXX");
|
||||
if((fd=mkstemp(respfile)) >= 0 && (f=fdopen(fd, "w")) != NULL)
|
||||
{
|
||||
fprintf(f, "attach %d\n"
|
||||
"shell echo \"\"\n"
|
||||
"shell echo \"* Loaded Libraries\"\n"
|
||||
"info sharedlibrary\n"
|
||||
"shell echo \"\"\n"
|
||||
"shell echo \"* Threads\"\n"
|
||||
"info threads\n"
|
||||
"shell echo \"\"\n"
|
||||
"shell echo \"* FPU Status\"\n"
|
||||
"info float\n"
|
||||
"shell echo \"\"\n"
|
||||
"shell echo \"* Registers\"\n"
|
||||
"info registers\n"
|
||||
"shell echo \"\"\n"
|
||||
"shell echo \"* Backtrace\"\n"
|
||||
"thread apply all backtrace full\n"
|
||||
"detach\n"
|
||||
"quit\n", pid);
|
||||
fclose(f);
|
||||
|
||||
/* Run gdb and print process info. */
|
||||
snprintf(cmd_buf, sizeof(cmd_buf), "gdb --quiet --batch --command=%s", respfile);
|
||||
printf("Executing: %s\n", cmd_buf);
|
||||
fflush(stdout);
|
||||
|
||||
system(cmd_buf);
|
||||
/* Clean up */
|
||||
remove(respfile);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Error creating temp file */
|
||||
if(fd >= 0)
|
||||
{
|
||||
close(fd);
|
||||
remove(respfile);
|
||||
}
|
||||
printf("!!! Could not create gdb command file\n");
|
||||
}
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
static void sys_info(void)
|
||||
{
|
||||
#ifdef __unix__
|
||||
system("echo \"System: `uname -a`\"");
|
||||
putchar('\n');
|
||||
fflush(stdout);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static size_t safe_write(int fd, const void *buf, size_t len)
|
||||
{
|
||||
size_t ret = 0;
|
||||
while(ret < len)
|
||||
{
|
||||
ssize_t rem;
|
||||
if((rem=write(fd, (const char*)buf+ret, len-ret)) == -1)
|
||||
{
|
||||
if(errno == EINTR)
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
ret += rem;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void crash_catcher(int signum, siginfo_t *siginfo, void *context)
|
||||
{
|
||||
//ucontext_t *ucontext = (ucontext_t*)context;
|
||||
pid_t dbg_pid;
|
||||
int fd[2];
|
||||
|
||||
/* Make sure the effective uid is the real uid */
|
||||
if(getuid() != geteuid())
|
||||
{
|
||||
raise(signum);
|
||||
return;
|
||||
}
|
||||
|
||||
safe_write(STDERR_FILENO, fatal_err, sizeof(fatal_err)-1);
|
||||
if(pipe(fd) == -1)
|
||||
{
|
||||
safe_write(STDERR_FILENO, pipe_err, sizeof(pipe_err)-1);
|
||||
raise(signum);
|
||||
return;
|
||||
}
|
||||
|
||||
crash_info.signum = signum;
|
||||
crash_info.pid = getpid();
|
||||
crash_info.has_siginfo = !!siginfo;
|
||||
if(siginfo)
|
||||
crash_info.siginfo = *siginfo;
|
||||
if(cc_user_info)
|
||||
cc_user_info(crash_info.buf, crash_info.buf+sizeof(crash_info.buf));
|
||||
|
||||
/* Fork off to start a crash handler */
|
||||
switch((dbg_pid=fork()))
|
||||
{
|
||||
/* Error */
|
||||
case -1:
|
||||
safe_write(STDERR_FILENO, fork_err, sizeof(fork_err)-1);
|
||||
raise(signum);
|
||||
return;
|
||||
|
||||
case 0:
|
||||
dup2(fd[0], STDIN_FILENO);
|
||||
close(fd[0]);
|
||||
close(fd[1]);
|
||||
|
||||
execl(argv0, argv0, crash_switch, NULL);
|
||||
|
||||
safe_write(STDERR_FILENO, exec_err, sizeof(exec_err)-1);
|
||||
_exit(1);
|
||||
|
||||
default:
|
||||
#ifdef __linux__
|
||||
prctl(PR_SET_PTRACER, dbg_pid, 0, 0, 0);
|
||||
#endif
|
||||
safe_write(fd[1], &crash_info, sizeof(crash_info));
|
||||
close(fd[0]);
|
||||
close(fd[1]);
|
||||
|
||||
/* Wait; we'll be killed when gdb is done */
|
||||
do {
|
||||
int status;
|
||||
if(waitpid(dbg_pid, &status, 0) == dbg_pid &&
|
||||
(WIFEXITED(status) || WIFSIGNALED(status)))
|
||||
{
|
||||
/* The debug process died before it could kill us */
|
||||
raise(signum);
|
||||
break;
|
||||
}
|
||||
} while(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void crash_handler(const char *logfile)
|
||||
{
|
||||
const char *sigdesc = "";
|
||||
int i;
|
||||
|
||||
if(fread(&crash_info, sizeof(crash_info), 1, stdin) != 1)
|
||||
{
|
||||
fprintf(stderr, "!!! Failed to retrieve info from crashed process\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Get the signal description */
|
||||
for(i = 0;signals[i].name;++i)
|
||||
{
|
||||
if(signals[i].signum == crash_info.signum)
|
||||
{
|
||||
sigdesc = signals[i].name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(crash_info.has_siginfo)
|
||||
{
|
||||
switch(crash_info.signum)
|
||||
{
|
||||
case SIGSEGV:
|
||||
for(i = 0;sigsegv_codes[i].name;++i)
|
||||
{
|
||||
if(sigsegv_codes[i].code == crash_info.siginfo.si_code)
|
||||
{
|
||||
sigdesc = sigsegv_codes[i].name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SIGFPE:
|
||||
for(i = 0;sigfpe_codes[i].name;++i)
|
||||
{
|
||||
if(sigfpe_codes[i].code == crash_info.siginfo.si_code)
|
||||
{
|
||||
sigdesc = sigfpe_codes[i].name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SIGILL:
|
||||
for(i = 0;sigill_codes[i].name;++i)
|
||||
{
|
||||
if(sigill_codes[i].code == crash_info.siginfo.si_code)
|
||||
{
|
||||
sigdesc = sigill_codes[i].name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SIGBUS:
|
||||
for(i = 0;sigbus_codes[i].name;++i)
|
||||
{
|
||||
if(sigbus_codes[i].code == crash_info.siginfo.si_code)
|
||||
{
|
||||
sigdesc = sigbus_codes[i].name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "%s (signal %i)\n", sigdesc, crash_info.signum);
|
||||
if(crash_info.has_siginfo)
|
||||
fprintf(stderr, "Address: %p\n", crash_info.siginfo.si_addr);
|
||||
fputc('\n', stderr);
|
||||
|
||||
if(logfile)
|
||||
{
|
||||
/* Create crash log file and redirect shell output to it */
|
||||
if(freopen(logfile, "wa", stdout) != stdout)
|
||||
{
|
||||
fprintf(stderr, "!!! Could not create %s following signal\n", logfile);
|
||||
exit(1);
|
||||
}
|
||||
fprintf(stderr, "Generating %s and killing process %d, please wait... ", logfile, crash_info.pid);
|
||||
|
||||
printf("*** Fatal Error ***\n"
|
||||
"%s (signal %i)\n", sigdesc, crash_info.signum);
|
||||
if(crash_info.has_siginfo)
|
||||
printf("Address: %p\n", crash_info.siginfo.si_addr);
|
||||
fputc('\n', stdout);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
sys_info();
|
||||
|
||||
crash_info.buf[sizeof(crash_info.buf)-1] = '\0';
|
||||
printf("%s\n", crash_info.buf);
|
||||
fflush(stdout);
|
||||
|
||||
if(crash_info.pid > 0)
|
||||
{
|
||||
gdb_info(crash_info.pid);
|
||||
kill(crash_info.pid, SIGKILL);
|
||||
}
|
||||
|
||||
if(logfile)
|
||||
{
|
||||
char cwd[MAXPATHLEN];
|
||||
getcwd(cwd, MAXPATHLEN);
|
||||
|
||||
std::string message = "OpenMW has encountered a fatal error.\nCrash log saved to '" + std::string(cwd) + "/" + std::string(logfile) + "'.\n Please report this to https://bugs.openmw.org !";
|
||||
SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), NULL);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int cc_install_handlers(int argc, char **argv, int num_signals, int *signals, const char *logfile, int (*user_info)(char*, char*))
|
||||
{
|
||||
struct sigaction sa;
|
||||
stack_t altss;
|
||||
int retval;
|
||||
|
||||
if(argc == 2 && strcmp(argv[1], crash_switch) == 0)
|
||||
crash_handler(logfile);
|
||||
|
||||
cc_user_info = user_info;
|
||||
|
||||
if(argv[0][0] == '/')
|
||||
snprintf(argv0, sizeof(argv0), "%s", argv[0]);
|
||||
else
|
||||
{
|
||||
getcwd(argv0, sizeof(argv0));
|
||||
retval = strlen(argv0);
|
||||
snprintf(argv0+retval, sizeof(argv0)-retval, "/%s", argv[0]);
|
||||
}
|
||||
|
||||
/* Set an alternate signal stack so SIGSEGVs caused by stack overflows
|
||||
* still run */
|
||||
altss.ss_sp = altstack;
|
||||
altss.ss_flags = 0;
|
||||
altss.ss_size = sizeof(altstack);
|
||||
sigaltstack(&altss, NULL);
|
||||
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_sigaction = crash_catcher;
|
||||
sa.sa_flags = SA_RESETHAND | SA_NODEFER | SA_SIGINFO | SA_ONSTACK;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
|
||||
retval = 0;
|
||||
while(num_signals--)
|
||||
{
|
||||
if((*signals != SIGSEGV && *signals != SIGILL && *signals != SIGFPE && *signals != SIGABRT &&
|
||||
*signals != SIGBUS) || sigaction(*signals, &sa, NULL) == -1)
|
||||
{
|
||||
*signals = 0;
|
||||
retval = -1;
|
||||
}
|
||||
++signals;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
// gdb apparently opens FD(s) 3,4,5 (whereas a typical prog uses only stdin=0, stdout=1,stderr=2)
|
||||
bool
|
||||
is_debugger_attached(void)
|
||||
{
|
||||
bool rc = false;
|
||||
FILE *fd = fopen("/tmp", "r");
|
||||
|
||||
if (fileno(fd) > 5)
|
||||
{
|
||||
rc = true;
|
||||
}
|
||||
|
||||
fclose(fd);
|
||||
return rc;
|
||||
}
|
@ -0,0 +1,196 @@
|
||||
#include "recharge.hpp"
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/format.hpp>
|
||||
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
|
||||
#include "../mwworld/player.hpp"
|
||||
#include "../mwworld/containerstore.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
#include "../mwmechanics/npcstats.hpp"
|
||||
|
||||
#include "widgets.hpp"
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
|
||||
Recharge::Recharge()
|
||||
: WindowBase("openmw_recharge_dialog.layout")
|
||||
{
|
||||
getWidget(mBox, "Box");
|
||||
getWidget(mView, "View");
|
||||
getWidget(mGemBox, "GemBox");
|
||||
getWidget(mGemIcon, "GemIcon");
|
||||
getWidget(mChargeLabel, "ChargeLabel");
|
||||
getWidget(mCancelButton, "CancelButton");
|
||||
|
||||
mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &Recharge::onCancel);
|
||||
|
||||
setVisible(false);
|
||||
}
|
||||
|
||||
void Recharge::open()
|
||||
{
|
||||
center();
|
||||
}
|
||||
|
||||
void Recharge::start (const MWWorld::Ptr &item)
|
||||
{
|
||||
std::string path = std::string("icons\\");
|
||||
path += MWWorld::Class::get(item).getInventoryIcon(item);
|
||||
int pos = path.rfind(".");
|
||||
path.erase(pos);
|
||||
path.append(".dds");
|
||||
mGemIcon->setImageTexture (path);
|
||||
mGemIcon->setUserString("ToolTipType", "ItemPtr");
|
||||
mGemIcon->setUserData(item);
|
||||
|
||||
updateView();
|
||||
}
|
||||
|
||||
void Recharge::updateView()
|
||||
{
|
||||
MWWorld::Ptr gem = *mGemIcon->getUserData<MWWorld::Ptr>();
|
||||
|
||||
std::string soul = gem.getCellRef().mSoul;
|
||||
const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get<ESM::Creature>().find(soul);
|
||||
|
||||
mChargeLabel->setCaptionWithReplacing("#{sCharges} " + boost::lexical_cast<std::string>(creature->mData.mSoul));
|
||||
|
||||
bool toolBoxVisible = (gem.getRefData().getCount() != 0);
|
||||
mGemBox->setVisible(toolBoxVisible);
|
||||
|
||||
bool toolBoxWasVisible = (mBox->getPosition().top != mGemBox->getPosition().top);
|
||||
|
||||
if (toolBoxVisible && !toolBoxWasVisible)
|
||||
{
|
||||
// shrink
|
||||
mBox->setPosition(mBox->getPosition() + MyGUI::IntPoint(0, mGemBox->getSize().height));
|
||||
mBox->setSize(mBox->getSize() - MyGUI::IntSize(0,mGemBox->getSize().height));
|
||||
}
|
||||
else if (!toolBoxVisible && toolBoxWasVisible)
|
||||
{
|
||||
// expand
|
||||
mBox->setPosition(MyGUI::IntPoint (mBox->getPosition().left, mGemBox->getPosition().top));
|
||||
mBox->setSize(mBox->getSize() + MyGUI::IntSize(0,mGemBox->getSize().height));
|
||||
}
|
||||
|
||||
while (mView->getChildCount())
|
||||
MyGUI::Gui::getInstance().destroyWidget(mView->getChildAt(0));
|
||||
|
||||
int currentY = 0;
|
||||
|
||||
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
|
||||
MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player);
|
||||
for (MWWorld::ContainerStoreIterator iter (store.begin());
|
||||
iter!=store.end(); ++iter)
|
||||
{
|
||||
std::string enchantmentName = iter->getClass().getEnchantment(*iter);
|
||||
if (enchantmentName.empty())
|
||||
continue;
|
||||
const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(enchantmentName);
|
||||
if (iter->getCellRef().mEnchantmentCharge >= enchantment->mData.mCharge
|
||||
|| iter->getCellRef().mEnchantmentCharge == -1)
|
||||
continue;
|
||||
|
||||
MyGUI::TextBox* text = mView->createWidget<MyGUI::TextBox> (
|
||||
"SandText", MyGUI::IntCoord(8, currentY, mView->getWidth()-8, 18), MyGUI::Align::Default);
|
||||
text->setCaption(MWWorld::Class::get(*iter).getName(*iter));
|
||||
text->setNeedMouseFocus(false);
|
||||
currentY += 19;
|
||||
|
||||
MyGUI::ImageBox* icon = mView->createWidget<MyGUI::ImageBox> (
|
||||
"ImageBox", MyGUI::IntCoord(16, currentY, 32, 32), MyGUI::Align::Default);
|
||||
std::string path = std::string("icons\\");
|
||||
path += MWWorld::Class::get(*iter).getInventoryIcon(*iter);
|
||||
int pos = path.rfind(".");
|
||||
path.erase(pos);
|
||||
path.append(".dds");
|
||||
icon->setImageTexture (path);
|
||||
icon->setUserString("ToolTipType", "ItemPtr");
|
||||
icon->setUserData(*iter);
|
||||
icon->eventMouseButtonClick += MyGUI::newDelegate(this, &Recharge::onItemClicked);
|
||||
icon->eventMouseWheel += MyGUI::newDelegate(this, &Recharge::onMouseWheel);
|
||||
|
||||
Widgets::MWDynamicStatPtr chargeWidget = mView->createWidget<Widgets::MWDynamicStat>
|
||||
("MW_ChargeBar", MyGUI::IntCoord(72, currentY+2, 199, 20), MyGUI::Align::Default);
|
||||
chargeWidget->setValue(iter->getCellRef().mEnchantmentCharge, enchantment->mData.mCharge);
|
||||
chargeWidget->setNeedMouseFocus(false);
|
||||
|
||||
currentY += 32 + 4;
|
||||
}
|
||||
mView->setCanvasSize (MyGUI::IntSize(mView->getWidth(), std::max(mView->getHeight(), currentY)));
|
||||
}
|
||||
|
||||
void Recharge::onCancel(MyGUI::Widget *sender)
|
||||
{
|
||||
MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Recharge);
|
||||
}
|
||||
|
||||
void Recharge::onItemClicked(MyGUI::Widget *sender)
|
||||
{
|
||||
MWWorld::Ptr gem = *mGemIcon->getUserData<MWWorld::Ptr>();
|
||||
|
||||
if (!gem.getRefData().getCount())
|
||||
return;
|
||||
|
||||
MWWorld::Ptr item = *sender->getUserData<MWWorld::Ptr>();
|
||||
|
||||
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
|
||||
MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);
|
||||
MWMechanics::NpcStats& npcStats = player.getClass().getNpcStats(player);
|
||||
|
||||
float luckTerm = 0.1 * stats.getAttribute(ESM::Attribute::Luck).getModified();
|
||||
if (luckTerm < 1|| luckTerm > 10)
|
||||
luckTerm = 1;
|
||||
|
||||
float intelligenceTerm = 0.2 * stats.getAttribute(ESM::Attribute::Intelligence).getModified();
|
||||
|
||||
if (intelligenceTerm > 20)
|
||||
intelligenceTerm = 20;
|
||||
if (intelligenceTerm < 1)
|
||||
intelligenceTerm = 1;
|
||||
|
||||
float x = (npcStats.getSkill(ESM::Skill::Enchant).getModified() + intelligenceTerm + luckTerm) * stats.getFatigueTerm();
|
||||
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
|
||||
if (roll < x)
|
||||
{
|
||||
std::string soul = gem.getCellRef().mSoul;
|
||||
const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get<ESM::Creature>().find(soul);
|
||||
|
||||
float restored = creature->mData.mSoul * (roll / x);
|
||||
|
||||
const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(
|
||||
item.getClass().getEnchantment(item));
|
||||
item.getCellRef().mEnchantmentCharge =
|
||||
std::min(item.getCellRef().mEnchantmentCharge + restored, static_cast<float>(enchantment->mData.mCharge));
|
||||
|
||||
player.getClass().skillUsageSucceeded (player, ESM::Skill::Enchant, 0);
|
||||
}
|
||||
|
||||
gem.getContainerStore()->remove(gem, 1, player);
|
||||
|
||||
if (gem.getRefData().getCount() == 0)
|
||||
{
|
||||
std::string message = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sNotifyMessage51")->getString();
|
||||
message = boost::str(boost::format(message) % gem.getClass().getName(gem));
|
||||
MWBase::Environment::get().getWindowManager()->messageBox(message);
|
||||
}
|
||||
|
||||
updateView();
|
||||
}
|
||||
|
||||
void Recharge::onMouseWheel(MyGUI::Widget* _sender, int _rel)
|
||||
{
|
||||
if (mView->getViewOffset().top + _rel*0.3 > 0)
|
||||
mView->setViewOffset(MyGUI::IntPoint(0, 0));
|
||||
else
|
||||
mView->setViewOffset(MyGUI::IntPoint(0, mView->getViewOffset().top + _rel*0.3));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
#ifndef OPENMW_MWGUI_RECHARGE_H
|
||||
#define OPENMW_MWGUI_RECHARGE_H
|
||||
|
||||
#include "windowbase.hpp"
|
||||
|
||||
#include "../mwworld/ptr.hpp"
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
|
||||
class Recharge : public WindowBase
|
||||
{
|
||||
public:
|
||||
Recharge();
|
||||
|
||||
virtual void open();
|
||||
|
||||
void start (const MWWorld::Ptr& gem);
|
||||
|
||||
protected:
|
||||
MyGUI::Widget* mBox;
|
||||
MyGUI::ScrollView* mView;
|
||||
|
||||
MyGUI::Widget* mGemBox;
|
||||
|
||||
MyGUI::ImageBox* mGemIcon;
|
||||
|
||||
MyGUI::TextBox* mChargeLabel;
|
||||
|
||||
MyGUI::Button* mCancelButton;
|
||||
|
||||
void updateView();
|
||||
|
||||
void onItemClicked (MyGUI::Widget* sender);
|
||||
void onCancel (MyGUI::Widget* sender);
|
||||
void onMouseWheel(MyGUI::Widget* _sender, int _rel);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,148 @@
|
||||
#include "aicombat.hpp"
|
||||
|
||||
#include "movement.hpp"
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/player.hpp"
|
||||
#include "../mwworld/timestamp.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/mechanicsmanager.hpp"
|
||||
|
||||
#include "creaturestats.hpp"
|
||||
#include "npcstats.hpp"
|
||||
|
||||
#include "OgreMath.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
static float sgn(float a)
|
||||
{
|
||||
if(a > 0)
|
||||
return 1.0;
|
||||
return -1.0;
|
||||
}
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
|
||||
AiCombat::AiCombat(const std::string &targetId)
|
||||
:mTargetId(targetId),mTimer(0),mTimer2(0)
|
||||
{
|
||||
}
|
||||
|
||||
bool AiCombat::execute (const MWWorld::Ptr& actor,float duration)
|
||||
{
|
||||
if(!MWWorld::Class::get(actor).getCreatureStats(actor).isHostile()) return true;
|
||||
|
||||
const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mTargetId, false);
|
||||
|
||||
if(MWWorld::Class::get(actor).getCreatureStats(actor).getHealth().getCurrent() <= 0) return true;
|
||||
|
||||
if(actor.getTypeName() == typeid(ESM::NPC).name())
|
||||
{
|
||||
MWWorld::Class::get(actor).
|
||||
MWWorld::Class::get(actor).setStance(actor, MWWorld::Class::Run,true);
|
||||
MWMechanics::DrawState_ state = MWWorld::Class::get(actor).getNpcStats(actor).getDrawState();
|
||||
if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing)
|
||||
MWWorld::Class::get(actor).getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon);
|
||||
//MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true);
|
||||
}
|
||||
ESM::Position pos = actor.getRefData().getPosition();
|
||||
const ESM::Pathgrid *pathgrid =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*actor.getCell()->mCell);
|
||||
|
||||
float xCell = 0;
|
||||
float yCell = 0;
|
||||
|
||||
if (actor.getCell()->mCell->isExterior())
|
||||
{
|
||||
xCell = actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE;
|
||||
yCell = actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE;
|
||||
}
|
||||
|
||||
ESM::Pathgrid::Point dest;
|
||||
dest.mX = target.getRefData().getPosition().pos[0];
|
||||
dest.mY = target.getRefData().getPosition().pos[1];
|
||||
dest.mZ = target.getRefData().getPosition().pos[2];
|
||||
|
||||
ESM::Pathgrid::Point start;
|
||||
start.mX = pos.pos[0];
|
||||
start.mY = pos.pos[1];
|
||||
start.mZ = pos.pos[2];
|
||||
|
||||
mTimer2 = mTimer2 + duration;
|
||||
|
||||
if(!mPathFinder.isPathConstructed())
|
||||
mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true);
|
||||
else
|
||||
{
|
||||
mPathFinder2.buildPath(start, dest, pathgrid, xCell, yCell, true);
|
||||
ESM::Pathgrid::Point lastPt = mPathFinder.getPath().back();
|
||||
if((mTimer2 > 0.25)&&(mPathFinder2.getPathSize() < mPathFinder.getPathSize() ||
|
||||
(dest.mX - lastPt.mX)*(dest.mX - lastPt.mX)+(dest.mY - lastPt.mY)*(dest.mY - lastPt.mY)+(dest.mZ - lastPt.mZ)*(dest.mZ - lastPt.mZ) > 200*200))
|
||||
{
|
||||
mTimer2 = 0;
|
||||
mPathFinder = mPathFinder2;
|
||||
}
|
||||
}
|
||||
|
||||
mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]);
|
||||
|
||||
float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]);
|
||||
MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false);
|
||||
MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1;
|
||||
|
||||
|
||||
float range = 100;
|
||||
MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false);
|
||||
if((dest.mX - start.mX)*(dest.mX - start.mX)+(dest.mY - start.mY)*(dest.mY - start.mY)+(dest.mZ - start.mZ)*(dest.mZ - start.mZ)
|
||||
< range*range)
|
||||
{
|
||||
float directionX = dest.mX - start.mX;
|
||||
float directionY = dest.mY - start.mY;
|
||||
float directionResult = sqrt(directionX * directionX + directionY * directionY);
|
||||
|
||||
zAngle = Ogre::Radian( acos(directionY / directionResult) * sgn(asin(directionX / directionResult)) ).valueDegrees();
|
||||
MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false);
|
||||
|
||||
mPathFinder.clearPath();
|
||||
|
||||
if(mTimer == 0)
|
||||
{
|
||||
MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false);
|
||||
//mTimer = mTimer + duration;
|
||||
}
|
||||
if( mTimer > 1)
|
||||
{
|
||||
MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true);
|
||||
mTimer = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
mTimer = mTimer + duration;
|
||||
}
|
||||
|
||||
MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0;
|
||||
//MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(!MWWorld::Class::get(actor).getCreatureStats(actor).getAttackingOrSpell());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int AiCombat::getTypeId() const
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
|
||||
unsigned int AiCombat::getPriority() const
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
AiCombat *MWMechanics::AiCombat::clone() const
|
||||
{
|
||||
return new AiCombat(*this);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,36 @@
|
||||
#ifndef GAME_MWMECHANICS_AICOMBAT_H
|
||||
#define GAME_MWMECHANICS_AICOMBAT_H
|
||||
|
||||
#include "aipackage.hpp"
|
||||
|
||||
#include "pathfinding.hpp"
|
||||
|
||||
#include "movement.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
class AiCombat : public AiPackage
|
||||
{
|
||||
public:
|
||||
AiCombat(const std::string &targetId);
|
||||
|
||||
virtual AiCombat *clone() const;
|
||||
|
||||
virtual bool execute (const MWWorld::Ptr& actor,float duration);
|
||||
///< \return Package completed?
|
||||
|
||||
virtual int getTypeId() const;
|
||||
|
||||
virtual unsigned int getPriority() const;
|
||||
|
||||
private:
|
||||
std::string mTargetId;
|
||||
|
||||
PathFinder mPathFinder;
|
||||
PathFinder mPathFinder2;
|
||||
float mTimer;
|
||||
float mTimer2;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,505 @@
|
||||
#include "spellcasting.hpp"
|
||||
|
||||
#include <boost/format.hpp>
|
||||
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
#include "../mwbase/soundmanager.hpp"
|
||||
|
||||
|
||||
#include "../mwworld/containerstore.hpp"
|
||||
|
||||
#include "../mwrender/animation.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
|
||||
CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target)
|
||||
: mCaster(caster)
|
||||
, mTarget(target)
|
||||
, mStack(false)
|
||||
{
|
||||
}
|
||||
|
||||
void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster,
|
||||
const ESM::EffectList &effects, ESM::RangeType range, bool reflected)
|
||||
{
|
||||
// If none of the effects need to apply, we can early-out
|
||||
bool found = false;
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.mList.begin());
|
||||
iter!=effects.mList.end(); ++iter)
|
||||
{
|
||||
if (iter->mRange != range)
|
||||
continue;
|
||||
found = true;
|
||||
}
|
||||
if (!found)
|
||||
return;
|
||||
|
||||
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (mId);
|
||||
if (spell && (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight))
|
||||
{
|
||||
float x = (spell->mData.mType == ESM::Spell::ST_Disease) ?
|
||||
target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistCommonDisease).mMagnitude
|
||||
: target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistBlightDisease).mMagnitude;
|
||||
|
||||
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
|
||||
if (roll <= x)
|
||||
{
|
||||
// Fully resisted, show message
|
||||
if (target.getRefData().getHandle() == "player")
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ESM::EffectList reflectedEffects;
|
||||
std::vector<ActiveSpells::Effect> appliedLastingEffects;
|
||||
bool firstAppliedEffect = true;
|
||||
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (effects.mList.begin());
|
||||
effectIt!=effects.mList.end(); ++effectIt)
|
||||
{
|
||||
if (effectIt->mRange != range)
|
||||
continue;
|
||||
|
||||
const ESM::MagicEffect *magicEffect =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
|
||||
effectIt->mEffectID);
|
||||
|
||||
float magnitudeMult = 1;
|
||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful && target.getClass().isActor())
|
||||
{
|
||||
// If player is attempting to cast a harmful spell, show the target's HP bar
|
||||
if (caster.getRefData().getHandle() == "player" && target != caster)
|
||||
MWBase::Environment::get().getWindowManager()->setEnemy(target);
|
||||
|
||||
// Try absorbing if it's a spell
|
||||
// NOTE: Vanilla does this once per effect source instead of adding the % from all sources together, not sure
|
||||
// if that is worth replicating.
|
||||
if (spell && caster != target)
|
||||
{
|
||||
int absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).mMagnitude;
|
||||
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
|
||||
bool isAbsorbed = (roll < absorb);
|
||||
if (isAbsorbed)
|
||||
{
|
||||
const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Absorb");
|
||||
MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect(
|
||||
"meshes\\" + absorbStatic->mModel, ESM::MagicEffect::Reflect, false, "");
|
||||
// Magicka is increased by cost of spell
|
||||
DynamicStat<float> magicka = target.getClass().getCreatureStats(target).getMagicka();
|
||||
magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost);
|
||||
target.getClass().getCreatureStats(target).setMagicka(magicka);
|
||||
magnitudeMult = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Try reflecting
|
||||
if (!reflected && magnitudeMult > 0 && caster != target && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable))
|
||||
{
|
||||
int reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).mMagnitude;
|
||||
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
|
||||
bool isReflected = (roll < reflect);
|
||||
if (isReflected)
|
||||
{
|
||||
const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Reflect");
|
||||
MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect(
|
||||
"meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, "");
|
||||
reflectedEffects.mList.push_back(*effectIt);
|
||||
magnitudeMult = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Try resisting
|
||||
if (magnitudeMult > 0 && target.getClass().isActor())
|
||||
{
|
||||
|
||||
magnitudeMult = MWMechanics::getEffectMultiplier(effectIt->mEffectID, target, caster, spell);
|
||||
if (magnitudeMult == 0)
|
||||
{
|
||||
// Fully resisted, show message
|
||||
if (target.getRefData().getHandle() == "player")
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}");
|
||||
else
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (magnitudeMult > 0)
|
||||
{
|
||||
float random = std::rand() / static_cast<float>(RAND_MAX);
|
||||
float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random;
|
||||
magnitude *= magnitudeMult;
|
||||
|
||||
if (target.getClass().isActor() && !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration))
|
||||
{
|
||||
ActiveSpells::Effect effect;
|
||||
effect.mKey = MWMechanics::EffectKey(*effectIt);
|
||||
effect.mDuration = effectIt->mDuration;
|
||||
effect.mMagnitude = magnitude;
|
||||
|
||||
appliedLastingEffects.push_back(effect);
|
||||
|
||||
// For absorb effects, also apply the effect to the caster - but with a negative
|
||||
// magnitude, since we're transfering stats from the target to the caster
|
||||
for (int i=0; i<5; ++i)
|
||||
{
|
||||
if (effectIt->mEffectID == ESM::MagicEffect::AbsorbAttribute+i)
|
||||
{
|
||||
std::vector<ActiveSpells::Effect> effects;
|
||||
ActiveSpells::Effect effect_ = effect;
|
||||
effect_.mMagnitude *= -1;
|
||||
effects.push_back(effect_);
|
||||
caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell("", true, effects, mSourceName);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
applyInstantEffect(target, effectIt->mEffectID, magnitude);
|
||||
|
||||
if (target.getClass().isActor() || magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
|
||||
{
|
||||
// Play sound, only for the first effect
|
||||
if (firstAppliedEffect)
|
||||
{
|
||||
static const std::string schools[] = {
|
||||
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
|
||||
};
|
||||
|
||||
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
||||
if(!magicEffect->mHitSound.empty())
|
||||
sndMgr->playSound3D(target, magicEffect->mHitSound, 1.0f, 1.0f);
|
||||
else
|
||||
sndMgr->playSound3D(target, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f);
|
||||
firstAppliedEffect = false;
|
||||
}
|
||||
|
||||
// Add VFX
|
||||
if (!magicEffect->mHit.empty())
|
||||
{
|
||||
const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find (magicEffect->mHit);
|
||||
bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx;
|
||||
// Note: in case of non actor, a free effect should be fine as well
|
||||
MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(target);
|
||||
if (anim)
|
||||
anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: For Area effects, launch a growing particle effect that applies the effect to more actors as it hits them. Best managed in World.
|
||||
}
|
||||
}
|
||||
|
||||
if (reflectedEffects.mList.size())
|
||||
inflict(caster, target, reflectedEffects, range, true);
|
||||
|
||||
if (appliedLastingEffects.size())
|
||||
target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects, mSourceName);
|
||||
}
|
||||
|
||||
void CastSpell::applyInstantEffect(const MWWorld::Ptr &target, short effectId, float magnitude)
|
||||
{
|
||||
if (!target.getClass().isActor())
|
||||
{
|
||||
if (effectId == ESM::MagicEffect::Lock)
|
||||
{
|
||||
if (target.getCellRef().mLockLevel < magnitude)
|
||||
target.getCellRef().mLockLevel = magnitude;
|
||||
}
|
||||
else if (effectId == ESM::MagicEffect::Open)
|
||||
{
|
||||
// TODO: This is a crime
|
||||
if (target.getCellRef().mLockLevel <= magnitude)
|
||||
{
|
||||
if (target.getCellRef().mLockLevel > 0)
|
||||
MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock", 1.f, 1.f);
|
||||
target.getCellRef().mLockLevel = 0;
|
||||
}
|
||||
else
|
||||
MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock Fail", 1.f, 1.f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (effectId == ESM::MagicEffect::CurePoison)
|
||||
target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(ESM::MagicEffect::Poison);
|
||||
else if (effectId == ESM::MagicEffect::CureParalyzation)
|
||||
target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze);
|
||||
else if (effectId == ESM::MagicEffect::CureCommonDisease)
|
||||
target.getClass().getCreatureStats(target).getSpells().purgeCommonDisease();
|
||||
else if (effectId == ESM::MagicEffect::CureBlightDisease)
|
||||
target.getClass().getCreatureStats(target).getSpells().purgeBlightDisease();
|
||||
else if (effectId == ESM::MagicEffect::CureCorprusDisease)
|
||||
target.getClass().getCreatureStats(target).getSpells().purgeCorprusDisease();
|
||||
else if (effectId == ESM::MagicEffect::Dispel)
|
||||
target.getClass().getCreatureStats(target).getActiveSpells().purgeAll();
|
||||
else if (effectId == ESM::MagicEffect::RemoveCurse)
|
||||
target.getClass().getCreatureStats(target).getSpells().purgeCurses();
|
||||
|
||||
else if (effectId == ESM::MagicEffect::DivineIntervention)
|
||||
{
|
||||
// We need to be able to get the world location of an interior cell before implementing this
|
||||
// or alternatively, the last known exterior location of the player, which is how vanilla does it.
|
||||
}
|
||||
else if (effectId == ESM::MagicEffect::AlmsiviIntervention)
|
||||
{
|
||||
// Same as above
|
||||
}
|
||||
|
||||
else if (effectId == ESM::MagicEffect::Mark)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
else if (effectId == ESM::MagicEffect::Recall)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CastSpell::cast(const std::string &id)
|
||||
{
|
||||
if (const ESM::Spell *spell =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (id))
|
||||
return cast(spell);
|
||||
|
||||
if (const ESM::Potion *potion =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Potion>().search (id))
|
||||
return cast(potion);
|
||||
|
||||
if (const ESM::Ingredient *ingredient =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Ingredient>().search (id))
|
||||
return cast(ingredient);
|
||||
|
||||
throw std::runtime_error("ID type cannot be casted");
|
||||
}
|
||||
|
||||
bool CastSpell::cast(const MWWorld::Ptr &item)
|
||||
{
|
||||
std::string enchantmentName = item.getClass().getEnchantment(item);
|
||||
if (enchantmentName.empty())
|
||||
throw std::runtime_error("can't cast an item without an enchantment");
|
||||
|
||||
mSourceName = item.getClass().getName(item);
|
||||
mId = item.getCellRef().mRefID;
|
||||
|
||||
const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(enchantmentName);
|
||||
|
||||
mStack = (enchantment->mData.mType == ESM::Enchantment::CastOnce);
|
||||
|
||||
if (enchantment->mData.mType == ESM::Enchantment::WhenUsed)
|
||||
{
|
||||
// Check if there's enough charge left
|
||||
const float enchantCost = enchantment->mData.mCost;
|
||||
MWMechanics::NpcStats &stats = MWWorld::Class::get(mCaster).getNpcStats(mCaster);
|
||||
int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified();
|
||||
const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10));
|
||||
|
||||
if (item.getCellRef().mEnchantmentCharge == -1)
|
||||
item.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge;
|
||||
|
||||
if (mCaster.getRefData().getHandle() == "player" && item.getCellRef().mEnchantmentCharge < castCost)
|
||||
{
|
||||
// TODO: Should there be a sound here?
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reduce charge
|
||||
item.getCellRef().mEnchantmentCharge -= castCost;
|
||||
}
|
||||
if (enchantment->mData.mType == ESM::Enchantment::CastOnce)
|
||||
item.getContainerStore()->remove(item, 1, mCaster);
|
||||
else if (enchantment->mData.mType != ESM::Enchantment::WhenStrikes)
|
||||
{
|
||||
if (mCaster.getRefData().getHandle() == "player")
|
||||
MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); // Set again to show the modified charge
|
||||
}
|
||||
|
||||
if (mCaster.getRefData().getHandle() == "player")
|
||||
mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 1);
|
||||
|
||||
inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self);
|
||||
|
||||
if (!mTarget.isEmpty())
|
||||
{
|
||||
if (!mTarget.getClass().isActor() || !mTarget.getClass().getCreatureStats(mTarget).isDead())
|
||||
inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch);
|
||||
}
|
||||
|
||||
MWBase::Environment::get().getWorld()->launchProjectile(mId, false, enchantment->mEffects, mCaster, mSourceName);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CastSpell::cast(const ESM::Potion* potion)
|
||||
{
|
||||
mSourceName = potion->mName;
|
||||
mId = potion->mId;
|
||||
mStack = true;
|
||||
|
||||
inflict(mCaster, mCaster, potion->mEffects, ESM::RT_Self);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CastSpell::cast(const ESM::Spell* spell)
|
||||
{
|
||||
mSourceName = spell->mName;
|
||||
mId = spell->mId;
|
||||
mStack = false;
|
||||
|
||||
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
||||
|
||||
int school = 0;
|
||||
|
||||
if (mCaster.getClass().isActor())
|
||||
{
|
||||
school = getSpellSchool(spell, mCaster);
|
||||
|
||||
CreatureStats& stats = mCaster.getClass().getCreatureStats(mCaster);
|
||||
|
||||
// Reduce fatigue (note that in the vanilla game, both GMSTs are 0, and there's no fatigue loss)
|
||||
static const float fFatigueSpellBase = store.get<ESM::GameSetting>().find("fFatigueSpellBase")->getFloat();
|
||||
static const float fFatigueSpellMult = store.get<ESM::GameSetting>().find("fFatigueSpellMult")->getFloat();
|
||||
DynamicStat<float> fatigue = stats.getFatigue();
|
||||
const float normalizedEncumbrance = mCaster.getClass().getEncumbrance(mCaster) / mCaster.getClass().getCapacity(mCaster);
|
||||
float fatigueLoss = spell->mData.mCost * (fFatigueSpellBase + normalizedEncumbrance * fFatigueSpellMult);
|
||||
fatigue.setCurrent(std::max(0.f, fatigue.getCurrent() - fatigueLoss));
|
||||
stats.setFatigue(fatigue);
|
||||
|
||||
// Check mana
|
||||
bool fail = false;
|
||||
DynamicStat<float> magicka = stats.getMagicka();
|
||||
if (magicka.getCurrent() < spell->mData.mCost)
|
||||
{
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientSP}");
|
||||
fail = true;
|
||||
}
|
||||
|
||||
// Reduce mana
|
||||
if (!fail)
|
||||
{
|
||||
magicka.setCurrent(magicka.getCurrent() - spell->mData.mCost);
|
||||
stats.setMagicka(magicka);
|
||||
}
|
||||
|
||||
// If this is a power, check if it was already used in last 24h
|
||||
if (!fail && spell->mData.mType & ESM::Spell::ST_Power)
|
||||
{
|
||||
if (stats.canUsePower(spell->mId))
|
||||
stats.usePower(spell->mId);
|
||||
else
|
||||
{
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sPowerAlreadyUsed}");
|
||||
fail = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check success
|
||||
int successChance = getSpellSuccessChance(spell, mCaster);
|
||||
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
|
||||
if (!fail && roll >= successChance)
|
||||
{
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}");
|
||||
fail = true;
|
||||
}
|
||||
|
||||
if (fail)
|
||||
{
|
||||
// Failure sound
|
||||
static const std::string schools[] = {
|
||||
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
|
||||
};
|
||||
|
||||
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
||||
sndMgr->playSound3D(mCaster, "Spell Failure " + schools[school], 1.0f, 1.0f);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (mCaster.getRefData().getHandle() == "player" && spell->mData.mType == ESM::Spell::ST_Spell)
|
||||
mCaster.getClass().skillUsageSucceeded(mCaster,
|
||||
spellSchoolToSkill(school), 0);
|
||||
|
||||
inflict(mCaster, mCaster, spell->mEffects, ESM::RT_Self);
|
||||
|
||||
if (!mTarget.isEmpty())
|
||||
{
|
||||
if (!mTarget.getClass().isActor() || !mTarget.getClass().getCreatureStats(mTarget).isDead())
|
||||
{
|
||||
inflict(mTarget, mCaster, spell->mEffects, ESM::RT_Touch);
|
||||
}
|
||||
}
|
||||
|
||||
MWBase::Environment::get().getWorld()->launchProjectile(mId, false, spell->mEffects, mCaster, mSourceName);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CastSpell::cast (const ESM::Ingredient* ingredient)
|
||||
{
|
||||
mId = ingredient->mId;
|
||||
mStack = true;
|
||||
mSourceName = ingredient->mName;
|
||||
|
||||
ESM::ENAMstruct effect;
|
||||
effect.mEffectID = ingredient->mData.mEffectID[0];
|
||||
effect.mSkill = ingredient->mData.mSkills[0];
|
||||
effect.mAttribute = ingredient->mData.mAttributes[0];
|
||||
effect.mRange = ESM::RT_Self;
|
||||
effect.mArea = 0;
|
||||
|
||||
const ESM::MagicEffect *magicEffect =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
|
||||
effect.mEffectID);
|
||||
|
||||
const MWMechanics::NpcStats& npcStats = mCaster.getClass().getNpcStats(mCaster);
|
||||
const MWMechanics::CreatureStats& creatureStats = mCaster.getClass().getCreatureStats(mCaster);
|
||||
|
||||
float x = (npcStats.getSkill (ESM::Skill::Alchemy).getModified() +
|
||||
0.2 * creatureStats.getAttribute (ESM::Attribute::Intelligence).getModified()
|
||||
+ 0.1 * creatureStats.getAttribute (ESM::Attribute::Luck).getModified())
|
||||
* creatureStats.getFatigueTerm();
|
||||
|
||||
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
|
||||
if (roll > x)
|
||||
{
|
||||
// "X has no effect on you"
|
||||
std::string message = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sNotifyMessage50")->getString();
|
||||
message = boost::str(boost::format(message) % ingredient->mName);
|
||||
MWBase::Environment::get().getWindowManager()->messageBox(message);
|
||||
return false;
|
||||
}
|
||||
|
||||
float magnitude = 0;
|
||||
float y = roll / std::min(x, 100.f);
|
||||
y *= 0.25 * x;
|
||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
|
||||
effect.mDuration = int(y);
|
||||
else
|
||||
effect.mDuration = 1;
|
||||
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude))
|
||||
{
|
||||
if (!magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
|
||||
magnitude = int((0.05 * y) / (0.1 * magicEffect->mData.mBaseCost));
|
||||
else
|
||||
magnitude = int(y / (0.1 * magicEffect->mData.mBaseCost));
|
||||
magnitude = std::max(1.f, magnitude);
|
||||
}
|
||||
else
|
||||
magnitude = 1;
|
||||
|
||||
effect.mMagnMax = magnitude;
|
||||
effect.mMagnMin = magnitude;
|
||||
|
||||
ESM::EffectList effects;
|
||||
effects.mList.push_back(effect);
|
||||
|
||||
inflict(mCaster, mCaster, effects, ESM::RT_Self);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue