--[[
	© 2011 CloudSixteen.com do not share, re-distribute or modify
	without permission of its author (kurozael@gmail.com).
--]]

-- Called when OpenAura has loaded all of the entities.
function openAura.schema:OpenAuraInitPostEntity()
	self:LoadRationDispensers();
	self:LoadCombineLocks();
	self:LoadObjectives();
	self:LoadRadios();
	self:LoadNPCs();
end;

-- Called when data should be saved.
function openAura.schema:SaveData()
	if (self.combineObjectives) then
		openAura:SaveSchemaData("objectives", self.combineObjectives);
	end;
end;

-- Called just after data should be saved.
function openAura.schema:PostSaveData()
	self:SaveRationDispensers();
	self:SaveCombineLocks();
	self:SaveRadios();
	self:SaveNPCs();
end;

-- Called when a player's default model is needed.
function openAura.schema:GetPlayerDefaultModel(player)
	if ( self:IsPlayerCombineRank(player, "GHOST") ) then
		return "models/eliteghostcp.mdl";
	elseif ( self:IsPlayerCombineRank(player, "OfC") ) then
		return "models/policetrench.mdl";
	elseif ( self:IsPlayerCombineRank(player, "DvL") ) then
		return "models/eliteshockcp.mdl";
	elseif ( self:IsPlayerCombineRank(player, "SeC") ) then
		return "models/sect_police2.mdl";
	end;
end;

-- Called when an entity's menu option should be handled.
function openAura.schema:EntityHandleMenuOption(player, entity, option, arguments)
	if (entity:GetClass() == "prop_ragdoll" and arguments == "aura_corpseLoot") then
		if (!entity.inventory) then entity.inventory = {}; end;
		if (!entity.cash) then entity.cash = 0; end;
		
		local entityPlayer = openAura.entity:GetPlayer(entity);
		
		if ( !entityPlayer or !entityPlayer:Alive() ) then
			player:EmitSound("physics/body/body_medium_impact_soft"..math.random(1, 7)..".wav");
			
			openAura.player:OpenStorage( player, {
				name = "Corpse",
				weight = 8,
				entity = entity,
				distance = 192,
				cash = entity.cash,
				inventory = entity.inventory,
				OnGiveCash = function(player, storageTable, cash)
					entity.cash = storageTable.cash;
				end,
				OnTakeCash = function(player, storageTable, cash)
					entity.cash = storageTable.cash;
				end
			} );
		end;
	elseif (entity:GetClass() == "aura_belongings" and arguments == "aura_belongingsOpen") then
		player:EmitSound("physics/body/body_medium_impact_soft"..math.random(1, 7)..".wav");
		
		openAura.player:OpenStorage( player, {
			name = "Belongings",
			weight = 100,
			entity = entity,
			distance = 192,
			cash = entity.cash,
			inventory = entity.inventory,
			OnGiveCash = function(player, storageTable, cash)
				entity.cash = storageTable.cash;
			end,
			OnTakeCash = function(player, storageTable, cash)
				entity.cash = storageTable.cash;
			end,
			OnClose = function(player, storageTable, entity)
				if ( IsValid(entity) ) then
					if ( (!entity.inventory and !entity.cash) or (table.Count(entity.inventory) == 0 and entity.cash == 0) ) then
						entity:Explode(entity:BoundingRadius() * 2);
						entity:Remove();
					end;
				end;
			end,
			CanGive = function(player, storageTable, itemTable)
				return false;
			end
		} );
	elseif (entity:GetClass() == "aura_breach") then
		entity:CreateDummyBreach();
		entity:BreachEntity(player);
	elseif (entity:GetClass() == "aura_radio") then
		if (option == "Set Frequency" and type(arguments) == "string") then
			if ( string.find(arguments, "^%d%d%d%.%d$") ) then
				local start, finish, decimal = string.match(arguments, "(%d)%d(%d)%.(%d)");
				
				start = tonumber(start);
				finish = tonumber(finish);
				decimal = tonumber(decimal);
				
				if (start == 1 and finish > 0 and finish < 10 and decimal > 0 and decimal < 10) then
					entity:SetFrequency(arguments);
					
					openAura.player:Notify(player, "You have set this stationary radio's arguments to "..arguments..".");
				else
					openAura.player:Notify(player, "The radio arguments must be between 101.1 and 199.9!");
				end;
			else
				openAura.player:Notify(player, "The radio arguments must look like xxx.x!");
			end;
		elseif (arguments == "aura_radioToggle") then
			entity:Toggle();
		elseif (arguments == "aura_radioTake") then
			local success, fault = player:UpdateInventory("stationary_radio", 1);
			
			if (!success) then
				openAura.player:Notify(entity, fault);
			else
				entity:Remove();
			end;
		end;
	end;
end;

-- Called when an NPC has been killed.
function openAura.schema:OnNPCKilled(npc, attacker, inflictor)
	for k, v in pairs(self.scanners) do
		local scanner = v[1];
		local player = k;
		
		if (IsValid(player) and IsValid(scanner) and scanner == npc) then
			openAura:CalculateSpawnTime(player, inflictor, attacker);
			
			npc:EmitSound("npc/scanner/scanner_explode_crash2.wav");
			
			self:PlayerDeath(player, inflictor, attacker, true);
			self:ResetPlayerScanner(player);
		end;
	end;
end;

-- Called when a player's visibility should be set up.
function openAura.schema:SetupPlayerVisibility(player)
	if ( self.scanners[player] ) then
		local scanner = self.scanners[player][1];
		
		if ( IsValid(scanner) ) then
			AddOriginToPVS( scanner:GetPos() );
		end;
	end;
end;

-- Called when a player's drop weapon info should be adjusted.
function openAura.schema:PlayerAdjustDropWeaponInfo(player, info)
	if (openAura.player:GetWeaponClass(player) == info.itemTable.weaponClass) then
		info.position = player:GetShootPos();
		info.angles = player:GetAimVector():Angle();
	else
		local gearTable = {
			openAura.player:GetGear(player, "Throwable"),
			openAura.player:GetGear(player, "Secondary"),
			openAura.player:GetGear(player, "Primary"),
			openAura.player:GetGear(player, "Melee")
		};
		
		for k, v in pairs(gearTable) do
			if ( IsValid(v) ) then
				local gearItemTable = v:GetItem();
				
				if (gearItemTable and gearItemTable.weaponClass == info.itemTable.weaponClass) then
					local position, angles = v:GetRealPosition();
					
					if (position and angles) then
						info.position = position;
						info.angles = angles;
						
						break;
					end;
				end;
			end;
		end;
	end;
end;

-- Called when a player has been given a weapon.
function openAura.schema:PlayerGivenWeapon(player, class, uniqueID, forceReturn)
	local itemTable = openAura.item:GetWeapon(class, uniqueID);
	
	if (openAura.item:IsWeapon(itemTable) and !itemTable.fakeWeapon) then
		if ( !itemTable:IsMeleeWeapon() and !itemTable:IsThrowableWeapon() ) then
			if (itemTable.weight <= 2) then
				openAura.player:CreateGear(player, "Secondary", itemTable);
			else
				openAura.player:CreateGear(player, "Primary", itemTable);
			end;
		elseif ( itemTable:IsThrowableWeapon() ) then
			openAura.player:CreateGear(player, "Throwable", itemTable);
		else
			openAura.player:CreateGear(player, "Melee", itemTable);
		end;
	end;
end;

-- Called when a player uses a door.
function openAura.schema:PlayerUseDoor(player, door)
	if (string.lower( game.GetMap() ) == "rp_c18_v1") then
		local name = string.lower( door:GetName() );
		
		if (name == "nxs_brnroom" or name == "nxs_brnroom2" or name == "openAura_al_door1"
		or name == "openAura_al_door2" or name == "nxs_brnbcroom") then
			local curTime = CurTime();
			
			if (!door.nextAutoClose or curTime >= door.nextAutoClose) then
				door:Fire("Close", "", 10);
				door.nextAutoClose = curTime + 10;
			end;
		end;
	end;
end;

-- Called when a player has an unknown inventory item.
function openAura.schema:PlayerHasUnknownInventoryItem(player, inventory, item, amount)
	if (item == "radio") then
		inventory["handheld_radio"] = amount;
	end;
end;

