1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * Additional copyright for this file:
8  * Copyright (C) 1994-1998 Revolution Software Ltd.
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23  */
24 
25 
26 #include "common/textconsole.h"
27 
28 #include "sword2/sword2.h"
29 #include "sword2/defs.h"
30 #include "sword2/header.h"
31 #include "sword2/logic.h"
32 #include "sword2/resman.h"
33 #include "sword2/router.h"
34 #include "sword2/sound.h"
35 
36 namespace Sword2 {
37 
Logic(Sword2Engine * vm)38 Logic::Logic(Sword2Engine *vm) :
39 	_vm(vm), _kills(0), _currentRunList(0), _moviePlayer(0),
40 	_smackerLeadIn(0), _smackerLeadOut(0), _sequenceTextLines(0),
41 	_speechTime(0), _animId(0), _speechAnimType(0), _leftClickDelay(0),
42 	_rightClickDelay(0), _officialTextNumber(0), _speechTextBlocNo(0) {
43 
44 	_scriptVars = NULL;
45 	memset(_eventList, 0, sizeof(_eventList));
46 	memset(_syncList, 0, sizeof(_syncList));
47 	_router = new Router(_vm);
48 
49 	_cycleSkip = false;
50 	_speechRunning = false;
51 
52 	setupOpcodes();
53 }
54 
~Logic()55 Logic::~Logic() {
56 	delete _router;
57 }
58 
59 /**
60  * Do one cycle of the current session.
61  */
62 
processSession()63 int Logic::processSession() {
64 	// might change during the session, so take a copy here
65 	uint32 run_list = _currentRunList;
66 
67 	_pc = 0;	// first object in list
68 
69 	// by minusing the pc we can cause an immediate cessation of logic
70 	// processing on the current list
71 
72 	while (_pc != 0xffffffff) {
73 		byte *game_object_list, *head, *raw_script_ad, *raw_data_ad;
74 		uint32 level, ret, script, id;
75 
76 		game_object_list = _vm->_resman->openResource(run_list) + ResHeader::size();
77 
78 		assert(_vm->_resman->fetchType(run_list) == RUN_LIST);
79 
80 		// read the next id
81 		id = READ_LE_UINT32(game_object_list + 4 * _pc);
82 		_pc++;
83 
84 		writeVar(ID, id);
85 
86 		_vm->_resman->closeResource(run_list);
87 
88 		if (!id) {
89 			// End of list - end the session naturally
90 			return 0;
91 		}
92 
93 		assert(_vm->_resman->fetchType(id) == GAME_OBJECT);
94 
95 		head = _vm->_resman->openResource(id);
96 		_curObjectHub.setAddress(head + ResHeader::size());
97 
98 		level = _curObjectHub.getLogicLevel();
99 
100 		debug(5, "Level %d id(%d) pc(%d)",
101 			level,
102 			_curObjectHub.getScriptId(level),
103 			_curObjectHub.getScriptPc(level));
104 
105 		// Do the logic for this object. We keep going until a function
106 		// says to stop - remember, system operations are run via
107 		// function calls to drivers now.
108 
109 		do {
110 			// There is a distinction between running one of our
111 			// own scripts and that of another object.
112 
113 			level = _curObjectHub.getLogicLevel();
114 			script = _curObjectHub.getScriptId(level);
115 
116 			if (script / SIZE == readVar(ID)) {
117 				// It's our own script
118 
119 				debug(5, "Run script %d pc=%d",
120 					script / SIZE,
121 					_curObjectHub.getScriptPc(level));
122 
123 				// This is the script data. Script and data
124 				// object are the same.
125 
126 				raw_script_ad = head;
127 
128 				ret = runScript2(raw_script_ad, raw_script_ad, _curObjectHub.getScriptPcPtr(level));
129 			} else {
130 				// We're running the script of another game
131 				// object - get our data object address.
132 
133 				uint8 type = _vm->_resman->fetchType(script / SIZE);
134 
135 				assert(type == GAME_OBJECT || type == SCREEN_MANAGER);
136 
137 				raw_script_ad = _vm->_resman->openResource(script / SIZE);
138 				raw_data_ad = head;
139 
140 				ret = runScript2(raw_script_ad, raw_data_ad, _curObjectHub.getScriptPcPtr(level));
141 
142 				_vm->_resman->closeResource(script / SIZE);
143 
144 				// reset to us for service script
145 				raw_script_ad = raw_data_ad;
146 			}
147 
148 			if (ret == 1) {
149 				level = _curObjectHub.getLogicLevel();
150 
151 				// The script finished - drop down a level
152 				if (level) {
153 					_curObjectHub.setLogicLevel(level - 1);
154 				} else {
155 					// Hmmm, level 0 terminated :-| Let's
156 					// be different this time and simply
157 					// let it restart next go :-)
158 
159 					// Note that this really does happen a
160 					// lot, so don't make it a warning.
161 
162 					debug(5, "object %d script 0 terminated", id);
163 
164 					// reset to rerun, drop out for a cycle
165 					_curObjectHub.setScriptPc(level, _curObjectHub.getScriptId(level) & 0xffff);
166 					ret = 0;
167 				}
168 			} else if (ret > 2) {
169 				error("processSession: illegal script return type %d", ret);
170 			}
171 
172 			// if ret == 2 then we simply go around again - a new
173 			// script or subroutine will kick in and run
174 
175 			// keep processing scripts until 0 for quit is returned
176 		} while (ret);
177 
178 		// Any post logic system requests to go here
179 
180 		// Clear any syncs that were waiting for this character - it
181 		// has used them or now looses them
182 
183 		clearSyncs(readVar(ID));
184 
185 		if (_pc != 0xffffffff) {
186 			// The session is still valid so run the graphics/mouse
187 			// service script
188 			runScript(raw_script_ad, raw_script_ad, 0);
189 		}
190 
191 		// and that's it so close the object resource
192 
193 		_vm->_resman->closeResource(readVar(ID));
194 	}
195 
196 	// Leaving a room so remove all ids that must reboot correctly. Then
197 	// restart the loop.
198 
199 	for (uint32 i = 0; i < _kills; i++)
200 		_vm->_resman->remove(_objectKillList[i]);
201 
202 	resetKillList();
203 	return 1;
204 }
205 
206 /**
207  * Bring an immediate halt to the session and cause a new one to start without
208  * a screen update.
209  */
210 
expressChangeSession(uint32 sesh_id)211 void Logic::expressChangeSession(uint32 sesh_id) {
212 	// Set new session and force the old one to quit.
213 	_currentRunList = sesh_id;
214 	_pc = 0xffffffff;
215 
216 	// Reset now in case we double-clicked an exit prior to changing screen
217 	writeVar(EXIT_FADING, 0);
218 
219 	// We're trashing the list - presumably to change room. In theory,
220 	// sync waiting in the list could be left behind and never removed -
221 	// so we trash the lot
222 	memset(_syncList, 0, sizeof(_syncList));
223 
224 	// Various clean-ups
225 	_router->clearWalkGridList();
226 	_vm->_sound->clearFxQueue(false);
227 	_router->freeAllRouteMem();
228 }
229 
230 /**
231  * @return The private _currentRunList variable.
232  */
233 
getRunList()234 uint32 Logic::getRunList() {
235 	return _currentRunList;
236 }
237 
238 /**
239  * Move the current object up a level. Called by fnGosub command. Remember:
240  * only the logic object has access to _curObjectHub.
241  */
242 
logicUp(uint32 new_script)243 void Logic::logicUp(uint32 new_script) {
244 	debug(5, "new pc = %d", new_script & 0xffff);
245 
246 	// going up a level - and we'll keep going this cycle
247 	_curObjectHub.setLogicLevel(_curObjectHub.getLogicLevel() + 1);
248 
249 	assert(_curObjectHub.getLogicLevel() < 3);	// Can be 0, 1, 2
250 	logicReplace(new_script);
251 }
252 
253 /**
254  * Force the level to one.
255  */
256 
logicOne(uint32 new_script)257 void Logic::logicOne(uint32 new_script) {
258 	_curObjectHub.setLogicLevel(1);
259 	logicReplace(new_script);
260 }
261 
262 /**
263  * Change current logic. Script must quit with a TERMINATE directive, which
264  * does not write to &pc
265  */
266 
logicReplace(uint32 new_script)267 void Logic::logicReplace(uint32 new_script) {
268 	uint32 level = _curObjectHub.getLogicLevel();
269 
270 	_curObjectHub.setScriptId(level, new_script);
271 	_curObjectHub.setScriptPc(level, new_script & 0xffff);
272 }
273 
resetKillList()274 void Logic::resetKillList() {
275 	_kills = 0;
276 }
277 
278 /**
279  * Read current location number from script vars
280  */
281 
getLocationNum()282 uint32 Logic::getLocationNum() {
283 	return readVar(LOCATION);
284 }
285 
286 } // End of namespace Sword2
287