Classes

Concepts

Libraries

advertisement »

ambientSounds »

bitser »

contentPoints »

eventBoxText »

factValidity »

frameBuffer »

officeBuildingInserter »

priorityRenderer »

randomEvents »

scaling »

spritesheetParser »

statusIcons »

test3 »

util »

Objects

registerNew

Description

Registers a new advertisement type.

Arguments

1 table data

the advertisement data.

Example

-- taken from game/game/advertisement.lua


local campaign = {}
campaign.icon = "advert_mass_advert"
campaign.id = "mass_advertisement"
campaign.display = _T("ADVERTISEMENT_MASS_ADVERTISEMENT", "Mass advertisement")
campaign.dataFact = "campaign_data"
campaign.campaignDuration = timeline.DAYS_IN_WEEK * timeline.WEEKS_IN_MONTH
campaign.maxEvents = campaign.campaignDuration * 0.5
campaign.maxDelayBetweenEvent = 5 -- max delay between campaign event; the delay between each event depends on the duration + amount of events


campaign.description = {
	{text = _T("CAMPAIGN_DESC_1", "Begin an advertisement campaign of a specific scale."), font = "pix24"},
	{text = _T("CAMPAIGN_DESC_2", "No risk involved, can amass popularity for a specific game project."), font = "pix20"},
	{text = _T("CAMPAIGN_DESC_3", "Usually a good idea to begin a campaign after the game was in development for a specific amount time."), font = "pix20"},
}

-- the best part about these additional advert options is that I can write their own callbacks for calculating the result