-- Called when a player's default inventory is needed.
function openAura.schema:GetPlayerDefaultInventory(player, character, inventory)
	if (character.faction == FACTION_ADMIN) then
		inventory["handheld_radio"] = 1;
	elseif (character.faction == FACTION_MPF) then
		inventory["handheld_radio"] = 1;
		inventory["weapon_pistol"] = 1;
		inventory["ammo_pistol"] = 2;
	elseif (character.faction == FACTION_OTA) then
		inventory["handheld_radio"] = 1;
		inventory["weapon_pistol"] = 1;
		inventory["ammo_pistol"] = 1;
		inventory["weapon_ar2"] = 1;
		inventory["ammo_ar2"] = 1;
	else
		inventory["suitcase"] = 1;
	end;
end;

-- Called when a player's typing display has started.
function openAura.schema:PlayerStartTypingDisplay(player, code)
	if ( openAura.schema:PlayerIsCombine(player) ) then
		if (code == "n" or code == "y" or code == "w" or code == "r") then
			if (!player.typingBeep) then
				player.typingBeep = true;
				
				player:EmitSound("npc/overwatch/radiovoice/on1.wav");
			end;
		end;
	end;
end;

-- Called when a player's typing display has finished.
function openAura.schema:PlayerFinishTypingDisplay(player, textTyped)
	if (openAura.schema:PlayerIsCombine(player) and textTyped) then
		if (player.typingBeep) then
			player:EmitSound("npc/overwatch/radiovoice/off4.wav");
		end;
	end;
	
	player.typingBeep = nil;
end;

-- Called when a player stuns an entity.
function openAura.schema:PlayerStunEntity(player, entity)
	local target = openAura.entity:GetPlayer(entity);
	local strength = openAura.attributes:Fraction(player, ATB_STRENGTH, 12, 6);
	
	player:ProgressAttribute(ATB_STRENGTH, 0.5, true);
	
	if ( target and target:Alive() ) then
		local curTime = CurTime();
		
		if ( target.nextStunInfo and curTime <= target.nextStunInfo[2] ) then
			target.nextStunInfo[1] = target.nextStunInfo[1] + 1;
			target.nextStunInfo[2] = curTime + 2;
			
			if (target.nextStunInfo[1] == 3) then
				openAura.player:SetRagdollState( target, RAGDOLL_KNOCKEDOUT, openAura.config:Get("knockout_time"):Get() );
			end;
		else
			target.nextStunInfo = {0, curTime + 2};
		end;
		
		target:ViewPunch( Angle(12 + strength, 0, 0) );
		
		umsg.Start("aura_Stunned", target);
			umsg.Float(0.5);
		umsg.End();
	end;
end;

-- Called when a player's weapons should be given.
function openAura.schema:PlayerGiveWeapons(player)
	if (player:QueryCharacter("faction") == FACTION_MPF) then
		openAura.player:GiveSpawnWeapon(player, "aura_stunstick");
	end;
end;

-- Called when a player's inventory item has been updated.
function openAura.schema:PlayerInventoryItemUpdated(player, itemTable, amount, force)
	local clothes = player:GetCharacterData("clothes");
	
	if (clothes == itemTable.index) then
		if ( !player:HasItem(itemTable.uniqueID) ) then
			itemTable:OnChangeClothes(player, false);
			
			player:SetCharacterData("clothes", nil);
		end;
	end;
end;

-- Called when a player switches their flashlight on or off.
function openAura.schema:PlayerSwitchFlashlight(player, on)
	if ( on and (self.scanners[player] or player:GetSharedVar("tied") != 0) ) then
		return false;
	end;
end;

-- Called when a player's storage should close.
function openAura.schema:PlayerStorageShouldClose(player, storage)
	local entity = player:GetStorageEntity();
	
	if (player.searching and entity:IsPlayer() and entity:GetSharedVar("tied") == 0) then
		return true;
	end;
end;

-- Called when a player attempts to spray their tag.
function openAura.schema:PlayerSpray(player)
	if (!player:HasItem("spray_can") or player:GetSharedVar("tied") != 0) then
		return true;
	end;
end;

-- Called when a player presses F3.
function openAura.schema:ShowSpare1(player)
	openAura.player:RunOpenAuraCommand(player, "InvAction", "zip_tie", "use");
end;

-- Called when a player presses F4.
function openAura.schema:ShowSpare2(player)
	openAura.player:RunOpenAuraCommand(player, "CharSearch");
end;

-- Called when a player's footstep sound should be played.
function openAura.schema:PlayerFootstep(player, position, foot, sound, volume, recipientFilter)
	local running = nil;
	local clothes = player:GetCharacterData("clothes");
	local model = string.lower( player:GetModel() );
	
	if (clothes) then
		local itemTable = openAura.item:Get(clothes);
		
		if (itemTable) then
			if ( player:IsRunning() or player:IsJogging() ) then
				if (itemTable.runSound) then
					if (type(itemTable.runSound) == "table") then
						sound = itemTable.runSound[ math.random(1, #itemTable.runSound) ];
					else
						sound = itemTable.runSound;
					end;
				end;
			elseif (itemTable.walkSound) then
				if (type(itemTable.walkSound) == "table") then
					sound = itemTable.walkSound[ math.random(1, #itemTable.walkSound) ];
				else
					sound = itemTable.walkSound;
				end;
			end;
		end;
	end;
	
	if ( player:IsRunning() or player:IsJogging() ) then
		running = true;
	end;
	
	if (running) then
		if ( string.find(model, "metrocop") or string.find(model, "shockcp") or string.find(model, "ghostcp") or string.find(model, "police") ) then
			if (foot == 0) then
				local randomSounds = {1, 3, 5};
				local randomNumber = math.random(1, 3);
				
				sound = "npc/metropolice/gear"..randomSounds[randomNumber]..".wav";
			else
				local randomSounds = {2, 4, 6};
				local randomNumber = math.random(1, 3);
				
				sound = "npc/metropolice/gear"..randomSounds[randomNumber]..".wav";
			end;
		elseif ( string.find(model, "combine") ) then
			if (foot == 0) then
				local randomSounds = {1, 3, 5};
				local randomNumber = math.random(1, 3);
				
				sound = "npc/combine_soldier/gear"..randomSounds[randomNumber]..".wav";
			else
				local randomSounds = {2, 4, 6};
				local randomNumber = math.random(1, 3);
				
				sound = "npc/combine_soldier/gear"..randomSounds[randomNumber]..".wav";
			end;
		end;
	end;
	
	player:EmitSound(sound);
	
	return true;
end;

-- Called when a player attempts to spawn a prop.
function openAura.schema:PlayerSpawnProp(player, model)
	if ( !player:IsAdmin() and openAura.config:Get("cwu_props"):Get() ) then
		if (player:QueryCharacter("faction") == FACTION_CITIZEN) then
			
			if (player:GetCharacterData("customclass") != "Civil Worker's Union") then
				model = string.Replace(model, "\\", "/");
				model = string.Replace(model, "//", "/");
				model = string.lower(model);
				
				if ( string.find(model, "bed") ) then
					openAura.player:Notify(player, "You are not in the Civil Worker's Union!");
					
					return false;
				end;
				
				for k, v in pairs(self.cwuProps) do
					if (string.lower(v) == model) then
						openAura.player:Notify(player, "You are not in the Civil Worker's Union!");
						
						return false;
					end;
				end;
			end;
		end;
	end;
end;

-- Called when a player spawns an object.
function openAura.schema:PlayerSpawnObject(player)
	if ( player:GetSharedVar("tied") != 0 or self.scanners[player] ) then
		openAura.player:Notify(player, "You don't have permission to do this right now!");
		
		return false;
	end;
end;

-- Called when a player's character data should be restored.
function openAura.schema:PlayerRestoreCharacterData(player, data)
	if (!self:PlayerIsCombine(player) and player:QueryCharacter("faction") != FACTION_ADMIN) then
		if (!data["citizenid"] or string.len( tostring( data["citizenid"] ) ) == 4) then
			data["citizenid"] = openAura:ZeroNumberToDigits(math.random(1, 99999), 5);
		end;
	end;
end;

-- Called when a player attempts to breach an entity.
function openAura.schema:PlayerCanBreachEntity(player, entity)
	if (string.lower( entity:GetClass() ) == "func_door_rotating") then
		return false;
	end;
	
	if ( openAura.entity:IsDoor(entity) ) then
		if ( !openAura.entity:IsDoorFalse(entity) ) then
			return true;
		end;
	end;
end;

-- Called when a player attempts to restore a recognised name.
function openAura.schema:PlayerCanRestoreRecognisedName(player, target)
	if ( self:PlayerIsCombine(target) ) then
		return false;
	end;
end;

-- Called when a player attempts to save a recognised name.
function openAura.schema:PlayerCanSaveRecognisedName(player, target)
	if ( self:PlayerIsCombine(target) ) then
		return false;
	end;
end;

-- Called when a player attempts to use the radio.
function openAura.schema:PlayerCanRadio(player, text, listeners, eavesdroppers)
	if ( player:HasItem("handheld_radio") or self.scanners[player] ) then
		if ( !player:GetCharacterData("frequency") ) then
			openAura.player:Notify(player, "You need to set the radio frequency first!");
			
			return false;
		end;
	else
		openAura.player:Notify(player, "You do not own a radio!");
		
		return false;
	end;
end;

-- Called when a player's character has initialized.
function openAura.schema:PlayerCharacterInitialized(player)
	local faction = player:QueryCharacter("faction");
	
	if ( self:PlayerIsCombine(player) ) then
		
		for k, v in pairs(openAura.class.stored) do
			if ( v.factions and table.HasValue(v.factions, faction) ) then
				if ( #_team.GetPlayers(v.index) < openAura.class:GetLimit(v.name) ) then
					if ( v.index == CLASS_MPS and self:IsPlayerCombineRank(player, "SCN") ) then
						openAura.class:Set(player, v.index); break;
					elseif ( v.index == CLASS_MPR and self:IsPlayerCombineRank(player, "RCT") ) then
						openAura.class:Set(player, v.index); break;
					elseif ( v.index == CLASS_EMP and self:IsPlayerCombineRank(player, "EpU") ) then
						openAura.class:Set(player, v.index); break;
					elseif ( v.index == CLASS_EOW and self:IsPlayerCombineRank(player, "EOW") ) then
						openAura.class:Set(player, v.index); break;
					end;
				end;
			end;
		end;
	elseif (faction == FACTION_CITIZEN) then
		self:AddCombineDisplayLine( "Rebuilding citizen manifest...", Color(255, 100, 255, 255) );
	end;
end;

-- Called when a player's name has changed.
function openAura.schema:PlayerNameChanged(player, previousName, newName)
	if ( self:PlayerIsCombine(player) ) then
		local faction = player:QueryCharacter("faction");
		
		if (faction == FACTION_OTA) then
			if ( !self:IsStringCombineRank(previousName, "OWS") and self:IsStringCombineRank(newName, "OWS") ) then
				openAura.class:Set(player, CLASS_OWS);
			elseif ( !self:IsStringCombineRank(previousName, "EOW") and self:IsStringCombineRank(newName, "EOW") ) then
				openAura.class:Set(player, CLASS_EOW);
			end;
		elseif (faction == FACTION_MPF) then
			if ( !self:IsStringCombineRank(previousName, "SCN") and self:IsStringCombineRank(newName, "SCN") ) then
				openAura.class:Set(player, CLASS_MPS, true);
				
				self:MakePlayerScanner(player, true);
			elseif ( !self:IsStringCombineRank(previousName, "RCT") and self:IsStringCombineRank(newName, "RCT") ) then
				openAura.class:Set(player, CLASS_MPR);
			elseif ( !self:IsStringCombineRank(previousName, "EpU") and self:IsStringCombineRank(newName, "EpU") ) then
				openAura.class:Set(player, CLASS_EMP);
			elseif ( !self:IsStringCombineRank(previousName, "OfC") and self:IsStringCombineRank(newName, "OfC") ) then
				player:SetModel("models/policetrench.mdl");
			elseif ( !self:IsStringCombineRank(previousName, "DvL") and self:IsStringCombineRank(newName, "DvL") ) then
				player:SetModel("models/eliteshockcp.mdl");
			elseif ( !self:IsStringCombineRank(previousName, "SeC") and self:IsStringCombineRank(newName, "SeC") ) then
				player:SetModel("models/sect_police2.mdl");
			elseif ( !self:IsStringCombineRank(newName, "RCT") ) then
				if (player:Team() != CLASS_MPU) then
					openAura.class:Set(player, CLASS_MPU);
				end;
			end;
			
			if ( !self:IsStringCombineRank(previousName, "GHOST") and self:IsStringCombineRank(newName, "GHOST") ) then
				player:SetModel("models/eliteghostcp.mdl");
			end;
		end;
	end;
end;

-- Called when a player attempts to use an entity in a vehicle.
function openAura.schema:PlayerCanUseEntityInVehicle(player, entity, vehicle)
	if ( entity:IsPlayer() or openAura.entity:IsPlayerRagdoll(entity) ) then
		return true;
	end;
end;

-- Called when a player presses a key.
function openAura.schema:KeyPress(player, key)
	if (key == IN_USE) then
		if ( !self.scanners[player] ) then
			local untieTime = openAura.schema:GetDexterityTime(player);
			local target = player:GetEyeTraceNoCursor().Entity;
			local entity = target;
			
			if ( IsValid(target) ) then
				target = openAura.entity:GetPlayer(target);
				
				if (target and player:GetSharedVar("tied") == 0) then
					if (target:GetShootPos():Distance( player:GetShootPos() ) <= 192) then
						if (target:GetSharedVar("tied") != 0) then
							openAura.player:SetAction(player, "untie", untieTime);
							
							openAura.player:EntityConditionTimer(player, target, entity, untieTime, 192, function()
								return player:Alive() and !player:IsRagdolled() and player:GetSharedVar("tied") == 0;
							end, function(success)
								if (success) then
									self:TiePlayer(target, false);
									
									player:ProgressAttribute(ATB_DEXTERITY, 15, true);
								end;
								
								openAura.player:SetAction(player, "untie", false);
							end);
						end;
					end;
				end;
			end;
		end;
	elseif (key == IN_ATTACK or key == IN_ATTACK2) then
		if ( self.scanners[player] ) then
			local scanner = self.scanners[player][1];
			
			if ( IsValid(scanner) ) then
				player.nextScannerSound = CurTime() + math.random(8, 48);
				
				scanner:EmitSound( self.scannerSounds[ math.random(1, #self.scannerSounds) ] );
			end;
		end;
	elseif (key == IN_RELOAD) then
		if ( self.scanners[player] ) then
			local scanner = self.scanners[player][1];
			local curTime = CurTime();
			local marker = self.scanners[player][2];
			
			if ( IsValid(scanner) ) then
				local position = scanner:GetPos();
				
				for k, v in ipairs( ents.FindInSphere(position, 384) ) do
					if ( v:IsPlayer() and v:HasInitialized() and !self:PlayerIsCombine(v) ) then
						local playerPosition = v:GetPos();
						local scannerDot = scanner:GetAimVector():Dot( (playerPosition - position):Normalize() );
						local playerDot = v:GetAimVector():Dot( (position - playerPosition):Normalize() );
						local threshold = 0.2 + math.Clamp( (0.6 / 384) * playerPosition:Distance(position), 0, 0.6 );
						
						if (openAura.player:CanSeeEntity( v, scanner, 0.9, {marker} ) and playerDot >= threshold and scannerDot >= threshold) then
							if (player != v) then
								if (v:QueryCharacter("faction") == FACTION_CITIZEN) then
									if ( !v:GetForcedAnimation() ) then
										v:SetForcedAnimation("photo_react_blind", 2, function(player)
											player:Freeze(true);
										end, function(player)
											player:Freeze(false);
										end);
									end;
								end;
								
								umsg.Start("aura_Stunned", v);
									umsg.Float(3);
								umsg.End();
							end;
						end;
					end;
				end;
				
				scanner:EmitSound("npc/scanner/scanner_photo1.wav");
			end;
		end;
	elseif (key == IN_WALK) then
		if ( self.scanners[player] ) then
			openAura.player:RunOpenAuraCommand(player, "CharFollow");
		end;
	end;
end;

-- Called each tick.
function openAura.schema:Tick()
	for k, v in pairs(self.scanners) do
		local scanner = v[1];
		local marker = v[2];
		
		if ( IsValid(k) ) then
			if ( IsValid(scanner) and IsValid(marker) ) then
				if ( k:KeyDown(IN_FORWARD) ) then
					local position = scanner:GetPos() + (scanner:GetForward() * 25) + (scanner:GetUp() * -64);
					
					if ( k:KeyDown(IN_SPEED) ) then
						marker:SetPos( position + (k:GetAimVector() * 64) );
					else
						marker:SetPos( position + (k:GetAimVector() * 128) );
					end;
					
					scanner.followTarget = nil;
				end;
				
				if ( IsValid(scanner.followTarget) ) then
					scanner:Input("SetFollowTarget", scanner.followTarget, scanner.followTarget, "!activator");
				else
					scanner:Fire("SetFollowTarget", "marker_"..k:UniqueID(), 0);
				end;
				
				if ( scannerClass == "npc_cscanner" and self:IsPlayerCombineRank(k, "SYNTH") ) then
					self:MakePlayerScanner(k, true);
				elseif ( scannerClass == "npc_clawscanner" and !self:IsPlayerCombineRank(k, "SYNTH") ) then
					self:MakePlayerScanner(k, true);
				end;
			else
				self:ResetPlayerScanner(k);
			end;
		else
			if ( IsValid(scanner) ) then
				scanner:Remove();
			end;
			
			if ( IsValid(marker) ) then
				marker:Remove();
			end;
			
			self.scanners[k] = nil;
		end;
	end;
end;

-- Called when a player's health is set.
function openAura.schema:PlayerHealthSet(player, newHealth, oldHealth)
	if ( self.scanners[player] ) then
		if ( IsValid( self.scanners[player][1] ) ) then
			self.scanners[player][1]:SetHealth(newHealth);
		end;
	end;
end;

-- Called when a player attempts to be given a weapon.
function openAura.schema:PlayerCanBeGivenWeapon(player, class, uniqueID, forceReturn)
	if ( self.scanners[player] ) then
		return false;
	end;
end;

-- Called each frame that a player is dead.
function openAura.schema:PlayerDeathThink(player)
	if ( player:GetCharacterData("permakilled") ) then
		return true;
	end;
end;

-- Called when a player attempts to switch to a character.
function openAura.schema:PlayerCanSwitchCharacter(player, character)
	if ( player:GetCharacterData("permakilled") ) then
		return true;
	end;
end;

-- Called when a player's death info should be adjusted.
function openAura.schema:PlayerAdjustDeathInfo(player, info)
	if ( player:GetCharacterData("permakilled") ) then
		info.spawnTime = 0;
	end;
end;

-- Called when a player's character screen info should be adjusted.
function openAura.schema:PlayerAdjustCharacterScreenInfo(player, character, info)
	if ( character.data["permakilled"] ) then
		info.details = "This character is permanently killed.";
	end;
	
	if (info.faction == FACTION_OTA) then
		if ( self:IsStringCombineRank(info.name, "EOW") ) then
			info.model = "models/combine_super_soldier.mdl";
		end;
	elseif ( self:IsCombineFaction(info.faction) ) then
		if ( self:IsStringCombineRank(info.name, "SCN") ) then
			if ( self:IsStringCombineRank(info.name, "SYNTH") ) then
				info.model = "models/shield_scanner.mdl";
			else
				info.model = "models/combine_scanner.mdl";
			end;
		elseif ( self:IsStringCombineRank(info.name, "SeC") ) then
			info.model = "models/sect_police2.mdl";
		elseif ( self:IsStringCombineRank(info.name, "DvL") ) then
			info.model = "models/eliteshockcp.mdl";
		elseif ( self:IsStringCombineRank(info.name, "EpU") ) then
			info.model = "models/leet_police2.mdl";
		elseif ( self:IsStringCombineRank(info.name, "OfC") ) then
			info.model = "models/policetrench.mdl";
		end;
		
		if ( self:IsStringCombineRank(info.name, "GHOST") ) then
			info.model = "models/eliteghostcp.mdl";
		end;
	end;
	
	if ( character.data["customclass"] ) then
		info.customClass = character.data["customclass"];
	end;
end;

-- Called when a chat box message has been added.
function openAura.schema:ChatBoxMessageAdded(info)
	if (info.class == "ic") then
		local eavesdroppers = {};
		local talkRadius = openAura.config:Get("talk_radius"):Get();
		local listeners = {};
		local players = _player.GetAll();
		local radios = ents.FindByClass("aura_radio");
		local data = {};
		
		for k, v in ipairs(radios) do
			if (!v:IsOff() and info.speaker:GetPos():Distance( v:GetPos() ) <= 80) then
				local frequency = v:GetSharedVar("frequency");
				
				if (frequency != "") then
					info.shouldSend = false;
					info.listeners = {};
					data.frequency = frequency;
					data.position = v:GetPos();
					data.entity = v;
					
					break;
				end;
			end;
		end;
		
		if (IsValid(data.entity) and data.frequency != "") then
			for k, v in ipairs(players) do
				if ( v:HasInitialized() and v:Alive() and !v:IsRagdolled(RAGDOLL_FALLENOVER) ) then
					if ( ( v:GetCharacterData("frequency") == data.frequency and v:GetSharedVar("tied") == 0
					and v:HasItem("handheld_radio") ) or info.speaker == v ) then
						listeners[v] = v;
					elseif (v:GetPos():Distance(data.position) <= talkRadius) then
						eavesdroppers[v] = v;
					end;
				end;
			end;
			
			for k, v in ipairs(radios) do
				local radioPosition = v:GetPos();
				local radioFrequency = v:GetSharedVar("frequency");
				
				if (!v:IsOff() and radioFrequency == data.frequency) then
					for k2, v2 in ipairs(players) do
						if ( v2:HasInitialized() and !listeners[v2] and !eavesdroppers[v2] ) then
							if ( v2:GetPos():Distance(radioPosition) <= (talkRadius * 2) ) then
								eavesdroppers[v2] = v2;
							end;
						end;
						
						break;
					end;
				end;
			end;
			
			if (table.Count(listeners) > 0) then
				openAura.chatBox:Add(listeners, info.speaker, "radio", info.text);
			end;
			
			if (table.Count(eavesdroppers) > 0) then
				openAura.chatBox:Add(eavesdroppers, info.speaker, "radio_eavesdrop", info.text);
			end;
			
			table.Merge(info.listeners, listeners);
			table.Merge(info.listeners, eavesdroppers);
		end;
	end;
	
	if (info.voice) then
		if ( IsValid(info.speaker) and info.speaker:HasInitialized() ) then
			info.speaker:EmitSound(info.voice.sound, info.voice.volume);
		end;
		
		if (info.voice.global) then
			for k, v in pairs(info.listeners) do
				if (v != info.speaker) then
					openAura.player:PlaySound(v, info.voice.sound);
				end;
			end;
		end;
	end;
end;

-- Called when a player has used their radio.
function openAura.schema:PlayerRadioUsed(player, text, listeners, eavesdroppers)
	local newEavesdroppers = {};
	local talkRadius = openAura.config:Get("talk_radius"):Get() * 2;
	local frequency = player:GetCharacterData("frequency");
	
	for k, v in ipairs( ents.FindByClass("aura_radio") ) do
		local radioPosition = v:GetPos();
		local radioFrequency = v:GetSharedVar("frequency");
		
		if (!v:IsOff() and radioFrequency == frequency) then
			for k2, v2 in ipairs( _player.GetAll() ) do
				if ( v2:HasInitialized() and !listeners[v2] and !eavesdroppers[v2] ) then
					if (v2:GetPos():Distance(radioPosition) <= talkRadius) then
						newEavesdroppers[v2] = v2;
					end;
				end;
				
				break;
			end;
		end;
	end;
	
	if (table.Count(newEavesdroppers) > 0) then
		openAura.chatBox:Add(newEavesdroppers, player, "radio_eavesdrop", text);
	end;
end;

-- Called when a player's radio info should be adjusted.
function openAura.schema:PlayerAdjustRadioInfo(player, info)
	for k, v in ipairs( _player.GetAll() ) do
		if ( v:HasInitialized() and v:HasItem("handheld_radio") ) then
			if ( v:GetCharacterData("frequency") == player:GetCharacterData("frequency") ) then
				if (v:GetSharedVar("tied") == 0) then
					info.listeners[v] = v;
				end;
			end;
		end;
	end;
end;

-- Called when a player attempts to use a tool.
function openAura.schema:CanTool(player, trace, tool)
	if ( !openAura.player:HasFlags(player, "w") ) then
		if (string.sub(tool, 1, 5) == "wire_" or string.sub(tool, 1, 6) == "wire2_") then
			player:RunCommand("gmod_toolmode \"\"");
			
			return false;
		end;
	end;
end;

-- Called when a player has been healed.
function openAura.schema:PlayerHealed(player, healer, itemTable)
	if (itemTable.uniqueID == "health_vial") then
		healer:BoostAttribute(itemTable.name, ATB_DEXTERITY, 2, 120);
		healer:ProgressAttribute(ATB_MEDICAL, 15, true);
	elseif (itemTable.uniqueID == "health_kit") then
		healer:BoostAttribute(itemTable.name, ATB_DEXTERITY, 3, 120);
		healer:ProgressAttribute(ATB_MEDICAL, 25, true);
	elseif (itemTable.uniqueID == "bandage") then
		healer:BoostAttribute(itemTable.name, ATB_DEXTERITY, 1, 120);
		healer:ProgressAttribute(ATB_MEDICAL, 5, true);
	end;
end;

-- Called when a player's shared variables should be set.
function openAura.schema:PlayerSetSharedVars(player, curTime)
	player:SetSharedVar( "customClass", player:GetCharacterData("customclass", "") );
	player:SetSharedVar( "citizenID", player:GetCharacterData("citizenid", "") );
	player:SetSharedVar( "clothes", player:GetCharacterData("clothes", 0) );
	player:SetSharedVar( "icon", player:GetCharacterData("icon", "") );
	
	if (player:Alive() and !player:IsRagdolled() and player:GetVelocity():Length() > 0) then
		local inventoryWeight = openAura.inventory:GetWeight(player);
		
		if (inventoryWeight >= openAura.inventory:GetMaximumWeight(player) / 4) then
			player:ProgressAttribute(ATB_STRENGTH, inventoryWeight / 400, true);
		end;
	end;
end;

-- Called at an interval while a player is connected.
function openAura.schema:PlayerThink(player, curTime, infoTable)
	if ( player:Alive() and !player:IsRagdolled() ) then
		if (!player:InVehicle() and player:GetMoveType() == MOVETYPE_WALK) then
			if ( player:IsInWorld() ) then
				if ( !player:IsOnGround() ) then
					player:ProgressAttribute(ATB_ACROBATICS, 0.25, true);
				elseif (infoTable.running) then
					player:ProgressAttribute(ATB_AGILITY, 0.125, true);
				elseif (infoTable.jogging) then
					player:ProgressAttribute(ATB_AGILITY, 0.0625, true);
				end;
			end;
		end;
	end;
	
	if ( openAura.player:HasAnyFlags(player, "vV") ) then
		if (infoTable.wages == 0) then
			infoTable.wages = 20;
		end;
	end;
	
	if ( self.scanners[player] ) then
		self:CalculateScannerThink(player, curTime);
	end;
	
	local acrobatics = openAura.attributes:Fraction(player, ATB_ACROBATICS, 100, 50);
	local strength = openAura.attributes:Fraction(player, ATB_STRENGTH, 8, 4);
	local agility = openAura.attributes:Fraction(player, ATB_AGILITY, 50, 25);
	
	if ( self:PlayerIsCombine(player) ) then
		infoTable.inventoryWeight = infoTable.inventoryWeight + 8;
	end;
	
	if (clothes != "") then
		local itemTable = openAura.item:Get(clothes);
		
		if (itemTable and itemTable.pocketSpace) then
			infoTable.inventoryWeight = infoTable.inventoryWeight + itemTable.pocketSpace;
		end;
	end;
	
	infoTable.inventoryWeight = infoTable.inventoryWeight + strength;
	infoTable.jumpPower = infoTable.jumpPower + acrobatics;
	infoTable.runSpeed = infoTable.runSpeed + agility;
end;

-- Called when an entity is removed.
function openAura.schema:EntityRemoved(entity)
	if (IsValid(entity) and entity:GetClass() == "prop_ragdoll") then
		if (entity.areBelongings and entity.inventory and entity.cash) then
			if (table.Count(entity.inventory) > 0 or entity.cash > 0) then
				local belongings = ents.Create("aura_belongings");
				
				belongings:SetAngles( Angle(0, 0, -90) );
				belongings:SetData(entity.inventory, entity.cash);
				belongings:SetPos( entity:GetPos() + Vector(0, 0, 32) );
				belongings:Spawn();
				
				entity.inventory = nil;
				entity.cash = nil;
			end;
		end;
	end;
end;

-- Called when the player attempts to be ragdolled.
function openAura.schema:PlayerCanRagdoll(player, state, delay, decay, ragdoll)
	if ( self.scanners[player] ) then
		return false;
	end;
end;

-- Called when a player attempts to NoClip.
function openAura.schema:PlayerNoClip(player)
	if ( self.scanners[player] ) then
		return false;
	end;
end;

-- Called when a player's data should be saved.
function openAura.schema:PlayerSaveData(player, data)
	if (data["serverwhitelist"] and table.Count( data["serverwhitelist"] ) == 0) then
		data["serverwhitelist"] = nil;
	end;
end;

-- Called when a player's data should be restored.
function openAura.schema:PlayerRestoreData(player, data)
	if ( !data["serverwhitelist"] ) then
		data["serverwhitelist"] = {};
	end;
	
	local serverWhitelistIdentity = openAura.config:Get("server_whitelist_identity"):Get();
	
	if (serverWhitelistIdentity != "") then
		if ( !data["serverwhitelist"][serverWhitelistIdentity] ) then
			player:Kick("You aren't whitelisted");
		end;
	end;
end;

-- Called to check if a player does have an flag.
function openAura.schema:PlayerDoesHaveFlag(player, flag)
	if ( !openAura.config:Get("permits"):Get() ) then
		if (flag == "x" or flag == "1") then
			return false;
		end;
		
		for k, v in pairs(self.customPermits) do
			if (v.flag == flag) then
				return false;
			end;
		end;
	end;
end;

-- Called when a player's attribute has been updated.
function openAura.schema:PlayerAttributeUpdated(player, attributeTable, amount)
	if (self:PlayerIsCombine(player) and amount and amount > 0) then
		self:AddCombineDisplayLine("Updating external "..openAura.option:GetKey("name_attributes", true).."...", Color(255, 125, 0, 255), player);
	end;
end;

-- Called to check if a player does recognise another player.
function openAura.schema:PlayerDoesRecognisePlayer(player, target, status, isAccurate, realValue)
	if (self:PlayerIsCombine(target) or target:QueryCharacter("faction") == FACTION_ADMIN) then
		return true;
	end;
end;

-- Called when a player attempts to delete a character.
function openAura.schema:PlayerCanDeleteCharacter(player, character)
	if ( character.data["permakilled"] ) then
		return true;
	end;
end;

-- Called when a player attempts to use a character.
function openAura.schema:PlayerCanUseCharacter(player, character)
	if ( character.data["permakilled"] ) then
		return character.name.." is permanently killed and cannot be used!";
	elseif (character.faction == FACTION_MPF) then
		if ( self:IsStringCombineRank(character.name, "SCN") ) then
			local amount = 0;
			
			for k, v in ipairs( _player.GetAll() ) do
				if ( v:HasInitialized() and self:PlayerIsCombine(v) ) then
					if ( self:IsPlayerCombineRank(v, "SCN") ) then
						amount = amount + 1;
					end;
				end;
			end;
			
			if (amount >= 3) then
				return "There are too many scanners online!";
			end;
		end;
	end;
end;

-- Called when attempts to use a command.
function openAura.schema:PlayerCanUseCommand(player, commandTable, arguments)
	if (player:GetSharedVar("tied") != 0) then
		local blacklisted = {
			"OrderShipment",
			"Broadcast",
			"Dispatch",
			"Request",
			"Radio"
		};
		
		if ( table.HasValue(blacklisted, commandTable.name) ) then
			openAura.player:Notify(player, "You cannot use this command when you are tied!");
			
			return false;
		end;
	end;
end;

-- Called when a player attempts to use a door.
function openAura.schema:PlayerCanUseDoor(player, door)
	if ( player:GetSharedVar("tied") != 0 or (!self:PlayerIsCombine(player) and player:QueryCharacter("faction") != FACTION_ADMIN) ) then
		return false;
	end;
end;

-- Called when a player attempts to lock an entity.
function openAura.schema:PlayerCanLockEntity(player, entity)
	if ( openAura.entity:IsDoor(entity) and IsValid(entity.combineLock) ) then
		if ( openAura.config:Get("combine_lock_overrides"):Get() or entity.combineLock:IsLocked() ) then
			return false;
		end;
	end;
end;

-- Called when a player attempts to unlock an entity.
function openAura.schema:PlayerCanUnlockEntity(player, entity)
	if ( openAura.entity:IsDoor(entity) and IsValid(entity.combineLock) ) then
		if ( openAura.config:Get("combine_lock_overrides"):Get() or entity.combineLock:IsLocked() ) then
			return false;
		end;
	end;
end;

-- Called when a player's character has unloaded.
function openAura.schema:PlayerCharacterUnloaded(player)
	self:ResetPlayerScanner(player);
end;

-- Called when a player attempts to change class.
function openAura.schema:PlayerCanChangeClass(player, class)
	if (player:GetSharedVar("tied") != 0) then
		openAura.player:Notify(player, "You cannot change classes when you are tied!");
		
		return false;
	elseif ( self:PlayerIsCombine(player) ) then
		if ( class == CLASS_MPS and !self:IsPlayerCombineRank(player, "SCN") ) then
			openAura.player:Notify(player, "You are not ranked high enough for this class!");
			
			return false;
		elseif ( class == CLASS_MPR and !self:IsPlayerCombineRank(player, "RCT") ) then
			openAura.player:Notify(player, "You are not ranked high enough for this class!");
			
			return false;
		elseif ( class == CLASS_EMP and !self:IsPlayerCombineRank(player, "EpU") ) then
			openAura.player:Notify(player, "You are not ranked high enough for this class!");
			
			return false;
		elseif ( class == CLASS_OWS and !self:IsPlayerCombineRank(player, "OWS") ) then
			openAura.player:Notify(player, "You are not ranked high enough for this class!");
			
			return false;
		elseif ( class == CLASS_EOW and !self:IsPlayerCombineRank(player, "EOW") ) then
			openAura.player:Notify(player, "You are not ranked high enough for this class!");
			
			return false;
		elseif (class == CLASS_MPU) then
			if ( self:IsPlayerCombineRank(player, "EpU") ) then
				openAura.player:Notify(player, "You are ranked too high for this class!");
				
				return false;
			elseif ( self:IsPlayerCombineRank(player, "RCT") ) then
				openAura.player:Notify(player, "You are not ranked high enough for this class!");
				
				return false;
			end;
		end;
	end;
end;

-- Called when a player attempts to use an entity.
function openAura.schema:PlayerUse(player, entity)
	local overlayText = entity:GetNetworkedString("GModOverlayText");
	local curTime = CurTime();
	local faction = player:QueryCharacter("faction");
	
	if ( string.find(overlayText, "CA") ) then
		if (faction != FACTION_ADMIN) then
			return false;
		end;
	elseif ( string.find(overlayText, "OTA") ) then
		if (faction != FACTION_ADMIN and faction != FACTION_OTA) then
			return false;
		end;
	elseif ( string.find(overlayText, "MPF") ) then
		if (faction != FACTION_ADMIN and faction != FACTION_OTA and faction != FACTION_MPF) then
			return false;
		end;
	elseif ( string.find(overlayText, "CWU") ) then
		if (faction != FACTION_ADMIN and faction != FACTION_OTA and faction != FACTION_MPF) then
			if (player:GetCharacterData("customclass") != "Civil Worker's Union") then
				return false;
			end;
		end;
	end;
	
	if ( self.scanners[player] ) then
		return false;
	end;
	
	if (entity.bustedDown) then
		return false;
	end;
	
	if ( player:KeyDown(IN_SPEED) and openAura.entity:IsDoor(entity) ) then
		if ( (self:PlayerIsCombine(player) or player:QueryCharacter("faction") == FACTION_ADMIN) and IsValid(entity.combineLock) ) then
			if (!player.nextCombineLock or curTime >= player.nextCombineLock) then
				entity.combineLock:ToggleWithChecks(player);
				
				player.nextCombineLock = curTime + 3;
			end;
			
			return false;
		end;
	end;
	
	if (player:GetSharedVar("tied") != 0) then
		if ( entity:IsVehicle() ) then
			if ( openAura.entity:IsChairEntity(entity) or openAura.entity:IsPodEntity(entity) ) then
				return;
			end;
		end;
		
		if ( !player.nextTieNotify or player.nextTieNotify < CurTime() ) then
			openAura.player:Notify(player, "You cannot use that when you are tied!");
			
			player.nextTieNotify = CurTime() + 2;
		end;
		
		return false;
	end;
end;

-- Called when a player attempts to destroy an item.
function openAura.schema:PlayerCanDestroyItem(player, itemTable, noMessage)
	if ( self.scanners[player] ) then
		if (!noMessage) then
			openAura.player:Notify(player, "You cannot destroy items when you are a scanner!");
		end;
		
		return false;
	elseif (player:GetSharedVar("tied") != 0) then
		if (!noMessage) then
			openAura.player:Notify(player, "You cannot destroy items when you are tied!");
		end;
		
		return false;
	end;
end;

-- Called when a player attempts to drop an item.
function openAura.schema:PlayerCanDropItem(player, itemTable, noMessage)
	if ( self.scanners[player] ) then
		if (!noMessage) then
			openAura.player:Notify(player, "You cannot drop items when you are a scanner!");
		end;
		
		return false;
	elseif (player:GetSharedVar("tied") != 0) then
		if (!noMessage) then
			openAura.player:Notify(player, "You cannot drop items when you are tied!");
		end;
		
		return false;
	end;
end;

-- Called when a player attempts to use an item.
function openAura.schema:PlayerCanUseItem(player, itemTable, noMessage)
	if ( self.scanners[player] ) then
		if (!noMessage) then
			openAura.player:Notify(player, "You cannot use items when you are a scanner!");
		end;
		
		return false;
	elseif (player:GetSharedVar("tied") != 0) then
		if (!noMessage) then
			openAura.player:Notify(player, "You cannot use items when you are tied!");
		end;
		
		return false;
	end;
	
	if (openAura.item:IsWeapon(itemTable) and !itemTable.fakeWeapon) then
		local secondaryWeapon;
		local primaryWeapon;
		local sideWeapon;
		local fault;
		
		for k, v in ipairs( player:GetWeapons() ) do
			local weaponTable = openAura.item:GetWeapon(v);
			
			if (weaponTable and !weaponTable.fakeWeapon) then
				if (weaponTable.weight >= 1) then
					if (weaponTable.weight <= 2) then
						secondaryWeapon = true;
					else
						primaryWeapon = true;
					end;
				else
					sideWeapon = true;
				end;
			end;
		end;
		
		if (itemTable.weight >= 1) then
			if (itemTable.weight <= 2) then
				if (secondaryWeapon) then
					fault = "You cannot use another secondary weapon!";
				end;
			elseif (primaryWeapon) then
				fault = "You cannot use another secondary weapon!";
			end;
		elseif (sideWeapon) then
			fault = "You cannot use another melee weapon!";
		end;
		
		if (fault) then
			if (!noMessage) then
				openAura.player:Notify(player, fault);
			end;
			
			return false;
		end;
	end;
end;

-- Called when a player attempts to earn generator cash.
function openAura.schema:PlayerCanEarnGeneratorCash(player, info, cash)
	if ( self:PlayerIsCombine(player) ) then
		return false;
	end;
end;

-- Called when a player's death sound should be played.
function openAura.schema:PlayerPlayDeathSound(player, gender)
	if ( self:PlayerIsCombine(player) ) then
		local sound = "npc/metropolice/die"..math.random(1, 4)..".wav";
		
		for k, v in ipairs( _player.GetAll() ) do
			if ( v:HasInitialized() ) then
				if ( self:PlayerIsCombine(v) ) then
					v:EmitSound(sound);
				end;
			end;
		end;
		
		return sound;
	end;
end;

-- Called when a player's pain sound should be played.
function openAura.schema:PlayerPlayPainSound(player, gender, damageInfo, hitGroup)
	if ( self:PlayerIsCombine(player) ) then
		return "npc/metropolice/pain"..math.random(1, 4)..".wav";
	end;
end;
-- Called when chat box info should be adjusted.
function openAura.schema:ChatBoxAdjustInfo(info)
	if (info.class != "ooc" and info.class != "looc") then
		if ( IsValid(info.speaker) and info.speaker:HasInitialized() ) then
			if (string.sub(info.text, 1, 1) == "?") then
				info.text = string.sub(info.text, 2);
				info.data.anon = true;
			end;
		end;
	end;
	
	if (info.class == "ic" or info.class == "yell" or info.class == "radio" or info.class == "whisper" or info.class == "request") then
		if ( IsValid(info.speaker) and info.speaker:HasInitialized() ) then
			local playerIsCombine = self:PlayerIsCombine(info.speaker);
			
			if ( playerIsCombine and self:IsPlayerCombineRank(info.speaker, "SCN") ) then
				for k, v in pairs(self.dispatchVoices) do
					if ( string.lower(info.text) == string.lower(v.command) ) then
						local voice = {
							global = false,
							volume = 90,
							sound = v.sound
						};
						
						if (info.class == "request" or info.class == "radio") then
							voice.global = true;
						elseif (info.class == "whisper") then
							voice.volume = 80;
						elseif (info.class == "yell") then
							voice.volume = 100;
						end;
						
						info.text = "<:: "..v.phrase;
						info.voice = voice;
						
						return true;
					end;
				end;
			else
				for k, v in pairs(self.voices) do
					if ( (v.faction == "Combine" and playerIsCombine) or (v.faction == "Human" and !playerIsCombine) ) then
						if ( string.lower(info.text) == string.lower(v.command) ) then
							local voice = {
								global = false,
								volume = 80,
								sound = v.sound
							};
							
							if (v.female and info.speaker:QueryCharacter("gender") == GENDER_FEMALE) then
								voice.sound = string.Replace(voice.sound, "/male", "/female");
							end;
							
							if (info.class == "request" or info.class == "radio") then
								voice.global = true;
							elseif (info.class == "whisper") then
								voice.volume = 60;
							elseif (info.class == "yell") then
								voice.volume = 100;
							end;
							
							if (playerIsCombine) then
								info.text = "<:: "..v.phrase;
							else
								info.text = v.phrase;
							end;
							
							info.voice = voice;
							
							return true;
						end;
					end;
				end;
			end;
			
			if (playerIsCombine) then
				if (string.sub(info.text, 1, 4) != "<:: ") then
					info.text = "<:: "..info.text;
				end;
			end;
		end;
	elseif (info.class == "dispatch") then
		for k, v in pairs(self.dispatchVoices) do
			if ( string.lower(info.text) == string.lower(v.command) ) then
				openAura.player:PlaySound(nil, v.sound);
				
				info.text = v.phrase;
				
				return true;
			end;
		end;
	end;
end;

-- Called when a player destroys generator.
function openAura.schema:PlayerDestroyGenerator(player, entity, generator)
	if ( self:PlayerIsCombine(player) ) then
		local players = {};
		
		for k, v in ipairs( _player.GetAll() ) do
			if ( v:HasInitialized() ) then
				if ( self:PlayerIsCombine(v) ) then
					players[#players + 1] = v;
				end;
			end;
		end;
		
		for k, v in pairs(players) do
			openAura.player:GiveCash( v, generator.cash / 4, "destroying a "..string.lower(generator.name) );
		end;
	else
		openAura.player:GiveCash( v, generator.cash / 4, "destroying a "..string.lower(generator.name) );
	end;
end;

-- Called just before a player dies.
function openAura.schema:DoPlayerDeath(player, attacker, damageInfo)
	local clothes = player:GetCharacterData("clothes");
	
	if (clothes) then
		player:UpdateInventory(clothes);
		player:SetCharacterData("clothes", nil);
	end;
	
	player.beingSearched = nil;
	player.searching = nil;
	
	self:TiePlayer(player, false, true);
end;

-- Called when a player dies.
function openAura.schema:PlayerDeath(player, inflictor, attacker, damageInfo)
	if ( self:PlayerIsCombine(player) ) then
		local location = self:PlayerGetLocation(player);
		
		self:AddCombineDisplayLine("Downloading lost biosignal...", Color(255, 255, 255, 255), nil, player);
		self:AddCombineDisplayLine("WARNING! Biosignal lost for protection team unit at "..location.."...", Color(255, 0, 0, 255), nil, player);
		
		if ( self.scanners[player] ) then
			if ( IsValid( self.scanners[player][1] ) ) then
				if (damageInfo != true) then
					self.scanners[player][1]:TakeDamage(self.scanners[player][1]:Health() + 100);
				end;
			end;
		end;
		
		for k, v in ipairs( _player.GetAll() ) do
			if ( self:PlayerIsCombine(v) ) then
				v:EmitSound("npc/overwatch/radiovoice/on1.wav");
				v:EmitSound("npc/overwatch/radiovoice/lostbiosignalforunit.wav");
			end;
		end;
		
		timer.Simple(1.5, function()
			for k, v in ipairs( _player.GetAll() ) do
				if ( self:PlayerIsCombine(v) ) then
					v:EmitSound("npc/overwatch/radiovoice/off4.wav");
				end;
			end;
		end);
	end;
	
	if ( !player:GetCharacterData("permakilled") ) then
		if ( ( attacker:IsPlayer() or attacker:IsNPC() ) and damageInfo ) then
			local miscellaneousDamage = damageInfo:IsBulletDamage() or damageInfo:IsFallDamage() or damageInfo:IsExplosionDamage();
			local meleeDamage = damageInfo:IsDamageType(DMG_CLUB) or damageInfo:IsDamageType(DMG_SLASH);
			
			if (miscellaneousDamage or meleeDamage) then
				if (openAura:GetSharedVar("PKMode") == 1) then
					self:PermaKillPlayer( player, player:GetRagdollEntity() );
				end;
			end;
		end;
	end;
end;

-- Called when a player's character has loaded.
function openAura.schema:PlayerCharacterLoaded(player)
	player:SetSharedVar("permaKilled", false);
	player:SetSharedVar("tied", 0);
end;

-- Called when a player attempts to switch to a character.
function openAura.schema:PlayerCanSwitchCharacter(player, character)
	if (player:GetSharedVar("tied") != 0) then
		return false, "You cannot switch to this character while tied!";
	end;
end;

-- Called just after a player spawns.
function openAura.schema:PostPlayerSpawn(player, lightSpawn, changeClass, firstSpawn)
	local clothes = player:GetCharacterData("clothes");
	
	if (!lightSpawn) then
		player:SetSharedVar("antidepressants", 0);
		
		umsg.Start("aura_ClearEffects", player);
		umsg.End();
		
		player.beingSearched = nil;
		player.searching = nil;
		
		if (self:PlayerIsCombine(player) or player:QueryCharacter("faction") == FACTION_ADMIN) then
			if (player:QueryCharacter("faction") == FACTION_OTA) then
				player:SetMaxHealth(150);
				player:SetMaxArmor(150);
				player:SetHealth(150);
				player:SetArmor(150);
			elseif ( !self:IsPlayerCombineRank(player, "RCT") ) then
				player:SetArmor(100);
			else
				player:SetArmor(50);
			end;
		end;
		
		if (self:PlayerIsCombine(player) and player:GetAmmoCount("pistol") == 0) then
			if ( !player:HasItem("ammo_pistol") ) then
				player:UpdateInventory("ammo_pistol", 1, true, true);
			end;
		end;
	end;
	
	if ( self:IsPlayerCombineRank(player, "SCN") ) then
		self:MakePlayerScanner(player, true, lightSpawn);
	else
		self:ResetPlayerScanner(player);
	end;
	
	if (player:GetSharedVar("tied") != 0) then
		self:TiePlayer(player, true);
	end;
	
	if (clothes) then
		local itemTable = openAura.item:Get(clothes);
		
		if ( itemTable and player:HasItem(itemTable.uniqueID) ) then
			self:PlayerWearClothes(player, itemTable);
		else
			player:SetCharacterData("clothes", nil);
		end;
	end;
end;

-- Called when a player spawns lightly.
function openAura.schema:PostPlayerLightSpawn(player, weapons, ammo, special)
	local clothes = player:GetCharacterData("clothes");
	
	if (clothes) then
		local itemTable = openAura.item:Get(clothes);
		
		if (itemTable) then
			itemTable:OnChangeClothes(player, true);
		end;
	end;
end;

-- Called when a player throws a punch.
function openAura.schema:PlayerPunchThrown(player)
	player:ProgressAttribute(ATB_STRENGTH, 0.25, true);
end;

-- Called when a player punches an entity.
function openAura.schema:PlayerPunchEntity(player, entity)
	if ( entity:IsPlayer() or entity:IsNPC() ) then
		player:ProgressAttribute(ATB_STRENGTH, 1, true);
	else
		player:ProgressAttribute(ATB_STRENGTH, 0.5, true);
	end;
end;

-- Called when an entity has been breached.
function openAura.schema:EntityBreached(entity, activator)
	if ( openAura.entity:IsDoor(entity) ) then
		if ( !IsValid(entity.combineLock) ) then
			if (!IsValid(activator) or string.lower( entity:GetClass() ) != "prop_door_rotating") then
				openAura.entity:OpenDoor(entity, 0, true, true);
			else
				self:BustDownDoor(activator, entity);
			end;
		elseif ( IsValid(activator) and activator:IsPlayer() and self:PlayerIsCombine(activator) ) then
			if (string.lower( entity:GetClass() ) == "prop_door_rotating") then
				entity.combineLock:ActivateSmokeCharge( (entity:GetPos() - activator:GetPos() ):Normalize() * 10000 );
			else
				entity.combineLock:SetFlashDuration(2);
			end;
		else
			entity.combineLock:SetFlashDuration(2);
		end;
	end;
end;

-- Called when a player takes damage.
function openAura.schema:PlayerTakeDamage(player, inflictor, attacker, hitGroup, damageInfo)
	local curTime = CurTime();
	
	if (player:Armor() <= 0) then
		umsg.Start("aura_Stunned", player);
			umsg.Float(0.5);
		umsg.End();
	else
		umsg.Start("aura_Stunned", player);
			umsg.Float(1);
		umsg.End();
	end;
end;

-- Called when a player's limb damage is healed.
function openAura.schema:PlayerLimbDamageHealed(player, hitGroup, amount)
	if (hitGroup == HITGROUP_HEAD) then
		player:BoostAttribute("Limb Damage", ATB_MEDICAL, false);
	elseif (hitGroup == HITGROUP_CHEST or hitGroup == HITGROUP_STOMACH) then
		player:BoostAttribute("Limb Damage", ATB_ENDURANCE, false);
	elseif (hitGroup == HITGROUP_LEFTLEG or hitGroup == HITGROUP_RIGHTLEG) then
		player:BoostAttribute("Limb Damage", ATB_ACROBATICS, false);
		player:BoostAttribute("Limb Damage", ATB_AGILITY, false);
	elseif (hitGroup == HITGROUP_LEFTARM or hitGroup == HITGROUP_RIGHTARM) then
		player:BoostAttribute("Limb Damage", ATB_DEXTERITY, false);
		player:BoostAttribute("Limb Damage", ATB_STRENGTH, false);
	end;
end;

-- Called when a player's limb damage is reset.
function openAura.schema:PlayerLimbDamageReset(player)
	player:BoostAttribute("Limb Damage", nil, false);
end;

-- Called when a player's limb takes damage.
function openAura.schema:PlayerLimbTakeDamage(player, hitGroup, damage)
	local limbDamage = openAura.limb:GetDamage(player, hitGroup);
	
	if (hitGroup == HITGROUP_HEAD) then
		player:BoostAttribute("Limb Damage", ATB_MEDICAL, -limbDamage);
	elseif (hitGroup == HITGROUP_CHEST or hitGroup == HITGROUP_STOMACH) then
		player:BoostAttribute("Limb Damage", ATB_ENDURANCE, -limbDamage);
	elseif (hitGroup == HITGROUP_LEFTLEG or hitGroup == HITGROUP_RIGHTLEG) then
		player:BoostAttribute("Limb Damage", ATB_ACROBATICS, -limbDamage);
		player:BoostAttribute("Limb Damage", ATB_AGILITY, -limbDamage);
	elseif (hitGroup == HITGROUP_LEFTARM or hitGroup == HITGROUP_RIGHTARM) then
		player:BoostAttribute("Limb Damage", ATB_DEXTERITY, -limbDamage);
		player:BoostAttribute("Limb Damage", ATB_STRENGTH, -limbDamage);
	end;
end;

-- A function to scale damage by hit group.
function openAura.schema:PlayerScaleDamageByHitGroup(player, attacker, hitGroup, damageInfo, baseDamage)
	local endurance = openAura.attributes:Fraction(player, ATB_ENDURANCE, 0.75, 0.75);
	local clothes = player:GetCharacterData("clothes");
	
	damageInfo:ScaleDamage(1.5 - endurance);
	
	if ( damageInfo:IsBulletDamage() ) then
		if ( clothes and damageInfo:IsBulletDamage() ) then
			local itemTable = openAura.item:Get(clothes);
			
			if (itemTable and itemTable.protection) then
				damageInfo:ScaleDamage(1 - itemTable.protection);
			end;
		end;
	end;
end;

-- Called when an entity takes damage.
function openAura.schema:EntityTakeDamage(entity, inflictor, attacker, amount, damageInfo)
	local player = openAura.entity:GetPlayer(entity);
	local curTime = CurTime();
	local doDoorDamage = nil;
	
	if (player) then
		if (!player.nextEnduranceTime or CurTime() > player.nextEnduranceTime) then
			player:ProgressAttribute(ATB_ENDURANCE, math.Clamp(damageInfo:GetDamage(), 0, 75) / 10, true);
			player.nextEnduranceTime = CurTime() + 2;
		end;
		
		if ( self.scanners[player] ) then
			entity:EmitSound("npc/scanner/scanner_pain"..math.random(1, 2)..".wav");
			
			if (entity:Health() > 50 and entity:Health() - damageInfo:GetDamage() <= 50) then
				entity:EmitSound("npc/scanner/scanner_siren1.wav");
			elseif (entity:Health() > 25 and entity:Health() - damageInfo:GetDamage() <= 25) then
				entity:EmitSound("npc/scanner/scanner_siren2.wav");
			end;
		end;
		
		if ( attacker:IsPlayer() and self:PlayerIsCombine(player) ) then
			if (attacker != player) then
				local location = openAura.schema:PlayerGetLocation(player);
				
				if (!player.nextUnderFire or curTime >= player.nextUnderFire) then
					player.nextUnderFire = curTime + 15;
					
					openAura.schema:AddCombineDisplayLine("Downloading trauma packet...", Color(255, 255, 255, 255), nil, player);
					openAura.schema:AddCombineDisplayLine("WARNING! Protection team unit enduring physical bodily trauma at "..location.."...", Color(255, 0, 0, 255), nil, player);
				end;
			end;
		end;
	end;
	
	if ( attacker:IsPlayer() ) then
		local strength = openAura.attributes:Fraction(attacker, ATB_STRENGTH, 1, 0.5);
		local weapon = openAura.player:GetWeaponClass(attacker);
		
		if ( damageInfo:IsDamageType(DMG_CLUB) or damageInfo:IsDamageType(DMG_SLASH) ) then
			damageInfo:ScaleDamage(1 + strength);
		end;
		
		if (weapon == "weapon_357") then
			damageInfo:ScaleDamage(0.25);
		elseif (weapon == "weapon_crossbow") then
			damageInfo:ScaleDamage(2);
		elseif (weapon == "weapon_shotgun") then
			damageInfo:ScaleDamage(3);
			
			doDoorDamage = true;
		elseif (weapon == "weapon_crowbar") then
			damageInfo:ScaleDamage(0.25);
		elseif (weapon == "aura_stunstick") then
			if (player) then
				if (player:Health() <= 10) then
					damageInfo:ScaleDamage(0.5);
				end;
			end;
		end;
		
		if (damageInfo:IsBulletDamage() and weapon != "weapon_shotgun") then
			if ( !IsValid(entity.combineLock) and !IsValid(entity.breach) ) then
				if (string.lower( entity:GetClass() ) == "prop_door_rotating") then
					if ( !openAura.entity:IsDoorFalse(entity) ) then
						local damagePosition = damageInfo:GetDamagePosition();
						
						if (entity:WorldToLocal(damagePosition):Distance( Vector(-1.0313, 41.8047, -8.1611) ) <= 8) then
							entity.doorHealth = math.min( (entity.doorHealth or 50) - damageInfo:GetDamage(), 0 );
							
							local effectData = EffectData();
							
							effectData:SetStart(damagePosition);
							effectData:SetOrigin(damagePosition);
							effectData:SetScale(8);
							
							util.Effect("GlassImpact", effectData, true, true);
							
							if (entity.doorHealth <= 0) then
								openAura.entity:OpenDoor( entity, 0, true, true, attacker:GetPos() );
								
								entity.doorHealth = 50;
							else
								openAura:CreateTimer("reset_door_health_"..entity:EntIndex(), 60, 1, function()
									if ( IsValid(entity) ) then
										entity.doorHealth = 50;
									end;
								end);
							end;
						end;
					end;
				end;
			end;
		end;
		
		if ( damageInfo:IsExplosionDamage() ) then
			damageInfo:ScaleDamage(2);
		end;
	elseif ( attacker:IsNPC() ) then
		damageInfo:ScaleDamage(0.5);
	end;
	
	if (damageInfo:IsExplosionDamage() or doDoorDamage) then
		if ( !IsValid(entity.combineLock) and !IsValid(entity.breach) ) then
			if (string.lower( entity:GetClass() ) == "prop_door_rotating") then
				if ( !openAura.entity:IsDoorFalse(entity) ) then
					if (attacker:GetPos():Distance( entity:GetPos() ) <= 96) then
						entity.doorHealth = math.min( (entity.doorHealth or 50) - damageInfo:GetDamage(), 0 );
						
						local damagePosition = damageInfo:GetDamagePosition();
						local effectData = EffectData();
						
						effectData:SetStart(damagePosition);
						effectData:SetOrigin(damagePosition);
						effectData:SetScale(8);
						
						util.Effect("GlassImpact", effectData, true, true);
						
						if (entity.doorHealth <= 0) then
							self:BustDownDoor(attacker, entity);
							
							entity.doorHealth = 50;
						else
							openAura:CreateTimer("reset_door_health_"..entity:EntIndex(), 60, 1, function()
								if ( IsValid(entity) ) then
									entity.doorHealth = 50;
								end;
							end);
						end;
					end;
				end;
			end;
		end;
	end;
end;