-- ie. the cgi trailer option will depend on things like how far the game is into development, how shitty the previous game was (if it's a sequel)

campaign.additionalAdvertOptionsByID = {}
campaign.additionalAdvertOptions = {}
campaign.revealEfficiencyChance = 25 -- chance to reveal the efficiency of an advert method post mass advert

campaign.revealEffiecincyChancePerAdvertisement = 10 -- extra chance to reveal efficiency per each advertisement type selected

campaign.revealedAdvertisementsFact = "revealed_advertisements"
campaign.maxRepGainPerAdvertType = 400 -- the maximum reputation we can get per each advertisement type

campaign.noRepGainCutoff = 20000 -- when our reputation exceeds this much reputation, we won't be gaining any reputation from advertisement

campaign.minProjectCompletion = 0.4 -- if the overall project completion is less than this, then mass advert will suffer a penalty

campaign.lowCompletionPenalty = 0.7 -- the advertisement will only work at 70% efficiency if we're advertising the game while it's barely even done

campaign.marketingSpecBoost = 1.2 -- how much efficiency is boosted by a valid Marketing specialization

campaign.minimumFamiliarity = 0.8 -- minimum project familiarity for the marketing spec boost to be valid


campaign.minBudget = 0.5 -- minimum budget percentage

campaign.maxBudget = 1

campaign.EVENTS = {
	BUDGET_CHANGED = "mass_advert_budget_changed"
}

campaign.resultText = {
	{amount = 0, text = _T("ADVERT_RESULT_FAILURE", "The campaign was a complete failure, noone even knew it happened.")},
	{amount = 1000, text = _T("ADVERT_RESULT_TINY", "The campaign has raised only a tiny amount of interest towards the game.")},
	{amount = 5000, text = _T("ADVERT_RESULT_SMALL", "The campaign has raised a small amount of interest towards the game.")},
	{amount = 12000, text = _T("ADVERT_RESULT_MODERATE", "The campaign has raised a moderate amount of interest towards the game.")},
	{amount = 30000, text = _T("ADVERT_RESULT_HIGH", "The campaign was well-received, and as a result, the game has gained a high amount of interest.")},
	{amount = 80000, text = _T("ADVERT_RESULT_VERY_HIGH", "The campaign was a big success, and a very high amount of people have become interested in it.")},
	{amount = 150000, text = _T("ADVERT_RESULT_INSANE", "The campaign was an insane success, a huge amount of people have taken interest in it.")}
}

function campaign:registerAdvertOption(data)
	table.insert(campaign.additionalAdvertOptions, data)
	campaign.additionalAdvertOptionsByID[data.id] = data
end

function campaign:canSelect(gameProj)
	local data = gameProj:getFact(self.dataFact)
	
	if not data then
		return true
	end
	
	if timeline.curTime < data.lastsUntil then
		return false
	end
	
	return true
end

function campaign:applyCampaignDataToGame(gameProj, advertTypes, totalCost, budgetPercentage)
	local data = {
		startedOn = timeline.curTime,
		lastsUntil = timeline.curTime + self.campaignDuration,
		lastCampaignEvent = timeline.curTime,
		advertTypeAmount = #advertTypes,
		totalPopularityGained = 0,
		totalReputationGained = 0,
		moneySpent = totalCost,
		advertTypes = advertTypes,
		budgetPercentage = budgetPercentage,
		performedAdvertTypes = {}
	}
	
	gameProj:setFact(self.dataFact, data)
	gameProj:addAdvertisement(self.id)
end

function campaign:abort(gameProj)
	self:finishCampaign(gameProj, true, true)
end

function campaign.stopMassAdvertOption(self)
	campaign:abort(self.project)
end

function campaign:fillInteractionComboBox(comboBox, gameProj)
	if gameProj:getFact(self.dataFact) then
		local option = comboBox:addOption(0, 0, 0, 0, _T("CANCEL_ADVERTISEMENT", "Stop mass advertisement"), fonts.get("pix20"), campaign.stopMassAdvertOption)
		
		option.project = gameProj
	end
end

local unrevealedAdvertTypes = {}

function campaign:removeAdvertData(gameProj)
	gameProj:setFact(self.dataFact, nil)
	gameProj:removeActiveAdvertisement(self.id)
end

function campaign:finishCampaign(gameProj, suppressEfficiencyReveal, wasAborted)
	if not gameProj:getOwner():isPlayer() then
		self:removeAdvertData(gameProj)
		return
	end
	
	local data = gameProj:getFact(self.dataFact)
	
	-- get total popularity gained

	local amountOfAdvertisements = 0
	local totalPop = data.totalPopularityGained
	
	for advertType, amount in pairs(data.performedAdvertTypes) do
		amountOfAdvertisements = amountOfAdvertisements + 1
	end
	
	local randomRevealedAdvertType = nil
		
	if not suppressEfficiencyReveal and math.random(1, 100) <= (self.revealEfficiencyChance + amountOfAdvertisements * self.revealEffiecincyChancePerAdvertisement) then
		local revealedAdvertTypes = studio:getFact(campaign.revealedAdvertisementsFact) or {}
		
		for advertType, amount in pairs(data.performedAdvertTypes) do
			if not revealedAdvertTypes[advertType] then
				unrevealedAdvertTypes[#unrevealedAdvertTypes + 1] = advertType
			end
		end
		
		if #unrevealedAdvertTypes > 0 then
			randomRevealedAdvertType = unrevealedAdvertTypes[math.random(1, #unrevealedAdvertTypes)]
			revealedAdvertTypes[randomRevealedAdvertType] = true
			
			table.clear(unrevealedAdvertTypes)
		end
		
		studio:setFact(campaign.revealedAdvertisementsFact, revealedAdvertTypes)
	end
	
	-- get result text based on popularity gained

	local highest = -math.huge
	local fitting = nil
	
	for key, data in ipairs(self.resultText) do
		if totalPop >= data.amount and data.amount > highest then
			highest = data.amount
			fitting = data
		end
	end
	
	local displayText = fitting.text
	
	local popup = gui.create("DescboxPopup")
	popup:setWidth(600)
	popup:setFont(fonts.get("pix24"))
	popup:setTextFont(fonts.get("pix20"))
	popup:setTitle(_T("ADVERT_RESULT_TITLE", "Mass Advert Result"))
	popup:setText(displayText)
	
	local leftBox, rightBox, extra = popup:getDescboxes()
	
	leftBox:addSpaceToNextText(10)
	rightBox:addSpaceToNextText(10)
	local wrapWidth = popup.rawW - 10
	
	leftBox:addText(_format(_T("MASS_ADVERT_POPULARITY_GAINED", "Popularity gained: POPULARITY"), "POPULARITY", string.roundtobignumber(totalPop)), "bh22", nil, 0, wrapWidth, "star", 26, 26)
	leftBox:addText(_format(_T("MASS_ADVERT_REPUTATION_GAINED", "Reputation gained: REP pts."), "REP", string.roundtobignumber(data.totalReputationGained)), "bh22", nil, 0, wrapWidth, "star", 26, 26)
	
	rightBox:addText(_format(_T("MASS_ADVERT_MONEY_SPENT", "Money spent: MONEY"), "MONEY", string.roundtobigcashnumber(data.moneySpent or 0)), "bh22", nil, 0, wrapWidth, {{icon = "generic_backdrop", width = 26, height = 26}, {icon = "wad_of_cash_minus", width = 20, height = 20, x = 3, y = 1}})
	rightBox:addText(_format(_T("MASS_ADVERT_COMPLETED_IN", "Completed in TIME"), "TIME", timeline:getTimePeriodText(timeline.curTime - data.startedOn)), "bh22", nil, 0, wrapWidth, {{icon = "generic_backdrop", width = 26, height = 26}, {icon = "clock_full", width = 20, height = 20, x = 3, y = 1}})
	
	local spacingAdded = false
	
	if randomRevealedAdvertType then
		extra:addSpaceToNextText(4)
		spacingAdded = true
		
		extra:addText(_format(_T("DISCOVERED_CAMPAIGN_TYPE_EFFICIENCY", "Discovered efficiency of ADVERT!"), "ADVERT", campaign.additionalAdvertOptionsByID[randomRevealedAdvertType].display), "pix20", nil, 0, wrapWidth, "exclamation_point", 24, 24)
	end
	
	if wasAborted then
		extra:addSpaceToNextText(4)
		
		extra:addText(_T("ABORTED_ADVERTISEMENT_INFORM", "The campaign has been aborted. No efficiency info will be revealed and maximum campaign efficiency has not been reached."), "pix20", nil, 0, wrapWidth, "question_mark", 24, 24)
	end
	
	if data.marketingBoost then
		extra:addText(_T("MASS_ADVERT_MARKETING_BOOST", "Increased efficiency due to Marketing specialist."), "bh22", nil, 0, wrapWidth, "exclamation_point", 24, 24)
	end
	
	if data.sufferedPenalty then
		extra:addTextLine(popup.w - _S(20), game.UI_COLORS.UI_PENALTY_LINE)
		extra:addText(_T("MASS_ADVERT_EFFICIENCY_SUFFERED", "Lower efficiency due to early stages of development."), "bh22", game.UI_COLORS.RED, 0, wrapWidth, "exclamation_point_red", 24, 24)
	end
	
	popup:addButton(fonts.get("pix20"), _T("OK", "OK"))

	popup:center()
	frameController:push(popup)
	
	self:removeAdvertData(gameProj)
	events:fire(advertisement.EVENTS.FINISHED_ADVERTISEMENT, gameProj, self.id)
end

eventBoxText:registerNew({
	id = "mass_advert_performed",
	getText = function(self, data)
		return _format(_T("PERFORMED_MASS_ADVERT_TYPE", "'TYPE' advertisement performed. 'PROJECT' has gained POPULARITY Hype points."), "TYPE", campaign.additionalAdvertOptionsByID[data.advertID].display, "PROJECT", data.game, "POPULARITY", string.roundtobignumber(data.popGain))
	end
})

function campaign:handleEvent(gameProj, event)
	if event == timeline.EVENTS.NEW_DAY then
		local data = gameProj:getFact(self.dataFact)

		if data then
			if timeline.curTime >= data.lastsUntil then -- campaign is finished, set data to nil, could probably make a popup displaying the amount of popularity gained, etc.

				self:finishCampaign(gameProj)
			else
				local curTime = timeline.curTime
				local delta = math.max(math.min((data.lastsUntil - data.startedOn) / data.advertTypeAmount, self.maxDelayBetweenEvent), 1)
				local timeUntil = curTime - (data.lastCampaignEvent + delta)
			
				if timeUntil >= 0 then -- enough time has passed, now we can hold an event again

					--local totalOwners = platformShare:getMultiplePlatformOwners(gameProj:getTargetPlatforms())

					local totalOwners = platformShare:getPlatformUsers()
					local projCompletion = gameProj:getOverallCompletion()
					
					local multiplier = 1
					local team = gameProj:getTeam()
					
					if team then
						local marketers = team:getMembersBySpecialization("marketing")
						local ourID = gameProj:getUniqueID()
						
						for key, dev in ipairs(marketers) do
							if dev:getFamiliarityAffector(ourID) >= self.minimumFamiliarity then
								multiplier = multiplier * self.marketingSpecBoost
								data.marketingBoost = true
								break
							end
						end
					end
					
					if projCompletion < self.minProjectCompletion then
						multiplier = multiplier * self.lowCompletionPenalty
						data.sufferedPenalty = true
					end
					
					if #data.advertTypes > 0 then
						local advertType = table.remove(data.advertTypes, math.random(1, #data.advertTypes))
						local advertData = self.additionalAdvertOptionsByID[advertType]
						local maxPopGain = math.max(totalOwners - data.totalPopularityGained, 0)
						
						local basePopGain, gainedReputation, realPopGain = advertData:perform(gameProj, multiplier * (data.budgetPercentage or 1), maxPopGain)
						
						data.performedAdvertTypes[advertType] = realPopGain
						data.totalPopularityGained = data.totalPopularityGained + realPopGain
						data.totalReputationGained = data.totalReputationGained + gainedReputation
						data.lastCampaignEvent = curTime	
						
						if gameProj:getOwner():isPlayer() then
							game.addToEventBox("mass_advert_performed", {advertID = advertType, game = gameProj:getName(), popGain = realPopGain}, 1)
						end
					end
					
					if #data.advertTypes == 0 then -- if we haven't performed a single event even though enough time had passed, that means this campaign is over

						self:finishCampaign(gameProj)
					end
				end
			end
		end
	end
end

function campaign:canHaveAdvertOption(optionData)
	if optionData.unlockRequired then
		return unlocks:isAvailable(optionData.unlockRequired)
	end
	
	return true
end

function campaign:onSelected(gameProj)
	local frame = gui.create("Frame")
	frame:setSize(400, 570)
	frame:setFont(fonts.get("pix24"))
	frame:setTitle(_T("SELECT_ADVERT_TYPES_TITLE", "Select Advertisement"))
	
	local scrollBarPanel = gui.create("ScrollbarPanel", frame)
	scrollBarPanel:setPos(_S(5), _S(30))
	scrollBarPanel:setSize(390, 455)
	scrollBarPanel:setSpacing(4)
	scrollBarPanel:setPadding(4, 4)
	scrollBarPanel:setAdjustElementPosition(true)
	scrollBarPanel:addDepth(50)
	
	local costDisplay = gui.create("CostDisplay", frame)
	costDisplay:setSize(220, 27)
	costDisplay:setPos(_S(5), frame.h - _S(5) - costDisplay.h)
	costDisplay:setFont("pix24")
	
	local confirmButton = gui.create("ConfirmMassAdvertButton", frame)
	confirmButton:setSize(165, costDisplay.rawH)
	confirmButton:setFont("pix24")
	confirmButton:setPos(frame.w - _S(5) - confirmButton.w, costDisplay.y)
	confirmButton:setProject(gameProj)
	confirmButton:setCostDisplay(costDisplay)
	
	local budgetSlider = gui.create("MassAdvertBudgetSlider", frame)
	budgetSlider:setConfirmationButton(confirmButton)
	budgetSlider:setSize(390, 40)
	budgetSlider:setPos(scrollBarPanel.localX, scrollBarPanel.localY + scrollBarPanel.h + _S(5))
	budgetSlider:setFont("bh24")
	budgetSlider:setText(_T("MASS_ADVERT_SLIDER_PERCENTAGE", "Campaign budget: SLIDER_VALUE%"))
	budgetSlider:setMinMax(self.minBudget, self.maxBudget)
	budgetSlider:setValue(1)
	
	for key, option in ipairs(self.additionalAdvertOptions) do
		if self:canHaveAdvertOption(option) then
			local element = gui.create("MassAdvertSelection")
			element:setSize(370, 95)
			element:setOption(option)
			element:setConfirmationButton(confirmButton)
	
			scrollBarPanel:addItem(element)
		end
	end
	
	frame:center()
	
	frameController:push(frame)
end

function campaign:getReputationGainScaler(owner)
	local rep = studio:getReputation()
	local cutoff = self.noRepGainCutoff
	
	if rep >= cutoff then
		return 0
	end
	
	return (1 - rep / cutoff) * gameProject.DEFAULT_POPULARITY_TO_REPUTATION_MULT
end

campaign:registerAdvertOption(
	{id = "cgi_trailer", cost = 500000, display = _T("CGI_TRAILER", "CGI trailer"),
	description = _T("ADVERT_CGI_TRAILER", "Hire an outside studio to create a CGI trailer.
Sequels benefit most from this advertisement type."),
	popularityGain = 14500,
	uiIcon = "advert_cgi_trailer",
	unlockRequired = "cgi_trailer",
	minimalHypePrevGameScore = 5,
	hypeDecreasePerDelta = 0.15,
	repToPopularity = 0.1,
	perform = function(self, gameProj, multiplier, maxPopGain)
		local owner = gameProj:getOwner()
		
		local sequel = gameProj:getSequelTo()
		local basePopGain = owner:getReputation() * self.repToPopularity + self.popularityGain
			
		if sequel then
			local prevGame = owner:getProjectByUniqueID()
			
			if prevGame then
				local avgScore = prevGame:getReviewRating()
				
				if avgScore <= self.minimalHypePrevGameScore then
					local delta = self.minimalHypePrevGameScore - delta
					basePopGain = basePopGain * (delta * self.hypeDecreasePerDelta)
				end
			end
		end
		
		basePopGain = basePopGain * multiplier
		basePopGain = math.min(basePopGain, maxPopGain)
		
		local scaler = campaign:getReputationGainScaler(owner)
		local repGain, realPopGain = gameProj:increasePopularity(basePopGain, scaler > 0 and scaler, campaign.maxRepGainPerAdvertType)
		
		return basePopGain, repGain, realPopGain
	end}
)

-- lock cgi trailer behind a timer

game.registerTimedPopup("unlock_cgi_trailer", 
	_T("NEW_MASS_ADVERTISEMENT_TYPE", "New mass advert type"), 
	_T("CGI_TRAILER_UNLOCKED", "Computer-generated imagery has become somewhat popular in the mainstream media, and the video game industry is no stranger to this technology.

When starting a mass advertisement campaign you can now select the CGI trailer option."), 
	"pix24", 
	"pix20", 
	{year = 1993, month = 1},
	nil, -- fact to set

	"cgi_trailer" -- unlock ID

)

campaign:registerAdvertOption({id = "ads_on_websites", cost = 100000, display = _T("ADS_ON_WEBSITES", "Ads on websites"), 
	description = _T("ADVERT_ADS_ON_WEBSITES", "Advertise the game on various gaming sites."),
	popularityGain = 6000,
	uiIcon = "advert_ads_on_websites",
	unlockRequired = "ads_on_websites",
	repToPopularity = 0.05,
	perform = function(self, gameProj, multiplier, maxPopGain)
		local basePopGain = (gameProj:getOwner():getReputation() * self.repToPopularity + self.popularityGain) * multiplier
		basePopGain = math.min(basePopGain, maxPopGain)
		
		local owner = gameProj:getOwner()
		local scaler = campaign:getReputationGainScaler(owner)
		local repGain, realPopGain = gameProj:increasePopularity(basePopGain, scaler > 0 and scaler, campaign.maxRepGainPerAdvertType)
		
		return basePopGain, repGain, realPopGain
	end}
)

-- lock ads on websites behind a timer

game.registerTimedPopup("unlock_ads_on_websites", 
	_T("NEW_MASS_ADVERTISEMENT_TYPE", "New mass advert type"), 
	_T("ADS_ON_WEBSITES_UNLOCKED", "The internet has become very widespread in the general public and with the rise of game industry popularity, and profitability, it has become a welcome place for advertising games.

When starting a mass advertisement campaign you can now select the Ads on websites option."), 
	"pix24", 
	"pix20", 
	{year = 1996, month = 1},
	nil, -- fact to set

	"ads_on_websites" -- unlock ID

)

campaign:registerAdvertOption({id = "tv_ad", cost = 400000, display = _T("TV_AD", "TV advertisement"),
	description = _T("ADVERT_TV_AD", "Advertise the game on television."),
	popularityGain = 15000,
	uiIcon = "advert_tv_ads",
	repToPopularity = 0.1,
	perform = function(self, gameProj, multiplier, maxPopGain)
		local basePopGain = (gameProj:getOwner():getReputation() * self.repToPopularity + self.popularityGain) * multiplier
		basePopGain = math.min(basePopGain, maxPopGain)
		
		local owner = gameProj:getOwner()
		local scaler = campaign:getReputationGainScaler(owner)
		local repGain, realPopGain = gameProj:increasePopularity(basePopGain, scaler > 0 and scaler, campaign.maxRepGainPerAdvertType)
		
		return basePopGain, repGain, realPopGain
	end}
)

campaign:registerAdvertOption({id = "public_event", cost = 200000, display = _T("PUBLIC_EVENT", "Public event"),
	description = _T("ADVERT_PUBLIC_EVENT", "Hire actors to perform a public event related to the game."),
	popularityGain = 9000,
	uiIcon = "advert_public_event",
	repToPopularity = 0.01,
	perform = function(self, gameProj, multiplier, maxPopGain)
		local basePopGain = (gameProj:getOwner():getReputation() * self.repToPopularity + self.popularityGain) * multiplier
		basePopGain = math.min(basePopGain, maxPopGain)
		
		local owner = gameProj:getOwner()
		local scaler = campaign:getReputationGainScaler(owner)
		local repGain, realPopGain = gameProj:increasePopularity(basePopGain, scaler > 0 and scaler, campaign.maxRepGainPerAdvertType)
		
		return basePopGain, repGain, realPopGain
	end}
)

campaign:registerAdvertOption({id = "snack_promotion", cost = 500000, display = _T("SNACK_PROMOTION", "Snack promotion"),
	description = _T("ADVERT_SNACK_PROMOTION", "Partner with snack companies to provide various game codes with snacks."),
	popularityGain = 20000,
	uiIcon = "advert_snack_promotion",
	repToPopularity = 0.0001,
	perform = function(self, gameProj, multiplier, maxPopGain)
		local basePopGain = (gameProj:getOwner():getReputation() * self.repToPopularity + self.popularityGain) * multiplier
		basePopGain = math.min(basePopGain, maxPopGain)
		
		local owner = gameProj:getOwner()
		local scaler = campaign:getReputationGainScaler(owner)
		local repGain, realPopGain = gameProj:increasePopularity(basePopGain, scaler > 0 and scaler, campaign.maxRepGainPerAdvertType)
		
		return basePopGain, repGain, realPopGain
	end}
)

campaign:registerAdvertOption({id = "ads_on_radio_stations", cost = 150000, display = _T("RADIO_STATION_ADS", "Radio station ads"),
	description = _T("RADIO_STATION_ADS_DESCRIPTION", "Advertise your game on radio stations."),
	popularityGain = 4000,
	uiIcon = "advert_ads_on_radio",
	repToPopularity = 0.005,
	unlockRequired = "ads_on_radio_stations",
	perform = function(self, gameProj, multiplier, maxPopGain)
		local basePopGain = (gameProj:getOwner():getReputation() * self.repToPopularity + self.popularityGain) * multiplier
		basePopGain = math.min(basePopGain, maxPopGain)
		
		local owner = gameProj:getOwner()
		local scaler = campaign:getReputationGainScaler(owner)
		local repGain, realPopGain = gameProj:increasePopularity(basePopGain, scaler > 0 and scaler, campaign.maxRepGainPerAdvertType)
		
		return basePopGain, repGain, realPopGain
	end}
)

game.registerTimedPopup("unlock_ads_on_websites", 
	_T("NEW_MASS_ADVERTISEMENT_TYPE", "New mass advert type"), 
	_T("RADIO_STATION_ADS_UNLOCKED", "People listen to various radio stations every day, and a lot of information can be carried across that way. Spreading awareness about an upcoming game is no exception.

When starting a mass advertisement campaign you can now select the Radio station ads option."), 
	"pix24", 
	"pix20", 
	{year = 1998, month = 1},
	nil, -- fact to set

	"ads_on_radio_stations" -- unlock ID

)

advertisement:registerNew(campaign)